Part 3 — Git and GitHub
Keeping your work, and getting it back
Git answers a question you will have at some point: what did this look like before I broke it?
Everything up to the GitHub section runs on a real repository each time this site is built. Git works entirely on your own machine — the network is only involved when you deliberately talk to a server.
The problem
You have analysis.py. You change it. It stops working, and you cannot remember what you changed.
The usual response is analysis_v2.py, then analysis_final.py, then analysis_final_REALLY.py — and a folder where nobody can tell which file produced which result.
Git replaces all of that with one directory that remembers every version of everything in it, plus who changed what, when, and why.
Setting up
Git records a name and an email with every change, so tell it who you are once:
git config --global user.name "Your Name"
git config --global user.email "you@example.org"--global means “for every repository on this machine”, so this is a one-time step.
git config --global init.defaultBranch mainThat sets what the first branch is called. main is the current convention.
A repository
A repository (dt. Repositorium, usually just repo) is a directory that Git is watching. Make one:
mkdir myanalysis
cd myanalysis
git initInitialized empty Git repository in /home/user1/myanalysis/.git/
That created a hidden .git directory. That directory is the repository — the entire history lives there. Delete it and you have ordinary files again.
Now make something worth tracking:
echo "print('hello')" > analysis.py
git statusGit reports analysis.py as untracked: it can see the file but is not watching it yet.
The three steps
Saving work in Git is deliberately three steps, not one.
git add analysis.py
git commit -m "Add the analysis script"[main (root-commit) 9c1f2ab] Add the analysis script
1 file changed, 1 insertion(+)
| step | what it means |
|---|---|
| edit | change files as usual |
git add |
choose what goes into the next snapshot (“staging”) |
git commit |
take the snapshot, with a message saying why |
The middle step looks like bureaucracy and is not: it lets you commit some of your changes and leave the rest. Two unrelated fixes become two commits with two clear messages, instead of one commit called “stuff”.
That person is you, in six months, trying to find where something broke.
“Fix bug” is worthless. “Fix the off-by-one that dropped the last row” is worth the extra ten seconds. Say why, not what — the diff already shows what.
Seeing what happened
echo "print('goodbye')" >> analysis.py
git status --short M analysis.py
M for modified. To see the actual change:
git diff@@ -1 +1,2 @@
print('hello')
+print('goodbye')
Lines with + were added, - removed. Commit it:
git add analysis.py
git commit -m "Say goodbye as well as hello"
git log --oneline4d5e6f7 Say goodbye as well as hello
9c1f2ab Add the analysis script
That list is the point of the whole exercise.
Git is built for text — code, scripts, notes, configuration. It is bad at large binary files: it keeps every version forever, so a 2 GB dataset committed once is in the repository permanently, even after you delete it.
Keep data outside the repository, or list it in a .gitignore:
printf "results/\n*.csv\n" > .gitignore
git add .gitignore
git commit -m "Ignore results and data files"Now Git stops offering to track them. What belongs in a repository is everything needed to reproduce the results — including pixi.toml and pixi.lock from Part 2 — not the results themselves.
Going back
The reason for all of it. Discard changes you have not committed:
echo "print('a terrible mistake')" >> analysis.py
git restore analysis.py
git diffgit diff prints nothing: the file is back to its last committed state, and the mistake is gone.
Look at an old version without changing anything:
git show HEAD~1:analysis.pyprint('hello')
HEAD is where you are now; HEAD~1 is one commit before it. Nothing was modified — Git just read an old version out of its history.
Branches
A branch is a separate line of work. Try something risky without touching what already works:
git switch -c experiment
echo "print('a new idea')" >> analysis.py
git add analysis.py
git commit -m "Try a new idea"-c creates the branch and moves you onto it. To see what branches exist, and which one you are on:
git branch experiment
* main
The * marks where you are. git status says the same thing in its first line, which is why it is worth reading before anything else.
Switch back:
git switch main
cat analysis.pyprint('hello')
print('goodbye')
The new idea is not here — it is on experiment, intact. Keep it:
git merge experiment
cat analysis.pyprint('hello')
print('goodbye')
print('a new idea')
Or abandon it, by simply never merging.
git log --oneline --graph --allIf the same lines changed on both branches, Git cannot decide and says so. It marks the disagreement in the file with <<<<<<< and >>>>>>>, and waits.
You edit the file so it says what you want, delete the markers, then git add and git commit. That is all a conflict is: Git declining to guess. It is not a sign anything is broken.
GitHub
Everything so far was local. GitHub is a website that hosts Git repositories, so you can back them up, work from more than one machine, and let other people see your work. It is not Git — it is one of several hosts, alongside GitLab and others.
If your practical works out of a shared repository, at least one person on the team needs a GitHub account for that repository to exist at all. In practice everyone ends up with one — each of you will push your own commits, and a commit’s author is tied to the account that made it.
It costs nothing and takes a minute: github.com/join. Do this before you need it, not in the middle of a session. See also “Make a GitHub profile, and treat it as one” below — this is not only a practical requirement.
Figure 1 places everything on this page on one picture, and adds the piece GitHub is for: a remote repository, a copy of the same history sitting on a server rather than on your machine.
git push and git pull are the two new commands — send your local history to the remote, or fetch what changed there. git clone is git pull’s first-time counterpart: instead of updating an existing local repository, it creates one from scratch by copying a remote’s entire history.
Unlike the rest of this page, these need an account, credentials and a real server, so they are written out rather than run.
Make an empty repository on GitHub through the website, then connect your local one to it:
git remote add origin https://github.com/yourname/myanalysis.git
git push -u origin mainorigin is the conventional name for “the server this came from”. After the first push, sending later commits is one word:
git pushAnd to collect changes made elsewhere:
git pullStarting from someone else’s repository instead:
git clone https://github.com/someone/theirproject.git
cd theirprojectclone copies the entire history, not just the current files — so you get every version and can work offline immediately.
The everyday loop
git pull # get what others changed
# ... do some work ...
git add .
git commit -m "Describe what you did"
git push # send it backWhen it goes wrong
“Please tell me who you are.” You skipped the git config step above.
You committed something you should not have. If you have not pushed, git reset --soft HEAD~1 undoes the commit and keeps the changes. If you have pushed — especially a password or a key — treat it as public: the fix is to change the secret, not to rewrite history. Removing a commit from a public repository does not remove it from everyone who already has a copy.
git push is rejected. Someone else pushed first. git pull, resolve anything that conflicts, then push again.
You are lost. git status almost always tells you where you are and what it thinks you should do next. Read it before doing anything drastic.
Searching a Git error reliably turns up git reset --hard and git push --force. Both can destroy work permanently, and both are usually the wrong answer to a beginner’s problem.
git status and git log never destroy anything. Start there.
What you should be able to do now
Where to go next
Part 4 — Files you will meet covers the formats that turn up around all of this: Markdown for the README your repository should have, JSON for what tools hand back, and the tabular formats in between.