Git & GitHub for R

Marc Los Huertos

EA030 · Compiled 2026-04-14

Why Git? Concepts Setup Basic Workflow RStudio Pane History Branches Remotes Complications Shared Repos .gitignore Reference

What this module covers. Git records every change to a project, lets you travel back to any earlier state, and lets multiple people work on the same files without overwriting each other. GitHub hosts Git repositories and adds pull requests, issue tracking, and code review. This module builds from the first git init through branching, merge conflicts, and the conventions that make shared EA030 repositories work without chaos.


Why Version Control?

Without Git, project histories look like this:

# A folder without version control
analysis.R
analysis_v2.R
analysis_FINAL.R
analysis_FINAL_fixed.R
analysis_FINAL_fixed_marccomments.R
analysis_use_this_one.R

With Git, you have one file and a complete labelled history of every change — who made it, when, and why. You can revert to any previous state with one command, branch to try a new approach without touching the working version, and trace exactly which code produced which result.

📌 Git is not just for software engineers

Every reproducible environmental analysis benefits from version control. When a reviewer asks “what changed between your submitted draft and the revision?”, Git answers precisely. When you re-run an old analysis on new data, Git tells you whether the code itself changed.


Core Concepts — The Commit Path

Understanding Git means understanding four locations your files inhabit and the commands that move them between locations.

📝
Working
Directory
Files on disk
that you edit

git add → git restore

🗂️
Staging
Area
Preview of
next commit

git commit → git reset HEAD~

💾
Local
Repository
History on
your machine

git push → git pull

☁️
Remote
(GitHub)
Shared history
online
Location What it contains Analogy
Working directory Files you see and edit Your desk — messy, in progress
Staging area Curated preview of your next commit A tray of items ready to photograph
Local repository Permanent record of all commits A filing cabinet in your office
Remote (GitHub) The same history, hosted online A filing cabinet in a shared office

The staging area is the most misunderstood part of Git. It lets you group related changes into one logical commit even when many files have changed — you choose exactly what goes into each commit.

⚙️ A commit is a snapshot, not a diff

Git stores each commit as a complete snapshot of all tracked files — not just the changed lines. The diff you see with git diff is computed on demand by comparing two snapshots. Each commit has a unique SHA-1 hash (e.g., a3f9c2d) that permanently identifies it and can be referenced in advanced commands.


1
One-Time Setup
Git identity · GitHub account · Personal Access Token

Configure your identity

Git embeds your name and email in every commit. Set these once in the Terminal tab in RStudio:

$ git config –global user.name “Jamie Smith”
$ git config –global user.email “”
$ git config –global init.defaultBranch main

# Verify
$ git config –list

Create a GitHub account

Go to https://github.com and sign up with your Pomona email. Verify student status at education.github.com for free private repositories.

Authenticate with a Personal Access Token (PAT)

GitHub no longer accepts passwords over HTTPS. Generate a PAT:

  1. GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
  2. Generate new token (classic)
  3. Set expiration: 90 days (or the length of the semester)
  4. Check scopes: repo, workflow, user
  5. Generate token — copy it immediately (you cannot see it again)

Store the PAT in R:

install.packages("gitcreds")   # if needed — already on Pomona server
gitcreds::gitcreds_set()       # paste your PAT when prompted

The usethis package wraps common setup steps:

library(usethis)
create_github_token()    # opens browser with correct scopes pre-selected
gitcreds::gitcreds_set() # store the PAT after copying from GitHub
git_sitrep()             # "situation report" — verifies everything is connected

⚠️ Never commit a PAT or any secret inside a script or .Rmd file

If you commit a token — even briefly — GitHub revokes it automatically. Secrets belong in environment variables or credential stores, never in code.


2
Basic Workflow — Init, Stage, Commit, Push
The daily loop you will run hundreds of times

Start: two routes to a Git-enabled project

