Handbook contents

Learn 10~17 min

Git & pull requests

Git gives analytical code a shared history. Pull requests turn a proposed change into a reviewable decision with evidence before it becomes part of production.

Git records decisions, not copies of files

Version tools are not just important for maintaining a history of a project, they are also the foundation for a team to collaborate.
Martin Fowler, author of Refactoring and co-author of the Agile Manifesto, in Version Control Tools

Without version control, analytical work tends to accumulate copies: analysis_v2.sql, analysis_final.sql and analysis_final_comments.sql. Each file preserves a state, but the relationship between them is implicit. It is difficult to tell which change introduced a rule, why it changed or which version other people should use.

Git keeps one set of project files and records snapshots of them over time. Each snapshot—a commit—has an identifier, an author, a time and a message. Git can show the exact difference between any two snapshots, so the history records both the state of the project and the sequence of decisions that produced it.

This matters more for dbt than simple backup. A change to one line can alter a clinical population, the grain of a mart or the products that depend on it. The repository places SQL, YAML, tests, macros and documentation in one reviewable history. A future developer can see that the implementation, public contract and protecting assertions changed together.

The repository exists in two places. GitHub holds the shared remote repository. A clone on a developer's machine contains the working files and the project history. Git does not synchronise them silently: commits record work in the clone, push shares commits with GitHub and pull brings shared commits back.

History makes analytical definitions explainable

The current SQL answers “how is this calculated now?” History can answer “when did this rule change, what else changed with it and why did the team accept it?” That context is particularly valuable when a reasonable present-day implementation replaced another reasonable implementation because policy, source coverage or clinical guidance changed.

A commit message provides the concise label. The associated pull request carries the fuller rationale, evidence and discussion. Neither should try to preserve patient-level results: the permanent record contains the decision and reproducible checks, while governed data remains in the warehouse.

History becomes less useful when commits mix unrelated work or messages say only “updates”. Good Git practice is therefore part of documentation. It gives future maintainers a path from the line they are reading to the decision that introduced it.

Branches separate unfinished work from trusted production code

The project's default branch is main. It represents the reviewed code from which production is deployed, so it needs to remain in a deployable state. Direct pushes are protected. Work begins on a branch: a separate line of commits starting from a known point on main.

A branch is a movable name for a line of history, not a copied project folder. Creating one is cheap, and changes made there do not alter main. This gives developers room to compile, test, revise and even abandon an approach without making unfinished work part of the production definition.

mainproduction — always correctyour branchyour commits, made safelyABCDEFGmerge
branch off after C · commit D, E, F in safety · merge lands it all as G

In this diagram, the branch begins after commit C and records D, E and F while main continues to represent the trusted line. The histories meet only when the proposed branch is reviewed and merged.

Branches work best when they are small and short-lived. One branch should deliver one coherent outcome: a new register, a corrected age boundary or a documentation improvement. Smaller changes are easier to test and review, and they spend less time diverging from other work on main.

A merge conflict does not mean two people touched the repository at once. Git can combine changes to different files and usually different parts of the same file. It stops when two histories change overlapping lines and no mechanical choice can preserve both intentions. Short-lived branches reduce the opportunity for that overlap; when it occurs, a person decides which meaning should remain.

The staging area lets each commit tell one story

Git distinguishes the files being edited, the changes selected for the next snapshot and the commits already recorded. The middle state is the staging area. git add copies the current content of a file into that proposed snapshot; git commit records exactly what has been staged.

This extra step is deliberate. A working directory can contain an SQL change, an unrelated note and a generated file. Staging allows the SQL and its YAML to become one commit while leaving unrelated work outside it. If a staged file is edited again, the later edit is not included until the file is staged again.

the daily Git loop
git switch main                    # return to the trusted branch
git pull                           # update it from origin/main
git switch -c feat/asthma-measure  # branch from fresh main

# edit, compile, build and inspect...
git status                         # see working and staged changes
git diff                           # inspect unstaged changes
git add models/reporting/olids/fct_person_asthma_measure.sql
git add models/reporting/olids/fct_person_asthma_measure.yml
git diff --staged                  # inspect the proposed commit
git commit -m "feat: add asthma prescribing measure"
git push                           # share the branch on GitHub

git status is the safest orientation command: it reports the current branch, modified files, staged files and untracked files without changing anything. git diff shows edits that have not been staged. git diff --staged shows the exact patch the next commit will record.

That last review is important whether the commands were typed by a person, clicked in VS Code or run by an assistant. Tools can perform the mechanics; the author remains accountable for the files and information included in the snapshot.

Commits should preserve understandable steps

A useful commit has one purpose and leaves the project in a coherent state. SQL and the YAML that documents and tests the same model usually belong together. An unrelated refactor does not. The goal is not the smallest possible diff; it is a unit of history that another person can understand, review or reverse.

