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?

TipMost of this page is executed

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 "[email protected]"

--global means “for every repository on this machine”, so this is a one-time step.

git config --global init.defaultBranch main

That 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 init
Initialized 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 status

Git 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”.

TipWrite the message for the person reading it later

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 --oneline
4d5e6f7 Say goodbye as well as hello
9c1f2ab Add the analysis script

That list is the point of the whole exercise.

ImportantDo not commit data

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 diff

git 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.py
print('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.py
print('hello')
print('goodbye')

The new idea is not here — it is on experiment, intact. Keep it:

git merge experiment
cat analysis.py
print('hello')
print('goodbye')
print('a new idea')

Or abandon it, by simply never merging.

git log --oneline --graph --all
NoteMerge conflicts

If 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.

ImportantYou will need an account

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.

Four boxes in a row: Workspace, Staging area, Local repository and Remote repository. Workspace connects to Staging area via git add, Staging area to Local repository via git commit, and Local repository to Remote repository via git push. A dashed arrow labelled git pull or git clone runs from Remote repository back to Workspace.
Figure 1: The four stages a change passes through, and the command that moves it from one to the next.

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.

NoteThe commands below are not executed

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 main

origin is the conventional name for “the server this came from”. After the first push, sending later commits is one word:

git push

And to collect changes made elsewhere:

git pull

Starting from someone else’s repository instead:

git clone https://github.com/someone/theirproject.git
cd theirproject

clone 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 back

Working in a shared repository

If several of you share one repository and all commit to main, you will hit this within the first hour:

 ! [rejected]        main -> main (fetch first)

Someone else pushed while you were working. Nothing is broken and nothing is lost — Git is refusing to overwrite their work with yours. git pull, sort out anything that conflicts, then push again.

That works, but it gets tiring, and it means every half-finished change lands straight on the branch everyone else is using. The usual answer is the one from the previous section: work on a branch, and merge it when it is ready.

Branch, push, pull request

git switch -c add-quality-filter

Do the work, commit it as usual, then push the branch rather than main:

git push -u origin add-quality-filter

-u links your local branch to the one on the server, so afterwards plain git push is enough.

Then open a pull request (PR) on GitHub. Despite the name, nothing is being pulled by you: a PR is a request that your branch be merged into main, with a place to discuss it first.

NoteThis part is done in the browser

After you push a branch, GitHub shows a Compare & pull request button on the repository’s front page. From there: check that the base branch is main and the compare branch is yours, give it a title and a sentence saying what it does, and create it.

Anyone on the team can then read the change, comment on specific lines, and click Merge pull request when it is agreed. Afterwards, back in your terminal:

git switch main
git pull

so your local main catches up with what was just merged.

WarningThe exact buttons move

GitHub redesigns its interface regularly, so the names and positions above are what to look for rather than a guaranteed click path. The underlying steps do not change: push a branch, open a request to merge it, agree, merge.

Why bother, for a small team

Three reasons that hold even for two people:

  • main always works. Half-finished work sits on a branch, so nobody else inherits it.
  • Somebody read it. A PR is the natural place for a second pair of eyes, which catches more than any tool.
  • It records the reasoning. The discussion stays attached to the change, so in six months the repository answers why, not just what.
TipSmall pull requests get reviewed; large ones get approved

A PR touching one thing gets read properly. A PR touching nineteen things gets a thumbs-up and no attention, which defeats the point. Merge more often rather than less.

TipMake a GitHub profile, and treat it as one

It costs nothing and it accumulates. A practical you did properly — a repository with a clear README, code that runs, and an environment file that lets someone reproduce it — is a genuine example of your work, and it will be the first thing in a portfolio you did not realise you were building.

That is a much better reason to write decent commit messages than being told to.

This is worth taking seriously, not just as a habit. A GitHub profile can become a genuine landing page — pinned repositories, a short bio, links to papers or projects, even its own page at username.github.io. One example worth looking at: github.com/jonas-fuchs, a working bioinformatician’s profile built exactly this way. For a job application in this field, a profile like that can say more than a CV — it is evidence you can actually do the work, not a claim that you can.

None of this needs to happen this semester. It needs to start this semester, because a profile with one good repository on it beats one with none, and the gap only closes by adding to it.

When 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.

WarningBe careful with advice from the internet

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.