Site icon Ampersand Tutorials

Git and GitHub for Bioinformatics: Version Control for Analysis Projects

Branching tree of light forming a network over a dark field, representing version control branches

Six months into a project, someone asks which settings produced a figure. You have align_v3.sh, align_v3_fixed.sh and align_v3_fixed_USE_THIS.sh in a folder, and none of them records why the second one was abandoned. This is not a discipline problem; it is a tooling problem, and Git is the fix.

Git is a version control system: it stores snapshots of your text files so you can see what changed, why, and undo any of it. GitHub is a hosting service for Git repositories. You need the first for your own sanity and the second only when you collaborate or want a backup off your laptop.

What belongs in a bioinformatics repository

The single most important decision is what you track. Git stores full snapshots of every version of every file it tracks, which is ideal for scripts and catastrophic for sequencing data. A 40 GB BAM committed once will make the repository unclonable for everyone, and it cannot simply be deleted later because the history still contains it.

Track itNever track it
Shell, Python, R scriptsFASTQ, BAM, CRAM files
Snakemake / Nextflow workflowsReference genomes and indices
environment.yml, Dockerfile, requirements.txtCount matrices and large result tables
Config files, sample sheets, metadata CSVsNotebook outputs and checkpoint files
Small test fixtures (a few KB)Anything with credentials or API keys

Results are generated, not authored. If a directory can be rebuilt by running a command, it belongs in .gitignore, not in the repository.

Create the repository and the ignore rules together

mkdir ampcrispr-analysis && cd ampcrispr-analysis
git init
git config user.name "Your Name"
git config user.email "you@lab.edu"

cat > .gitignore <<'EOF'
# sequence data
*.fastq
*.fastq.gz
*.fq.gz
*.bam
*.bam.bai
*.cram
*.vcf.gz

