Skip to content

Python

Git for analysts: versioning SQL and scripts

How analysts can use Git to version SQL, Python, and reporting logic: repositories, commits, branches, review, and secrets.

Talha Ansari··5 min read

Key takeaways

  • Version the queries, scripts, and metric definitions, not generated output.
  • Keep each commit to one logical change with a reason in the message.
  • Use short branches and a review step for changes to published numbers.
  • Never commit credentials or customer extracts; rotate any secret that leaks.
On this page

Version the logic, not the output

A report is the visible product; the queries, scripts, and metric definitions behind it are the assets that need a history. When a number changes, the first useful question is what changed in the logic and when. Git answers that with a diff, a commit message, and an author, provided the logic lives in the repository.

Keep generated files out of version control. A CSV export or a screenshot can be regenerated; committing it adds noise and may expose customer data. Store the code, the metric definitions, and a small synthetic sample instead.

Set up a repository that fits analysis work

Create a repository per reporting domain or shared analytics codebase, not one per query. A sensible layout separates SQL, Python, documentation, and tests. Add a README that explains how to connect to a development database, run the scripts, and find the definitions.

What belongs in an analytics repository
ItemDecision
SQL models and queries behind a reportInclude; this is the logic that needs history
Python scripts and notebook exports used in productionInclude, with outputs cleared
Metric definitions and data dictionariesInclude
Small synthetic fixtures for testsInclude
Customer extracts and production dataNever commit
Large binary workbooksUsually exclude; document the source

Make each commit tell one story

A commit should describe one logical change: fix a join, add a validation, rename a metric. Mixing a formatting pass with a logic change makes review harder and hides the risk. Stage specific files or hunks until the commit is coherent.

Write the subject in the imperative and explain the reason in the body. 'Fix duplicate invoice totals in ar_aging' is useful; 'updates' is not. If the change alters a published number, say so and note the period affected.

Review the diff, stage one file, and commit with a reasonShell
git status
git diff sql/ar_aging.sql
git add sql/ar_aging.sql
git commit -m 'Exclude credit notes from invoice totals'

Use short branches and a review step

Branch for a change, open a pull request, and ask someone to review the logic. The reviewer does not need to be a Git expert; they need to understand the metric. A second pair of eyes catches a missing filter, a changed join, or a hard-coded date that will break next period.

Keep branches short-lived. Long-running branches drift from main and produce painful merges. If a change is large, split it into a sequence that each leaves the repository working. Protect the main branch so changes arrive through review rather than direct commits.

Never commit secrets or production extracts

Connection strings, API keys, and passwords belong in environment variables or a secret manager. Add .env to .gitignore and commit a blank .env.example that lists the required variable names. A key committed once remains in history even after a later commit deletes it.

If a secret is committed, treat it as exposed. Rotate it first, then remove it from history with the team's agreed process. Deleting the file in a new commit is not enough. The same rule applies to customer data: use synthetic fixtures rather than extracts.

  • Check the staged diff before every commit, not just the final one.
  • Keep .env, credentials, and data extracts in .gitignore.
  • Commit a blank .env.example that names the required variables.
  • Use synthetic sample data that preserves structure, not identities.
  • Rotate any secret that reaches a remote, even a private one.

Recover without rewriting shared history

Use git revert to undo a change that has already been shared; it creates a new commit and preserves the record. Reserve git reset for local commits that have not been pushed. Rewriting shared history forces everyone else to reconcile their work and is rarely worth the disruption.

Commit often enough that a mistake is cheap to undo, and keep experiments on a branch or in a stash. The goal is not a perfect history; it is a history someone can follow when a number looks wrong six months later.

Frequently asked questions

Do analysts need to learn the command line?+

Not all of it. A handful of commands covers most daily work: status, diff, add, commit, pull, push, and switch. A graphical client is fine if it makes diffs and history easier to read.

Should notebooks be committed to Git?+

Commit a cleaned notebook or, better, the script that reproduces the analysis. Clear outputs that contain data, keep execution order meaningful, and avoid noisy metadata changes that make every diff unreadable.

What is a good first repository?+

The SQL and scripts behind one recurring report. Add a README, a .gitignore, and a small synthetic fixture, then make a few focused commits. That teaches the workflow on a task you already understand.

How do I version a binary dashboard file?+

Decide explicitly. Some teams keep the binary for history; others document the model and rebuild it from source. Either way, version the definitions and source queries, because those are what can be reviewed and diffed.

Need help applying this?

Improve your data cleanup & pipelines.

Review the service scope or tell us what you are trying to improve. We will reply with a clear next step.

Free KPI definition template

Related reading