Route A — New project from GitHub (recommended for shared work)

  1. Create a repository on GitHub: github.com → New repository
  2. Copy the HTTPS URL
  3. In RStudio: File → New Project → Version Control → Git → paste URL

Route B — Existing RStudio Project → GitHub

library(usethis)
use_git()      # initialises git, makes first commit, opens Git pane
use_github()   # creates the GitHub repo and pushes

The daily commit loop

1
<div class="wf-title">Check status — what has changed?</div>
<div class="wf-code">git status</div>
<div class="wf-note">Red = not staged. Green = staged and ready to commit.</div>
2
<div class="wf-title">Review the diff — what exactly changed?</div>
<div class="wf-code">git diff scripts/01_clean.R</div>
<div class="wf-note">Lines with <code>+</code> were added; lines with <code>-</code> were removed.</div>
3
<div class="wf-title">Stage the changes you want in this commit</div>
<div class="wf-code">git add scripts/01_clean.R</div>
<div class="wf-note">Stage everything reviewed: <code>git add .</code></div>
4
<div class="wf-title">Commit with a meaningful message</div>
<div class="wf-code">git commit -m "fix: drop QC-flagged DO records before summarising"</div>
<div class="wf-note">A permanent record — write it for your future self.</div>
5
<div class="wf-title">Push to GitHub</div>
<div class="wf-code">git push origin main</div>
<div class="wf-note">Or just <code>git push</code> once upstream is set.</div>

Writing good commit messages

Complete the sentence: “If applied, this commit will…”

# Bad — tells you nothing
git commit -m "changes"
git commit -m "fix stuff"

# Good — one sentence, explains what and why
git commit -m "add DO percent-saturation using Benson and Krause equation"
git commit -m "fix: filter() drops NAs before group_by to prevent silent errors"
git commit -m "refactor: extract site-cleaning logic into its own function"
git commit -m "docs: add README section on data sources and field protocol"

✅ Conventional Commits prefixes

feat: new feature or analysis step  ·  fix: bug or error correction  ·  docs: documentation only  ·  refactor: restructuring without behaviour change  ·  data: new or updated data file


The RStudio Git Pane

The Git pane (top-right, next to Environment) provides a graphical interface for the most common operations.

RStudio Git pane actions and their terminal equivalents.
Action RStudio Git pane Terminal equivalent
See changed files Files listed automatically with status icons git status
Stage a file Check the box in the Staged column git add <file>
Unstage a file Uncheck the box git restore --staged <file>
View diff Click the file name git diff <file>
Commit Commit button → write message → Commit git commit -m 'message'
Push Green up-arrow Push button git push
Pull Blue down-arrow Pull button git pull
View history Clock icon (History) git log --oneline
Switch branch Branch dropdown top-right of pane git checkout <branch>
New branch Branch dropdown → New Branch git checkout -b <branch>

📌 Use the Terminal for anything not in the pane

The Git pane covers about 80% of daily operations. For branching, rebasing, reset, stash, and conflict resolution you need the Terminal tab (Alt+Shift+R or Tools → Terminal → New Terminal). It opens at the project root automatically.


Reading History and Diffs

# Compact one-line history
$ git log –oneline
a3f9c2d fix: drop NAs before group_by
8b21e4a add DO percent-saturation calculation
f1dc39a initial project structure and raw data

# Graph showing all branches
$ git log –oneline –graph –all –decorate

# Full detail for one commit
$ git show a3f9c2d

Compare states

# Changes between two commits
$ git diff f1dc39a a3f9c2d

# One file between HEAD and 3 commits ago
$ git diff HEAD~3 HEAD – scripts/02_visualise.R

# What is staged but not yet committed?
$ git diff –staged

Recover an old version of a single file

# Restore a file to how it looked in commit f1dc39a
$ git restore –source=f1dc39a scripts/01_clean.R
# File is now restored — stage and commit to keep it


Branching

A branch is a parallel line of development. Use branches to work on analyses without touching the working main branch. Merge back when done.

C1 C2 merge C6 C3 C4 C5 main feature/new-plot branch merge