# derived results
results/
qc/
*.html
!docs/*.html

# tooling
.ipynb_checkpoints/
__pycache__/
.Rhistory
.Rproj.user
.snakemake/
*.log

# secrets
.env
*.pem
EOF

git add .gitignore
git commit -m "Initialise project with ignore rules for sequencing data"

Write .gitignore before your first git add. Once a 2 GB file is in the index, removing it means rewriting history — a recoverable but genuinely annoying detour that beginners hit constantly.

The *.log line is worth a comment. Pipeline logs look like valuable provenance, and they are, but they change on every run and turn every commit into a wall of noise. Keep logs in results/, and record the run configuration instead.

The four commands that cover most of your day

Git has a reputation for complexity, but daily use reduces to a small loop. You check what changed, you stage what you want, you describe it, you save it.

git status              # what is modified, staged, untracked
git diff                # exact line changes, not yet staged
git add scripts/align.sh
git commit -m "Add BWA-MEM2 alignment step with read-group tags"

git log --oneline -5    # recent history
git diff HEAD~1         # what changed in the last commit

git add stages a change and git commit records it. Between them you can review with git diff --staged, which is the cheapest code review you will ever get. Keeping commits small enough to review in a minute is the whole skill.

What a good commit message looks like

Describe the reason, not the diff. The diff already says which lines changed; the message should say what problem you were solving.

# weak
git commit -m "update script"

# strong
git commit -m "Filter MAPQ < 20 before variant calling

Low mapping quality reads were inflating heterozygous calls in
repetitive regions. bcftools mpileup now receives pre-filtered BAMs.
Affects all samples in batch 2."

Branching for experiments

A branch is a movable pointer to a commit. Branches let you try a different aligner or a new filtering threshold without disturbing an analysis that currently works. If the experiment fails, you delete the branch and nothing is lost; if it succeeds, you merge it.

git switch -c try-minimap2          # new branch from current state
# ... edit, run, commit ...
git switch main                     # back to the working analysis
git merge try-minimap2              # bring the experiment in
git branch -d try-minimap2          # delete the merged branch

One caution specific to science: a branch is not an experiment log. Record the run outputs and parameters in a results file or a lab notebook as well, because a branch that is deleted takes its context with it. Branch for code, document for results.

Tags: marking the analysis behind a figure

When a figure is accepted or a paper is submitted, tag the exact commit. Tags are permanent names for a moment in history; unlike branches they are not expected to move.

git tag -a v1.0-paper -m "Analysis frozen for submission, 2026-11-07"
git show v1.0-paper --stat

Then the methods section can carry a single line: analysis code version v1.0-paper, commit 4f2a9c1. Reviewers increasingly expect this, and it costs you nothing at submission time if you tagged as you went.

Backing up and collaborating on GitHub

GitHub is optional for solo work but useful for three things: an off-machine backup, code review, and sharing workflows with collaborators. Create an empty repository on GitHub, then connect it.

git remote add origin git@github.com:yourlab/ampcrispr-analysis.git
git branch -M main
git push -u origin main
git push origin v1.0-paper      # tags are not pushed by default

Those last two words matter: plain git push does not send tags, so your carefully created release tag will be missing from GitHub unless you push it explicitly or use git push --tags.

For collaboration, work on branches and open pull requests rather than pushing straight to main. A pull request gives your colleague a place to comment on the filtering logic before it reaches the shared branch, which is exactly the review a methods section deserves.

If you have no SSH key yet

ssh-keygen -t ed25519 -C "you@lab.edu" -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub
# paste the public key at github.com/settings/keys, then test:
ssh -T git@github.com

Never paste the private key (the file without .pub) anywhere. If you already committed a credential — an API key in a config file, a database password in a notebook — rotate the key first, then clean history. Rewriting history does not un-leak something that was already public.

Recovering when something breaks

Git’s real value shows up on the bad day. Someone overwrites a working script, a merge goes wrong, or you committed to the wrong branch.

SituationCommand
Discard unstaged edits to one filegit restore scripts/align.sh
Unstage a file, keep the editsgit restore --staged scripts/align.sh
Undo the last commit, keep changesgit reset --soft HEAD~1
See the exact lines in a file at an old commitgit show v1.0:scripts/align.sh
Find a commit you reset awaygit reflog
Who last touched this linegit blame -L 40,60 scripts/align.sh

git reflog is the safety net nobody mentions. It records every position HEAD has held, including commits that reset appeared to destroy, for around 90 days. Almost nothing is truly lost in a repository you have been committing to regularly.

The limits: what Git cannot do for you

Git stores text efficiently and binaries badly. If you genuinely must version large data or models, use Git LFS or a data-versioning tool like DVC, which keeps small pointer files in Git and pushes the blobs to separate storage. For most projects the simpler answer is better: commit the recipe, keep the data on a filesystem with its own backup, and let a checksum in the sample sheet tie them together.

The same instinct — record what you did so someone else can repeat it — is what every reproducible analysis depends on, whether that is a container definition, a pinned conda environment or a commit hash. If you want to see how version control, environments and containers fit together across a whole project, Ampersand Academy walks through it end to end.

A first hour you can actually finish

  1. Create a repository for the analysis you are working on right now and add a .gitignore covering your data formats.
  2. Commit the scripts exactly as they are, imperfect, with an honest message.
  3. Tag the current state v0.1-working so you have a known-good point to return to.
  4. Create a branch, change one parameter, and merge it only if the results improve.

Can I put sequencing data in Git?

Practically no. Git stores a full copy of every version of every file, so a 40 GB BAM makes the repository permanently enormous and painful for anyone to clone. Track scripts and configuration; keep data on a backed-up filesystem, referenced by path and checksum.

Do I need GitHub to use Git?

No. Git works entirely offline with local repositories. GitHub adds an off-site backup, collaboration, and a place to publish workflows. Many analysts use Git locally for years before pushing anywhere.

What should I do if I committed an API key?

Rotate or revoke the key immediately — that is the only action that actually protects you, because the value may already have been cloned or cached. Then remove it from the working tree and history. Deleting the commit alone does not un-leak the secret.

Is Git LFS a good idea for bioinformatics data?

It helps for moderate files you truly need versioned, such as small reference panels or model weights. It does not make gigabyte-scale sequencing data convenient, and many institutions restrict LFS storage. Prefer DVC or plain filesystem storage with checksums for real data.

How do I record the analysis version in a paper?

Tag the commit at submission, for example v1.0-paper, push the tag, and cite the tag plus the commit hash in the methods section or a code availability statement. That identifier is specific enough to retrieve the exact scripts that produced the figures.

What is the difference between git pull and git fetch?

fetch downloads new commits without touching your working files; pull fetches and then merges them into your current branch. When you want to inspect what a collaborator changed before accepting it, fetch first and review the diff.

Exit mobile version