
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.
Table of Contents
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 it | Never track it |
|---|---|
| Shell, Python, R scripts | FASTQ, BAM, CRAM files |
| Snakemake / Nextflow workflows | Reference genomes and indices |
| environment.yml, Dockerfile, requirements.txt | Count matrices and large result tables |
| Config files, sample sheets, metadata CSVs | Notebook 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 commitgit 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 branchOne 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 --statThen 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 defaultThose 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.comNever 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.
| Situation | Command |
|---|---|
| Discard unstaged edits to one file | git restore scripts/align.sh |
| Unstage a file, keep the edits | git restore --staged scripts/align.sh |
| Undo the last commit, keep changes | git reset --soft HEAD~1 |
| See the exact lines in a file at an old commit | git show v1.0:scripts/align.sh |
| Find a commit you reset away | git reflog |
| Who last touched this line | git 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
- Create a repository for the analysis you are working on right now and add a
.gitignorecovering your data formats. - Commit the scripts exactly as they are, imperfect, with an honest message.
- Tag the current state
v0.1-workingso you have a known-good point to return to. - Create a branch, change one parameter, and merge it only if the results improve.
Related tutorials
- Bash Scripting for Bioinformatics: Automate the Same Steps Across Every Sample — the scripts that end up in the repository you are versioning
- Bioinformatics Workflows Explained: Nextflow, Snakemake, and Why Pipelines Beat Scripts — what replaces loose scripts once an analysis outgrows them
- Conda for Bioinformatics: Installing Tools Without Breaking Your Machine — pinning environments, the other half of reproducible analysis
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.
Last updated on · Written by Vijay Gladwin

