Git for SSDT Projects: Where It All Comes Together
This is the post where everything clicks.
Over the last eight posts, we’ve mapped git concepts to database analogies: repositories as databases, branches as database copies, merging as synchronization, tags as snapshots. You’ve got the vocabulary. Now let’s apply it to the workflow you actually care about — managing SQL Server database projects with version control.
SSDT (SQL Server Data Tools) projects — whether the classic .sqlproj or the newer SDK-style format — are the ideal git use case for DBAs. Every database object becomes a file. Every change becomes a commit. And git does what it does best: tracks every version of every file, forever.

Why SSDT + Git Is a Natural Fit
An SSDT project decomposes your database into individual files:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
MyDatabase/ ├── dbo/ │ ├── Tables/ │ │ ├── Customers.sql │ │ ├── Orders.sql │ │ └── OrderItems.sql │ ├── Views/ │ │ └── vw_ActiveCustomers.sql │ └── Stored Procedures/ │ ├── usp_GetCustomerOrders.sql │ └── usp_CalculateShipping.sql ├── Security/ │ └── Schemas/ │ └── reporting.sql └── MyDatabase.sqlproj |
That’s your database catalog, materialized as a file system. Every table, view, stored procedure, and schema gets its own file. This maps directly to what we discussed in Your Repository Is Just a Database — the repository is a database, and now you’re literally storing a database in it.
Your .gitignore: What NOT to Track
Before your first commit, set up a .gitignore file. SSDT projects generate build artifacts that shouldn’t be version-controlled — they’re like tempdb files. You wouldn’t back up tempdb, and you shouldn’t commit these:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# Build output bin/ obj/ # User-specific settings *.dbmdl *.jfm *.user *.suo # Publish profiles with passwords (security!) # *.publish.xml — only ignore these if they contain credentials |
The Daily Workflow
Here’s what a typical day looks like with SSDT + git. Every step uses concepts from earlier in this series.
Morning: sync up
|
1 2 |
# Get the latest from your team (Post 4: Pull) git pull --rebase origin dev |
Start a feature: branch
|
1 2 |
# Create a branch for your schema change (Post 2: Branches) git switch -c feature/add-shipping-audit-table |
Make changes: commit
Open the SSDT project, create a new table, modify a stored procedure. Each change goes into a commit:
|
1 2 3 4 5 6 7 |
# Stage and commit your new table git add dbo/Tables/ShippingAudit.sql git commit -m "Add ShippingAudit table for tracking carrier responses" # Stage and commit the proc change git add dbo/StoredProcedures/usp_CalculateShipping.sql git commit -m "Update shipping proc to write audit records" |
Before merging: clean up
|
1 2 |
# Squash those "fix typo" commits (Post 6: Rebase) git rebase -i HEAD~3 |
Tag the milestone
|
1 2 |
# Tag the pre-change state for safety (Post 7: Tags) git tag -a pre-shipping-audit -m "Schema state before shipping audit feature" |
Share your work: push and create a PR
|
1 2 |
# Push your branch (Post 4: Push) git push origin feature/add-shipping-audit-table |
Then create a pull request. We’ll talk about why PRs matter in a moment.
Merge Conflicts in Stored Procedures
Here’s where it gets real. Two DBAs modify the same stored procedure on different branches. When you merge (Merging Is Like Synchronizing Two Databases), git can’t auto-resolve the conflict:
|
1 2 3 4 5 6 7 |
<<<<<<< HEAD WHERE [o].[OrderDate] >= DATEADD(DAY, -30, GETDATE()) AND [o].[Status] = N'Active' ======= WHERE [o].[OrderDate] >= DATEADD(DAY, -90, GETDATE()) AND [o].[ShippingMethod] IS NOT NULL >>>>>>> feature/quarterly-report |
You changed the date filter to 30 days. Your colleague changed it to 90 days and added a shipping filter. Git doesn’t know which business rule is correct — that’s your call.
Fix it by combining both intents:
|
1 2 3 |
WHERE [o].[OrderDate] >= DATEADD(DAY, -90, GETDATE()) AND [o].[Status] = N'Active' AND [o].[ShippingMethod] IS NOT NULL |
Then:
|
1 2 |
git add dbo/StoredProcedures/usp_GetCustomerOrders.sql git commit -m "Resolve merge: combine date range and shipping filter" |
Pull Requests: The New Deployment Approval
This is the concept that transforms how database teams work.
A pull request (PR) is a formal request to merge your branch into another branch — typically dev or main. But it’s more than a merge request. It’s a review gate.
Before your schema change hits the shared branch:
- Another DBA reviews the SQL
- They can comment on specific lines — “This index is missing INCLUDE columns”
- Automated checks can run (build the SSDT project, check for warnings)
- You need explicit approval before the merge happens
This is your deployment approval process, codified. No more “I sent a Slack message and nobody objected, so I deployed it.” The PR is the audit trail.
For how CI/CD pipelines can automatically build and test your SSDT project on every PR, see AI for DBAs Post 14: Version Control & CI/CD. For using AI to review database code in PRs, see AI-Assisted Pull Request Reviews for Database Code.
Branching Strategy for Database Projects
Keep it simple:
main — always represents the current production schema. Never commit directly to main.
dev — integration branch. Feature branches merge here first.
feature/xxx — one branch per schema change. Create from dev, merge back to dev via PR.
hotfix/xxx — emergency fixes. Create from main, cherry-pick (Cherry-Pick Is Cross-Database INSERT…SELECT) to dev after deploying.
Try This Yourself
If you have an existing database you want to get under version control, sqlpackage can extract it. (For common pitfalls, see SqlPackage, Synonyms, and the Third-Party Database Problem.)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# Extract your database to a folder structure sqlpackage /Action:Extract /SourceServerName:"localhost" /SourceDatabaseName:"AdventureWorks" /TargetFile:"C:\Dev\AdventureWorks" /p:ExtractTarget=SchemaObjectType # Initialize a git repo cd C:\Dev\AdventureWorks git init git add . git commit -m "Initial schema extraction from AdventureWorks" # Create your dev branch git switch -c dev git push -u origin dev |
You just put a database under version control. Every concept from this series — branching, merging, rebasing, tagging, cherry-picking — now applies to your schema.
The One Sentence to Remember
SSDT + git turns your database schema into version-controlled, reviewable, branchable code — every concept from this series working together in one workflow.
Previously: Cherry-Pick Is Cross-Database INSERT…SELECT
This is the final post in the Git for DBAs series. Start from the beginning with Your Repository Is Just a Database.