# Create and switch to a new branch
$ git checkout -b feature/turbidity-analysis
Switched to a new branch ‘feature/turbidity-analysis’

# List branches — asterisk marks current
$ git branch
* feature/turbidity-analysis
main

# After making commits, merge back into main
$ git checkout main
$ git pull
$ git merge feature/turbidity-analysis

# Delete the merged branch
$ git branch -d feature/turbidity-analysis

Pull Requests on GitHub

For shared repositories, merge through a Pull Request (PR):

  1. Push your branch: git push origin feature/turbidity-analysis
  2. GitHub → Compare & pull request banner appears automatically
  3. Write a description: what does this branch do, what should reviewers check?
  4. Assign a reviewer; link issues with Closes #12
  5. Reviewer comments inline; push new commits to respond
  6. Once approved: Merge pull request → Confirm → Delete branch

✅ Branch naming conventions

feature/add-turbidity-model  ·  fix/do-na-filter  ·  docs/update-readme  ·  analysis/seasonal-comparison

Use consistent prefixes. Never work directly on main in a shared repository.


Remote Repositories

git push
Upload local commits to GitHub. Fails if the remote has commits you have not pulled.
git pull
Download remote commits AND merge into current branch. Equivalent to fetch + merge.
git fetch
Download remote commits but do NOT merge. Inspect first with git log origin/main.
git remote -v
List remote connections. origin is the default name for the GitHub remote.
git clone <url>
Download an entire repository from GitHub. Sets origin automatically.
git push -u origin main
First push: sets upstream so future git push needs no arguments.

Complications

Merge conflicts — anatomy and resolution

A merge conflict occurs when two branches edited the same lines of the same file. Git marks the conflict and asks you to resolve it manually.

scripts/02_visualise.R — after git merge feature/seasonal
<<<<<<< HEAD  (your current branch — main)
  scale_colour_manual(values = c("#4a7c5e", "#c8832a"))
=======
  scale_colour_brewer(palette = "Dark2")
>>>>>>> feature/seasonal  (the branch being merged in)

Resolution steps:

  1. Open the conflicted file in the Source pane
  2. Find every set of conflict markers (<<<<<<<, =======, >>>>>>>)
  3. Edit to keep what is correct — delete all three marker lines
  4. Stage the resolved file: git add scripts/02_visualise.R
  5. Complete the merge: git commit — Git pre-fills the message

⚙️ Reading the conflict markers

&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD — start of your version (the branch you are merging INTO)
======= — divider between the two versions
&gt;&gt;&gt;&gt;&gt;&gt;&gt; branch-name — end of their version (the branch being merged in)

You can keep yours, keep theirs, keep both, or write something new entirely. No markers may remain in the file when you stage it.

✅ Use RStudio’s three-panel merge tool

When a conflict is detected, the Git pane shows an orange U (unresolved) next to the file. Click it and choose Open in merge tool for a side-by-side view with Accept Ours / Accept Theirs buttons.

Detached HEAD

You are in ‘detached HEAD’ state.
HEAD is now at f1dc39a initial project structure

This happens when you git checkout a commit hash directly. Commits made here are orphaned and will be garbage-collected.

# Option 1 — go back to main, discarding detached commits
$ git checkout main

# Option 2 — save your work by creating a branch first
$ git checkout -b rescue/my-exploration
$ git checkout main
$ git merge rescue/my-exploration

Accidental commit to main (not yet pushed)

# Save the commit on a new branch
$ git branch feature/oops-commit

# Reset main back one commit, keeping changes in working directory
$ git reset HEAD~1

# Continue on the feature branch
$ git checkout feature/oops-commit

⚠️ Never use git push –force on a shared branch

git push --force rewrites remote history. Everyone who has pulled the branch will have a diverged history that is very difficult to reconcile. On main or any shared branch, use git revert instead — it adds a new undo commit without touching history.

Undoing things — choosing the right command