The project uses Conventional Commit messages: a recognised type followed by a short imperative description. The message can finish the sentence “this commit will…”:

feat: add asthma prescribing measure
fix: preserve unknown ethnicity in person demographics
docs: describe diabetes register reference time

Signed commits establish which configured identity produced the snapshot. The signing and message hooks are guardrails around provenance and readable history; they do not replace reviewing the diff.

The diff is the reviewable unit

Reviewers do not assess an abstract final file; they assess the difference between the proposed branch and main. A diff makes additions, removals and replacements visible together. It can show that a threshold changed while its description did not, or that a new join was added without a corresponding grain test.

Authors should read the diff before asking anyone else to. This catches debugging comments, generated artefacts, accidental formatting changes and unrelated files while they are cheapest to remove. It also tests whether the intended story is visible: can another person see the public contract changing alongside its implementation?

A pull request is a proposal, not a file-transfer step

Pushing a branch makes its commits visible on GitHub. It does not put them on main. A pull request proposes that the branch should become part of the trusted history and shows the complete difference from the target branch.

The PR is where an individual implementation becomes a team decision. The diff gives reviewers the evidence, the description explains the intent, automated checks report what they can establish and review threads record questions and resolutions. That discussion remains attached to the change after the branch is gone.

A useful description lets a reviewer understand the proposal before opening the first file:

  • Why: the user need, defect or domain gap that justifies the change.
  • What: the responsibility of each changed model and how the pieces fit together.
  • Checked: the builds, tests, comparisons and limitations that form the author's evidence.
  • Review: the design or domain decisions where human attention is most valuable.

A draft PR is useful before the change is ready to merge. It shares the direction, runs fast automation and gives collaborators a place to discuss an approach while revision is still cheap. “Draft” describes readiness, not quality.

Small pull requests improve velocity because they shorten the feedback loop. Reviewers can hold the change in their heads, comments arrive while the author still remembers the decisions and the branch merges before it drifts far from main. Splitting one coherent contract across several dependent PRs can make review harder, however. The unit should be small enough to reason about and complete enough to evaluate.

A small diff can still have a large blast radius

Review effort should follow semantic impact, not line count. Rewording a description may be low risk. Changing one operator in a shared register can alter thousands of rows and every downstream product. The PR should use lineage to identify affected consumers and explain whether the change is additive, corrective or contract-breaking.

Evidence should be proportional to that impact. A new description may need only compilation and review. A grain change needs downstream builds, comparison of old and new populations and coordination with consumers. A programme-specific published change should demonstrate that shared marts remain unaffected.

This is why the PR is more than a mechanism for moving code. It is the place where scope, risk and evidence are made legible enough for the team to decide whether the change is ready.

Automation and review answer different questions

The path from PR to merge uses several forms of evidence. They overlap, but none is a substitute for the others.

EvidenceWhat it is good atWhat it cannot establish alone
Fast CI gatesCompilation, refs, project structure and enforceable conventionsWhether the analytical concept is the right one
Snowflake DEV validationBuilding changed models and running tests against development dataUnstated requirements or edge cases absent from the data
Automated code reviewRepeatable patterns, likely bugs, fan-out risks and missing metadataProgramme authority, clinical intent and organisational context
Human reviewArchitecture, domain correctness, scope and maintainabilityExhaustive mechanical checking on every change

On this project, fast gates run when a PR opens or changes. CodeRabbit reviews a ready PR against the repository's rules and common coding risks. Snowflake DEV validation runs when its trigger is met and supplies evidence from real development data. Required checks protect main by preventing merge while their conditions fail.

An automated review comment is evidence, not an instruction. It may reveal a genuine staging-boundary violation or fan-out risk; it may also lack the context that makes a line correct. The author should fix valid findings and explain why an inapplicable one is being resolved. Silently accepting every suggestion gives the tool more authority than its evidence supports.

Human review protects meaning

A human reviewer should spend most attention where context changes the answer. Does the model represent the intended clinical population? Is a programme rule being allowed to redefine a shared domain concept? Does the grain match the advertised contract? Could the change reuse an established model? Will the next developer know where to alter the definition?

Useful comments identify an observation, explain its consequence and suggest a direction or ask a question. “This join can return several practices per person, so the model no longer appears to meet its person-grain contract. Which effective-date rule should select the practice?” gives the author a claim they can verify and a decision they can resolve.

Review is not an attempt to make the change resemble the reviewer's preferred style. Its purpose is to improve correctness, shared understanding and the project's ability to change safely later.

Review is an asynchronous design conversation

A review thread should preserve the reasoning that resolves a concern. The author may change the code, explain why the existing approach is correct or propose a third option. A brief reply describing the resolution is more useful than silently pushing a change and marking the thread complete.

