Lecture 4: Version Control with Git
08 September 2026
Adapted from Grant McDermott’s lecture notes on Git and GitHub.
Anyone who has finished a paper has a folder that looks like this:
analysis.R
analysis_final.R
analysis_final_v2.R
analysis_final_v2_USE_THIS.R
analysis_final_v2_USE_THIS_really.R
Which file is current? What changed between v2 and USE_THIS? If a coauthor edits the wrong copy, can you get the old one back?
A VCS takes snapshots of a folder. Each snapshot records the whole tree, who made it, when, and why (the commit message).
That lets you answer questions like:
You need this even on a solo project. With coauthors it is close to non-negotiable.
Working alone
Working with others
Git is a distributed version control system:
git log and git blame tell you what changed, when, and whyThink of Git as Dropbox plus Word’s “Track changes”, rebuilt for code, papers, and data analysis.
For a researcher’s comparison, see Michael Stepner’s Git vs. Dropbox.
GitHub is a hosting platform on top of Git:
origin)Just as we do not need an IDE to run R, we do not need GitHub to use Git. GitHub is the shared copy that makes collaboration and backup easy.
Scientists adopted these tools because they match how research is supposed to work:
See Perkel (2016), Nature: “Democratic databases: science on GitHub”.
This lecture is terminal-first. The same four commands work in a local terminal, on a server, in CI, and inside AI coding tools.
GUIs are optional wrappers around those commands. These all call Git for you. Use one later if you like; learn the shell commands first.
Desktop apps
Inside an editor
Clicking “Stage” in any of these is git add. If you only click, you cannot debug a failed git pull on a remote server.
Before we start:
☑ A GitHub account
☑ Git itself (git --version should print 2.23 or newer)
☑ A Bash-compatible terminal
Windows: Git for Windows or WSL both work. If git switch is missing, update Git.
Jenny Bryan’s Happy Git with R is still the best install walkthrough for R users, including editor Git panes.
Tell Git who you are. Do this once per machine:
Use the email attached to your GitHub account, or GitHub’s noreply address, if you do not want your real address in public commits.
GitHub no longer accepts account passwords from git. Pick one:
gh and run gh auth loginAfter that, git clone, git pull, and git push look the same. HTTPS is the least setup for a first repo; SSH is nicer long term.
Git is not “the files in this folder”. It is four places, and commands move snapshots between them.
origin is just the conventional name for “the GitHub copy”.
git add..git/ directory): the database of commits. This is Git.origin): another complete copy, usually on GitHub.git status is how you ask “where is my work right now?”
Start on GitHub so origin already exists and every laptop is downstream of it.
gdp-nowcast)Creating the repo on GitHub first is the whole trick. Local clones then have somewhere to git pull from and git push to.
$ git clone https://github.com/ada/gdp-nowcast.git
Cloning into 'gdp-nowcast'...
remote: Enumerating objects: 3, done.
Receiving objects: 100% (3/3), done.
$ cd gdp-nowcast
$ ls -a
. .. .git README.md
.git/ is the local repository. Everything else is the working directory. Do not edit files inside .git/.
Once the clone exists, almost all daily work is four operations:
git add) — choose which changes belong in the next snapshotgit commit) — record that snapshot in the local historygit pull) — bring in new commits from GitHubgit push) — send your new commits to GitHubImportant
Always pull before you push, even on a solo project. Make it a habit now and you will avoid most “rejected, fetch first” errors later.
git statusOpen README.md in any editor, add a line, and save. Git notices:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
Read the hints. git status is the command you run when you are lost.
git diffLines beginning with + were added; lines with - were removed. This is the unstaged patch: working directory versus staging area.
git add$ git add README.md
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md
The file has moved from “not staged” to “to be committed”. The working-directory copy and the staging area now match.
git add| Command | What it stages |
|---|---|
git add FILE |
that file (or folder) |
git add -A |
all changes in the repo: new, modified, deleted |
git add . |
all changes under the current directory |
git add -u |
modifications and deletions of tracked files only |
From the project root, git add -A is the daily default. git add . is not “new files only”.
git commit$ git commit -m "Greet the reader in the README"
[main 35bef63] Greet the reader in the README
1 file changed, 2 insertions(+)
The staging area is now empty. The snapshot lives in the local repository, and HEAD (where you are) points at 35bef63.
Tip
Write a message that will make sense in six months: what changed and why, not “update” or “fix”.
git log$ git log --oneline --decorate
35bef63 (HEAD -> main) Greet the reader in the README
929252c (origin/main) Initial commit: add README
$ git pull
Already up to date.
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 312 bytes, done.
To https://github.com/ada/gdp-nowcast.git
929252c..35bef63 main -> main
git pull is git fetch (download new commits) plus git merge (integrate them). Here GitHub had nothing new, so push is a fast-forward.
Repeat the first two often. Pull and push whenever you want the GitHub copy, or a coauthor’s laptop, to see the work.
Creating the repo on GitHub first means GitHub stays the hub of the network: every laptop is a clone, none is special.
Older recipes use git reset HEAD FILE and git checkout -- FILE. Prefer git restore. It cannot detach HEAD by accident.
Leave git reset --hard and git push --force alone until you can explain what they delete. When something is on fire, ohshitgit.com is more useful than improvising.
Ada and Bob both clone main, both edit the same lines of README.md, and both commit. Whoever pushes second is not a fast-forward: the branches have diverged.
$ git pull
From https://github.com/ada/gdp-nowcast
35bef63..e5f151c main -> origin/main
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
Git will not guess. You fix the file, then commit the resolution.
git status during a conflict$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
git merge --abort walks away and leaves your branch as it was before git pull.
Open the file in any text editor. Git has written both versions in place:
# gdp-nowcast
Nowcasting US GDP growth from weekly indicators.
<<<<<<< HEAD
Hello from Ada!
=======
Hello from Bob!
>>>>>>> e5f151c
<<<<<<< HEAD … ======= is your commit======= … >>>>>>> e5f151c is their commit<<<<<<<, =======, and >>>>>>> linegit add README.mdgit commit (Git fills in a “Merge branch …” message)git pushThe person who fixes the conflict decides what the file says. The losing lines are still in the other commit if anyone needs them.
VS Code, Positron, and similar editors offer “Accept Current / Incoming / Both”. That is the same edit. You can also copy from git show HEAD:README.md and git show origin/main:README.md.
Git sometimes reports a diff on a line you did not touch. Collaborators on mixed OS machines are the usual cause:
A branch is an independent line of commits. Use one to:
maingit branch -d)Merge back into main only when you (and coauthors) are happy. That is how you avoid “final_final_v2” in the Git era.
main kept moving while robustness was in flight. The merge commit joins the two histories.
Older material uses git checkout -b robustness and git checkout main. git switch is the dedicated command for branches (Git 2.23+).
If Git can fast-forward, main simply moves. If both sides changed, you get the same conflict machinery as git pull.
A pull request (PR) is a proposed merge, with discussion attached.
git push -u origin robustnessmainPRs are useful on solo projects too: they are a structured review of your own work before it lands on main.
A clone points at the original GitHub repo. A fork is a copy under your GitHub account, which you then clone.
This is how outside contributors send patches to projects they cannot push to.
GitHub renders README.md as the landing page of the repo. For a research project it should say:
Markdown in a README is ordinary Markdown. Subfolders can have their own README for extra detail.
.gitignoreA .gitignore file lists paths Git should not track:
.DS_Store, .Rhistory, *_cache/)Put .gitignore in the repo root and commit it. Untracked files that already match the rules will disappear from git status.
.gitignore syntax# a single file
secret.csv
# a whole folder
data/raw/**
# a pattern
*.csv
test*
# exception: do not ignore this one
!data/raw/README.md
.gitignoreA short, research-flavoured starter. Commit this file at the repo root.
.Rproj.user
.Rhistory
.RData
*_cache/
.DS_Store
*.aux
*.log
*.bbl
.gitignore does not untrack a file that is already committed. Stop tracking it, keep it on disk, then ignore it:
If the secret was ever pushed, rotate it. History still contains the old blob.
Issues are the project’s inbox:
Close an issue from a commit message with Fixes #12 when you push to the default branch.
git clone URL && cd the-repogit add -Agit commit -m "Helpful message"git pullgit pushRepeat 3–7 often, especially 3 and 4.
When should I commit?
Early and often. Every coherent change is a good commit. Push whatever coauthors should see.
Do I need branches on a solo paper?
You do not need them. They are still the safe way to try a robustness check, and a self-PR is a cheap review.
Clone or fork?
Clone when you can push to the repo (your paper, your lab). Fork when you want to contribute to someone else’s project.
ohshitgit.com covers the common panics (“I committed to the wrong branch”, “I need to undo a commit”).
When things go horribly wrong: copy any files you still need, delete the local clone, and clone a fresh copy from GitHub. That is a feature of a distributed VCS: somewhere else there is still a good copy.
See also Happy Git with R’s burn it down section.
Randall Munroe, XKCD 1597, CC BY-NC 2.5.
The Missing Semester notes are aimed at CS students and are still the clearest short treatment of Git’s data model. The rest of that course is worth skimming too.