Choosing the right undo command. When in doubt, prefer non-destructive options.
Situation Command Safety
Changed a file — not staged, not committed git restore <file> Destructive — file changes lost
Staged a file — not committed git restore --staged <file> Safe — file stays modified, just unstaged
Committed locally — not pushed (discard changes) git reset --hard HEAD~1 Destructive — changes lost forever
Committed locally — not pushed (keep changes in working dir) git reset HEAD~1 Safe — changes returned to working directory
Committed and pushed to shared branch Do not rewrite. Use git revert <hash> Safe — revert adds a new undo commit
Want to undo one commit without rewriting history git revert <hash> Safe — new commit, history intact

Stashing — pause work to switch context

# Mid-analysis and need to urgently fix something on main
$ git stash push -m “half-finished turbidity model”
Saved working directory and index state: half-finished turbidity model

$ git checkout main # working directory is now clean
# … fix the bug, commit, push …
$ git checkout feature/turbidity-analysis
$ git stash pop # restore stashed work


Shared Repository Best Practices

When an EA030 team shares one GitHub repository the biggest risks are accidental overwrites, tangled histories, and large binary files. These conventions prevent most problems.

Protect the main branch on GitHub

Settings → Branches → Branch protection rules → Add rule

Always pull before you push

$ git checkout main
$ git pull
$ git checkout your-feature-branch
$ git merge main # bring main updates into your branch before continuing

Long-lived branches that diverge for two weeks while main receives many commits produce large, painful merges. Keep feature branches to 1–3 days and one focused purpose.

Commit atomically

# Bad: two unrelated changes in one commit
git commit -m "fix NA filter and change colour palette"

# Good: two separate, focused commits
git commit -m "fix: drop NA rows before group_by in summarise step"
git commit -m "style: switch from manual colours to scale_colour_brewer"

Pull request review checklist

Pull request review checklist for EA030 shared repositories.
Category Check
Reproducibility Script runs end-to-end from a clean session without errors
Reproducibility All file paths are relative — no setwd(), no absolute paths
Functionality Outputs match what the PR description claims
Functionality All library() calls are at the top of the script
Style Column and object names are snake_case and self-documenting
Style Non-obvious lines have an explanatory comment
Data Raw data files in data/raw/ are unmodified
Data No large binary files (.tif, .xlsx) included in the commit

The .gitignore File for R Projects

.gitignore tells Git which files to never track. Place it in the project root.

# .gitignore for an EA030 R project

# R-specific
.Rhistory
.RData
.Rproj.user/

# Rendered outputs — reproducible from source
output/figures/
output/reports/
*.html
*.pdf

# Large raw data files — share via server or Google Drive
data/raw/*.csv
data/raw/*.xlsx
data/raw/*.tif
data/raw/*.shp

# OS clutter
.DS_Store
Thumbs.db

# Secrets
.Renviron
secrets.R

Generate a standard R .gitignore in one line:

usethis::use_gitignore()

⚠️ Already committed a file by mistake?

Adding a file to .gitignore does NOT untrack it if already committed. To stop tracking without deleting from disk:

git rm --cached data/raw/huge_file.csv
git commit -m "stop tracking large raw data file"

Quick-Reference Card

git init
Initialise a new repository in the current directory
git clone <url>
Clone a remote repository locally
git status
Show working directory and staging area state
git add <file>
Stage a file; git add . stages everything
git commit -m
Record staged changes with a message
git push
Upload local commits to GitHub
git pull
Fetch and merge remote commits
git fetch
Download remote commits without merging
git log –oneline
Compact commit history
git diff
Unstaged changes; –staged for staged
git checkout -b
Create and switch to a new branch
git merge <branch>
Merge a branch into the current branch
git stash
Shelve uncommitted changes temporarily
git revert <hash>
Safely undo a pushed commit with a new commit
git restore <file>
Discard working directory changes to a file
git rm –cached
Stop tracking a file without deleting it

🔑 Git & GitHub — key takeaways


What’s next?