Data Engineering  /  dbt

๐Ÿ”„ dbt โ€” Data Build Tool 60 guides ยท updated 2026

Analytics engineering with SQL โ€” models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

dbt Source Control: Managing a dbt Project With Git the Right Way

A dbt project is, at its core, a folder of SQL and YAML files โ€” which means every discipline that applies to managing application code with Git applies here too, but with a few dbt-specific wrinkles worth knowing before they cost you a broken production deploy.


Basic Repository Structure

A dbt projectโ€™s Git repository typically tracks the source, deliberately excluding generated artifacts:

.gitignore:
target/
dbt_packages/
logs/
.user.yml

target/ and dbt_packages/ are generated/downloaded on every run โ€” committing them bloats the repository with regenerable content and, worse, can cause confusing merge conflicts on files that should never be manually edited or reviewed in the first place.


Branching Strategy

Most dbt teams use a conventional feature-branch workflow, no different from application code:

Terminal window
git checkout -b feature/add-customer-ltv-model
# make changes to models, tests, docs
git add models/marts/customer_ltv.sql models/marts/_customer_ltv.yml
git commit -m "Add customer lifetime value model with tests"
git push origin feature/add-customer-ltv-model

A pull request against main (or whatever the production-tracking branch is called) is where CI validation, covered in dbt CI/CD Integration, runs automatically before merge.


What Actually Belongs in a dbt Pull Request

A well-scoped dbt PR usually bundles four things together for a single logical change:

models/marts/customer_ltv.sql โ† the model itself
models/marts/_customer_ltv.yml โ† description + tests
tests/assert_ltv_never_negative.sql โ† any custom test, if needed
docs/customer_ltv_description.md โ† doc block, if the description is long

Reviewing a model without its accompanying tests and documentation is reviewing half the change โ€” a good team convention is treating โ€œmodel with no testsโ€ as an automatic PR review flag, not an acceptable default.


Reviewing dbt Pull Requests Effectively

Reviewing dbt SQL differs from reviewing application code in a few specific ways worth calling out to reviewers:


Handling Merge Conflicts in YAML Schema Files

Multiple engineers frequently touch the same _models.yml file to add tests or descriptions to different models within it, and merge conflicts here are common. Splitting large schema YAML files by model (one YAML file per model or small group, rather than one giant file per folder) significantly reduces conflict frequency:

models/staging/
_stg_orders.yml โ† instead of one giant _staging.yml
_stg_customers.yml
stg_orders.sql
stg_customers.sql

Protecting the Production Branch

A production-tracking branch (commonly main) should require CI checks to pass before merge โ€” at minimum, dbt build succeeding against a CI environment, ideally with the source freshness and testing gates covered in dbt CI/CD Integration. Direct pushes to this branch, bypassing review and CI, are the most common way a broken model reaches production undetected.

Branch protection rule (GitHub example):
- Require pull request before merging
- Require status checks to pass (dbt build, dbt test)
- Require at least 1 approving review

Tagging Releases

For teams that deploy on a schedule rather than continuously, tagging specific commits as releases gives a clean rollback point and a clear record of what was actually running in production at any given time.

Terminal window
git tag -a v2026.07.01 -m "Weekly production release"
git push origin v2026.07.01

Commit Message Conventions Worth Adopting

Because a dbt commit often touches a model, its tests, and its documentation together, a commit message describing the business intent โ€” not just the mechanical change โ€” makes future history far more useful.

Terminal window
# Less useful
git commit -m "update customer_ltv.sql"
# More useful
git commit -m "Add customer_ltv model with churn-adjusted revenue calculation
Adds not_null and range tests on the ltv_value column.
Depends on the new subscription_tenure staging model."

Six months later, git log --follow models/marts/customer_ltv.sql becomes a genuinely readable history of why the model evolved the way it did, rather than a list of โ€œupdate modelโ€ commits that require opening every diff individually to understand what actually changed and why.

Common Mistakes

Committing target/ or dbt_packages/. This bloats the repository and can cause spurious merge conflicts on files nobody should be hand-editing or reviewing.

Reviewing only the Jinja source, never the compiled output. Templating bugs are frequently invisible in the raw file and only obvious in the fully-rendered SQL.

Allowing direct pushes to the production branch. This is the single most common way a broken or untested change reaches production without ever going through CI validation.

One giant schema YAML file per folder. This significantly increases merge conflict frequency as a team grows โ€” splitting by model avoids most of it.

Summary

PracticeWhy it matters
.gitignore for target/, dbt_packages/Avoids bloat and spurious conflicts on generated content
Reviewing compiled SQL, not just JinjaCatches templating bugs invisible in raw source
Branch protection with required CI checksPrevents unreviewed or failing changes from reaching production
Per-model YAML schema filesReduces merge conflict frequency as team size grows

Git discipline for a dbt project isnโ€™t fundamentally different from any other codebase โ€” the main adjustments are reviewing compiled output for Jinja correctness and treating tests as a mandatory, not optional, part of every model change.