Reproducibility exercise: git and pixi
In this exercise, you will turn the molecular clock analysis from Capstone Project 2 into a reproducible project: one that anyone (including you, a year from now) can download and rerun with a single command, using exactly the same software, and get exactly the same results. Along the way, you will:
Use git to keep track of every change to your project, and GitHub to back it up
Use pixi to create a project environment with all the software needed for the project, including:
- Python
- Python packages
- IQ-TREE, the program I used to estimate the tree of 24 SARS-CoV-2 genomes in Capstone Project 2
Estimate that tree yourself, fit the molecular clock to it, and record every step, so that the whole analysis can be rerun with one command
Prove that your project is reproducible by deleting your project and rebuilding everything from your GitHub repository
There is no new Python or biology in this exercise. Everything new is about how we do the work.
The exercise has five parts. Each part takes about 15–30 minutes, and each one ends with saving your work to GitHub, so you can stop at the end of any part and pick up where you left off later. Put your answers to the questions into the Exercise 13 quiz on Canvas.
Your GitHub codespace is temporary. It stops on its own after 30 minutes of inactivity, and GitHub deletes codespaces that haven’t been used for 30 days, even if they contain work you haven’t pushed to GitHub. Your work is only safe once you have committed it and pushed it to GitHub (you’ll learn how in Part 1).
Part 1: Your project repository
Make your copy and open your codespace
Go to the Comp Bio project template on GitHub, and follow the instructions in its README under the following two sections ONLY:
- “Make your own copy of this repository”: Name your repository
comp-bio-project, and choose Public - “Open your codespace”: When you get a “Do you trust the authors of the files in this folder?” pop-up, click “Trust Folder & Continue”
Stop and come back here once you get to “Set up your project environment” (don’t set up the environment; we will do that below)
- “Make your own copy of this repository”: Name your repository
Once your codespace is ready, you should see your files on the left side of VS Code and a terminal at the bottom. Click in the terminal and check that you’re in your project’s folder:
Terminal
pwd ls -apwd(print working directory) tells you what directory (“folder”) you are in, which should be/workspaces/comp-bio-project. This folder you are in is called your working directory.ls -alists all files and directories in your working directory. When you run this command, you should seeREADME.mdand a folder named.devcontainer(plus a folder named.git; more on that below).
The terminal is the same Bash command line you used in the Bash exercise, running on a Linux computer in the cloud. All of the commands in this exercise are typed in the terminal.
Hover over any code block on this page and click the clipboard icon in its upper right corner to copy it. To paste into the terminal, use Ctrl + Shift + V (or right-click and choose “Paste”).
What is a git repository?
Your project folder is a git repository. That means git is keeping a history of the folder in the hidden .git folder. (Never edit or delete the .git folder!) The history is a series of commits: snapshots of your files, each with a message describing what changed, who changed it, and when. Your repository on GitHub is a copy of the same history.
Making a commit takes three steps:
- Change some files (e.g., edit them, create them, or delete them)
- Stage the changes you want to include in the next commit, with
git add - Commit the staged changes, with
git commit, which saves the snapshot in your repository’s history
Then, git push sends your new commits to your copy of the repo on GitHub.
Your first commit
Let’s see what git thinks of our repository right now by running these two commands in the terminal:
Terminal
git status git loggit statusshould saynothing to commit, working tree clean, which means none of your files have changed since the last commit.git loglists the commits in your repository’s history (just one so far, which GitHub made when you copied the template).NoteLeavinggit log(andgit diff)When the output of a git command is longer than your terminal, git shows it one page at a time. Use the arrow keys to scroll, and press q to quit and get back to the command line.
In the file list on the left of VS Code, click
README.mdto open it. At the very top of the file, add these lines (with your name), and leave a blank line between them and the rest of the file (note: To add the text below to theREADME.mdfile, you will edit it using the top window of VS Code, not the terminal):README.md
# Dating the ancestor of SARS-CoV-2 Author: Your Name A reproducible molecular clock analysis of 24 SARS-CoV-2 genomes, for BIOL 5/6800.VS Code saves automatically after a moment (or press Ctrl + S).
Now, return to the terminal window, and ask git again:
Terminal
git statusNote: You will need the output from this command for Question 2 below.
Add your change to git’s staging area. This prepares it for the next commit:
Terminal
git add README.mdAsk git for the status again:
Terminal
git statusNote: You will need the output from
git statusfor Question 2 below.Commit your change to git’s database, with a message that says what you changed:
Terminal
git commit -m "Adding project title and author to README"Now, when you run:
Terminal
git logyou should see two commits, with yours at the top. The long string of letters and numbers next to each commit is its hash: a unique ID for that snapshot.
Send your commit to GitHub:
Terminal
git pushGo to your repository’s page on GitHub (
github.com/your-username/comp-bio-project) and refresh it. Your README should show your changes, and near the top of the file list, you should see your commit message.
-m with git commit
If you forget the -m "message" part, git opens a text editor in the terminal and waits for you to type a message there. If that happens, the easiest way out is to close the editor: If the bottom of the terminal shows ^X Exit, press Ctrl + X; if it shows a lot of ~ characters, type :q! and press Enter. Then run the git commit command again with -m.
Questions
Question 1: Paste the URL of your GitHub repository (e.g., https://github.com/your-username/comp-bio-project).
Question 2: What did git status say about README.md (a) after you edited it, but before git add, and (b) after git add, but before git commit?
Part 2: A reproducible environment
Just like any computer, your codespace already has software that came with the codespace’s operating system. Someone else’s computer (or this codespace, next year) will have different versions of software, with different packages installed, or none at all. If our analysis depends on “whatever is installed on this computer,” we can’t expect it to give the same results anywhere else.
To get around this problem, we will use pixi to create a project environment: a folder of software that belongs to this project, with every package’s version written down so that the environment can be rebuilt exactly, anywhere.
Create the environment
Tell pixi to start managing an environment for this project:
Terminal
pixi initThen, ask git to show you the status of things:
Terminal
git statuspixi initcreated three files:pixi.toml: the manifest, which lists what our project needs.gitignore: a list of files and folders that git should ignore.gitattributes: settings that tell git how to treat certain files
Click
pixi.tomlin VS Code to open it. It doesn’t list any software yet; the[dependencies]section is empty.Return to the terminal window and add Python and the Python packages we need using the following command:
Terminal
pixi add "python=3.14.*" numpy scipy matplotlibThis takes a minute or two. pixi figures out a set of package versions that work together (e.g., which versions of NumPy work with Python 3.14), downloads them from conda-forge (a large, free collection of scientific software), and installs them into a hidden folder named
.pixiin your project.The
"python=3.14.*"part asks for Python version 3.14 (any 3.14.x release), instead of whatever version is newest on the day you run the command, which might not be compatible with your project’s code or the packages it depends on.Look at
pixi.tomlagain. The[dependencies]section now lists each package with a range of versions (something likenumpy = ">=2.3.5,<3"). There is also a new file,pixi.lock, the lock file. Open it and scroll through it (it’s long!). It lists the exact version of every package that pixi installed, including the packages that our packages need, and the address it was downloaded from.- The manifest (
pixi.toml) says what we asked for - The lock file (
pixi.lock) says exactly what we got
When pixi builds the environment from a lock file, it installs exactly those versions.
- The manifest (
Confirm the version of Python pixi installed in our environment:
Terminal
pixi run python --version pixi listpixi runruns a command inside the project environment.pixi listlists every package in the environment, and its version (pixi installed a lot of packages that the packages we requested depend on!).
Commit the environment
Check what has changed:
Terminal
git statusNotice that the
.pixifolder, which holds the installed software, is not listed. Look in.gitignoreto see why. We never commit the installed software, only the instructions for rebuilding it (pixi.tomlandpixi.lock).Prepare (stage) the four new files for the next commit:
Terminal
git add pixi.toml pixi.lock .gitignore .gitattributesThen, commit the new files to the git database:
Terminal
git commit -m "Creating a pixi environment with Python 3.14"Lastly, use
git pushto back up your project database to GitHub:Terminal
git push
Add a program that isn’t Python
IQ-TREE is written in C++, not Python, so you can’t install it with Python’s usual tools (and it doesn’t work in molab). But it is available from bioconda, a collection of bioinformatics software that works with pixi.
Tell pixi to look for software in bioconda (after conda-forge):
Terminal
pixi workspace channel add biocondaThen, add IQ-TREE:
Terminal
pixi add iqtreeCheck to see which version of IQ-TREE pixi installed:
Terminal
pixi run iqtree3 --versionSee what changed, using
git diff, which shows exactly which lines changed in each file since the last commit:Terminal
git status git diff pixi.toml git diff --statIn
git diff, lines that start with-(in red) were removed, and lines that start with+(in green) were added.git diff --statsummarizes how much each file changed. (pixi.lockshows up asBinbecause the.gitattributesfile tells git not to bother showing line-by-line changes for it; it’s written by pixi, not by people.)Prepare the changes to
pixi.tomlandpixi.lockfor the next commit:Terminal
git add pixi.toml pixi.lockCommit the changes and push them to GitHub:
Terminal
git commit -m "Adding IQ-TREE from bioconda" git push
Questions
Question 3: What version of Python did pixi run python --version report?
Question 4: What range of versions does pixi.toml list for numpy? What exact version of NumPy is in your environment (look for it in pixi list)? Why is it useful to have both?
Question 5: What version of IQ-TREE is in your environment (look for iqtree in pixi list)? Write it down; you will need it in Part 5.
Question 6: Why doesn’t git list the .pixi folder in git status, and why don’t we want to commit it?
Part 3: Estimating the tree with IQ-TREE
To make an analysis reproducible, we need to record not only the software, but also the steps: the exact commands we ran, in order. pixi can store commands in pixi.toml as tasks, which anyone can run with pixi run <task name>.
Get the data
Add a task that downloads the alignment of 24 SARS-CoV-2 genomes we’ve been using (it’s one long command; use the copy button!):
Terminal
pixi task add get-data "mkdir -p data && curl -L -o data/sars-cov-2.fasta https://raw.githubusercontent.com/phyletica/intro-to-comp-bio/refs/heads/main/notebooks/data/SARS-CoV-2.nextclade.aligned.fasta" --description "Download the SARS-CoV-2 alignment"Look at the new
[tasks]section ofpixi.tomlto see the task. The-poption tellsmkdirnot to complain if thedatafolder already exists, so the task still works if we run it again.Now, run the task:
Terminal
pixi run get-dataCheck that it worked:
Terminal
grep ">" data/sars-cov-2.fastaYou should see the names of the 24 genomes.
The alignment is small (about 700 KB), so we will commit a copy of it. That way, our project still works even if the file at that web address changes or disappears, and the task records where the data came from.
Stage the new task (in
pixi.toml) and the data file for the next commit (git add), and then commit them to git’s database:Terminal
git add pixi.toml data/sars-cov-2.fasta git commit -m "Adding task to download the SARS-CoV-2 alignment, and the data"
GitHub doesn’t allow files larger than 100 MB, and repositories should stay under a few GB. For big data sets, we would commit only the task that downloads the data from a permanent public archive (like GenBank), and tell git to ignore the data files.
Run IQ-TREE
Add a task that runs IQ-TREE on the alignment:
Terminal
pixi task add tree "mkdir -p results && iqtree3 -s data/sars-cov-2.fasta -m HKY -seed 1 -T 1 --prefix results/tree --redo" --description "Estimate a maximum likelihood tree with IQ-TREE"Here’s what each part of the IQ-TREE command does:
Option What it does -s data/sars-cov-2.fastaThe alignment to analyze -m HKYUse the HKY model of sequence evolution (as in Capstone Project 2) -seed 1The seed of IQ-TREE’s random number generator (more on this in Part 4) -T 1Use one processor (the results can differ slightly with more) --prefix results/treeBegin all output files with the prefix results/tree.*--redoOverwrite the output files if they already exist Run it:
Terminal
pixi run treeIQ-TREE prints a lot as it searches for the maximum likelihood tree, but it only takes a few seconds. Near the end, look for the
BEST SCORE FOUND: the log-likelihood of the best tree it found.See what IQ-TREE created:
Terminal
ls results git status
Decide what to commit
IQ-TREE writes several files. The one we need is results/tree.treefile, which has the tree in Newick format (click it in VS Code to take a look). The others are reports, logs, and temporary files that IQ-TREE can recreate in a few seconds. Open results/tree.log and look at its first few lines: it records the date and time IQ-TREE ran, so it changes every time we run IQ-TREE, even when the results are exactly the same. It’s not worth cluttering our git database with these trivial changes.
We’ll tell git to ignore IQ-TREE’s other files.
Open
.gitignorein VS Code, and add these lines to the end of the file:.gitignore
# IQ-TREE files that we can recreate, and that change every time IQ-TREE runs results/*.log results/*.iqtree results/*.ckp.gz results/*.bionj results/*.mldistEach line is a pattern, where
*means “anything.” For example,results/*.logmatches every file inresultswhose name ends with.log. Lines that start with#are comments.Check that git now ignores those files (only
results/tree.treefileshould be listed in theresultsfolder):Terminal
git status --untracked-files=allStage the changes to
.gitignoreandpixi.tomland the tree estimated by IQ-TREE:Terminal
git add .gitignore pixi.toml results/tree.treefileCommit the changes in git’s staging area to the database:
Terminal
git commit -m "Estimating the ML tree with IQ-TREE"And sync your updated database with the copy on GitHub:
Terminal
git push
Questions
Question 7: What was the log-likelihood of the best tree IQ-TREE found (the BEST SCORE FOUND)?
Question 8: Why is it a good idea to tell git to ignore files like results/tree.log?
Part 4: Fitting the clock, and using your project’s history
Add the analysis script
Make a new
scriptsdirectory (“folder”):Terminal
mkdir scriptsDownload the
clock.pyscript that fits the molecular clock into thescriptsdirectory:Terminal
curl -L -o scripts/clock.py https://raw.githubusercontent.com/phyletica/intro-to-comp-bio/refs/heads/main/scripts/clock.pyOpen
scripts/clock.pyin VS Code and skim it. It’s the same code we used in Capstone Project 2, as a plain Python script instead of a notebook: It reads IQ-TREE’s tree, calculates each genome’s distance from the root, fits the clock, prints the results, and saves a plot toresults/clock.png.Now, try running the
clock.pyPython script:Terminal
pixi run python scripts/clock.pyWhen it finishes, click
results/clock.pngin VS Code to view the plot the script created.Add a task for it. The
--depends-on treepart tells pixi that this task needs thetreetask to run first:Terminal
pixi task add clock "python scripts/clock.py" --depends-on tree --description "Fit a molecular clock to the tree"You can verify that pixi added the task using:
Terminal
pixi task listNow, run the new
clocktask:Terminal
pixi run clockOne command,
pixi run clock, runs the whole analysis, from estimating the tree to creating the plot. Nice!Add the script, the plot it created, and the changes to
pixi.tomlto git’s staging area:Terminal
git add pixi.toml scripts/clock.py results/clock.pngCommit the staged changes to the database, and push the new commit to GitHub:
Terminal
git commit -m "Adding the molecular clock script and task" git push
Change the analysis
In Capstone Project 2, we found that Omicron doesn’t follow the clock. Let’s change the analysis to leave Omicron out, and use git to see and record the change.
Open the
scripts/clock.pyfile in the VS Code window, find this line (it’s about 40 lines from the bottom of the file):scripts/clock.py
excluded_lineages = []and change it to:
scripts/clock.py
excluded_lineages = ["Omicron"]Rerun the analysis:
Terminal
pixi run clockWhen it finishes, see what has changed with:
Terminal
git statusand:
Terminal
git diff scripts/clock.pygit statuslistsresults/clock.pngandscripts/clock.pyas changed, but notresults/tree.treefile. IQ-TREE ran again, but because we used the same data, software, and seed, it produced exactly the same tree file, so git sees no change.Look at the new plot, then add the changes to the staging area, commit, and push:
Terminal
git add scripts/clock.py results/clock.png git commit -m "Leaving Omicron out of the clock" git push
Go back in time
Git keeps every version of every file you’ve committed, so you can always look at (or go back to) an earlier version.
List your commits, one per line:
Terminal
git log --onelineHEADis git’s name for your latest commit, andHEAD~1means “one commit beforeHEAD.”To bring back the plot from the commit before your latest one, use the
git restorecommand:Terminal
git restore --source=HEAD~1 results/clock.pngand ask git for the status:
Terminal
git statusLook at the
results/clock.pngplot in VS Code (it should include Omicron again).Put the latest version back:
Terminal
git restore results/clock.pngand ask git for the status:
Terminal
git statusNow, when you look at
results/clock.pngin VS Code, it should be the version that excludes Omicron.git restore <file>, without--source, throws away any changes to the file since your last commit. This is also how you undo a mistake: if you break a file, you can always get back the last version you committed.
Why the seed matters
IQ-TREE uses random numbers while it searches for the best tree. The seed determines which random numbers it gets: the same seed gives the same random numbers, and so the same results. (If you don’t give IQ-TREE a seed, it picks one based on the current time.) Let’s see what happens with a different seed.
In
pixi.toml, in thetreetask, change-seed 1to-seed 2. Then, run the analysis again:Terminal
pixi run clockCompare the results to what it printed before (the rate, the date of the ancestor, and \(R^2\)).
Use git to see what changed:
Terminal
git statusand:
Terminal
git diff --color-words='[^,():]+' results/tree.treefileThe
--color-wordsoption shows the changes word by word (here, each name and branch length is a “word”), instead of line by line. The old values are in red and the new values are in green.Undo the change to the seed, and the results it produced:
Terminal
git restore pixi.toml results/And confirm everything is restored to match your last commit:
Terminal
git status
Questions
Question 9: With Omicron left out, what rate (substitutions per genome per year) and date of the ancestor did the clock estimate? (They should be close to what you found in Capstone Project 2.)
Question 10: When you changed the seed to 2, what changed in results/tree.treefile? Did the clock’s printed results change? Do you think the tree from seed 2 is a different scientific result than the tree from seed 1? Why is it still important to record the seed?
Part 5: Prove it’s reproducible
So far, everything has been on one computer: your codespace. The real test of reproducibility is whether the project works on a new computer. So, let’s delete your codespace, make a brand new one, and rebuild everything from your GitHub repository.
First, make sure all of your work is on GitHub.
git statusshould saynothing to commit, working tree clean, andYour branch is up to date with 'origin/main':Terminal
git statusIf not, commit and push before you go on! Anything that isn’t pushed will be lost when you delete the codespace.
Go to https://github.com/codespaces, click the “…” next to your codespace, and choose “Delete.”
Go to your repository on GitHub, and create a new codespace (“Code” → “Codespaces” → “Create codespace on main”). While it starts up, it will automatically run
pixi install, which rebuilds your environment frompixi.tomlandpixi.lock.In the new codespace, check your environment:
Terminal
pixi listRerun the whole analysis:
Terminal
pixi run clockAnd check to see if anything changed:
Terminal
git status
If git status says nothing to commit, working tree clean, then you just reproduced your entire analysis, on a new computer, from scratch, and got exactly the same tree and the same plot, down to the last byte.
IQ-TREE sometimes uses slightly different (but equally correct) arithmetic on different kinds of processors, so on a different computer the branch lengths in results/tree.treefile might differ in their last digits. If git says that the tree file changed, look at the changes with git diff --color-words='[^,():]+' results/tree.treefile to see how big they are, and then use git restore results/ to go back to your committed results.
Questions
Question 11: After rerunning the analysis in your new codespace, what did git status say? What does that tell you about your results?
Question 12: What version of IQ-TREE is in your new environment? Is it the same as in Part 2 (Question 5)? Which file made sure of that?
Question 13: In your own words, describe one way that git, and one way that pixi, would help you (or someone else) if you came back to this project a year from now.
Optional: Reproduce a classmate’s analysis
Anyone can rebuild and rerun a public project. Ask a classmate for their repository’s URL, and try it in your codespace (replace their-username below with their GitHub username):
Terminal
cd /tmp
git clone https://github.com/their-username/comp-bio-project
cd comp-bio-project
pixi run clock
git status
cd /workspaces/comp-bio-projectgit clone downloads a copy of a repository, including its whole history. Did you get exactly their results?
Quick reference
| Command | What it does |
|---|---|
git status |
Show which files have changed since your last commit |
git add <file> |
Stage a file’s changes for your next commit |
git commit -m "message" |
Save a snapshot of the staged changes, with a message |
git push |
Send your commits to GitHub |
git log --oneline |
List your commits, newest first |
git diff <file> |
Show what changed in a file since your last commit |
git restore <file> |
Throw away a file’s changes since your last commit |
git restore --source=HEAD~1 <file> |
Bring back a file as it was one commit ago |
git clone <URL> |
Download a copy of a repository |
pixi init |
Start a pixi project (creates pixi.toml) |
pixi workspace channel add <channel> |
Add a channel (like bioconda) for pixi to search for packages |
pixi add <package> |
Add a package to the environment (updates pixi.toml and pixi.lock) |
pixi list |
List the packages in the environment, and their versions |
pixi run <command> |
Run a command inside the environment |
pixi task add <name> "<command>" |
Save a command as a task in pixi.toml |
pixi task list |
List the tasks in pixi.toml |
pixi run <task> |
Run a task |
pixi install |
Rebuild the environment from pixi.toml and pixi.lock |