New commits on the same branch update the PR while retaining that conversation. Reviewers can focus on the new diff and confirm that the resolution matches the discussion. This allows work to happen asynchronously without losing the chain of reasoning that a meeting or direct message would otherwise hold.

A failed check is part of the loop

Red CI does not require a new branch or PR. Open the failed check, identify the first relevant error, reproduce it where possible, fix the branch, commit and push. The existing PR updates and the relevant checks run again.

Re-running an unexplained failure may be appropriate for a known transient infrastructure problem, but it should not be the default response. Preserving the same PR retains the diff, discussion and review history while the proposal improves.

Protected main makes continuous delivery trustworthy

Branch protection is a constraint that enables speed. Because every change reaches main through a PR with the required review and checks, deployment automation can treat main as the project's approved state. Production workflows do not need a separate manual process to determine which files are trustworthy.

When a PR is approved and green, it is squash-merged: the branch's work becomes one tidy commit on main, the feature branch can be deleted and deployment takes over. The PR still preserves the detailed discussion and original commits for context.

Version control also makes recovery deliberate. If a merged change must be undone, a revert records a new commit that reverses the earlier diff without erasing history. That is different from silently editing production back to an earlier state: the correction receives its own review, evidence and explanation.

The loop then begins again from fresh shared history:

git switch main
git pull
git switch -c feat/the-next-change

Pulling before the next branch ensures that it begins with the changes colleagues have already merged. Git supports parallel work because the team repeatedly rejoins the same trusted line.

Open definitions require a strict data boundary

The dbt project is public deliberately. Open analytical definitions can be inspected, challenged and adapted by others. Users can trace a result through documented SQL and lineage. The repository contains instructions for transforming data; the underlying person-level data remains in Snowflake under separate access controls.

Code, YAML, tests and documentation belong in Git. Patient data, row-level query results, credentials and private personal information do not. That applies to comments, screenshots, test fixtures, PR descriptions and copied error output as well as obvious CSV extracts.

.gitignore keeps predictable untracked files such as target/, logs, local environments and credentials out of normal Git status. It is not a security scanner. It does not inspect file contents and does not stop Git tracking a file that was already added.

Seeds are a deliberate exception for small, non-sensitive reference data that is reviewed and versioned as part of the project. They are not a convenient place for an extract. If sensitive data or a credential is committed, deleting the line in a later commit does not undo the disclosure; notify the appropriate team immediately so the incident or secret rotation can be handled.

Worked example: changing a shared clinical definition

Suppose a request proposes changing an asthma measure's prescribing window. Discovery shows that the measure is shared by several programmes, so the work is not merely a dashboard edit. The branch should contain the shared definition change, its updated documentation and tests, plus any intentional downstream adjustments needed to preserve product contracts.

The commits might first update and test the shared measure, then adapt a published product that deliberately needs the old programme-specific window. Each commit records an understandable step; together, the branch delivers one reviewable outcome.

The PR description explains why the definition is changing, distinguishes the organisation-wide rule from the programme exception and records the models built and comparisons performed. Lineage identifies affected consumers. CI establishes that the project compiles and tested data contracts still hold. Human review decides whether the authority and clinical interpretation are correct.

After merge, future readers can find the decision in one place: the SQL that implements it, the YAML that states it, the tests that protect it, the commit that records it and the PR discussion that explains why the project chose it.

Tools may type the commands; the change remains yours

VS Code exposes branches, changed files, staging, commits and synchronising as buttons. Coding assistants can run the entire loop. These tools remove command recall from the job, which is useful. They do not change the underlying states or transfer accountability.

Before a commit, inspect the staged diff. Before a push, know which commits and files will be shared. Before a merge, read the PR as the permanent record it will become. Understanding the Git model is what lets a developer use higher-level tools confidently rather than treating their actions as magic.

A change-delivery checklist

  1. Update main and create a focused branch.
  2. Keep SQL, documentation and tests for one contract change together.
  3. Use status and both forms of diff to inspect working and staged changes.
  4. Commit coherent steps with messages that explain their purpose.
  5. Open a draft PR early when feedback on the direction would reduce rework.
  6. Describe why, what, checks performed and the decisions needing review.
  7. Treat CI, automated review and human review as complementary evidence.
  8. Keep data, credentials and private information out of every committed file and PR artefact.
  9. Merge only when required evidence and review agree, then begin the next change from fresh main.

The full Git essentials course teaches the commands interactively. The official Git book explains snapshots and the staging area, while GitHub's pull-request documentation covers the collaboration model.

From private edit to shared decision

0/4 answered
  1. 1You edited three files but staged only the SQL and YAML for one model. What does the next commit contain?

  2. 2Why can a passing CI run not establish that a register change is clinically correct?

  3. 3A required check fails on a PR. What normally happens next?

  4. 4What is the best reason to keep a pull request focused?