0. The only mental model you need

  • SVN: one central timeline of revisions (r12345) on the server.
  • Git: commits form a DAG on your machine. A branch is just a movable name pointing at a commit.
  • A commit is a snapshot of the whole tree (not a per-file delta).
  • 3 places changes can live:
    1. Working tree — your files on disk
    2. Index / staging area — what the next commit will record
    3. Commit history — recorded snapshots
+-------------+   git add    +-------------+  git commit  +------------+
| working     | -----------> | index       | -----------> | commits    |
| tree        |              | (staging)   |              | (history)  |
+-------------+              +-------------+              +------------+
      ^                                                        |
      |                  git restore FILE                      |
      +<-------------------------------------------------------+
                         git restore --staged FILE
                         (unstages, keeps your edits)

Rule: when confused, run:

git status
git log --oneline --graph --decorate --all

1. The 20% of commands you will use 80% of the time

In the examples below, <path> means the actual file or directory name (e.g. src/foo.c), <branch> means a branch name (e.g. main), and <commit> means a commit hash (e.g. a1b2c3d).

Inspect:

git status                                  # your compass
git diff                                    # unstaged changes
git diff --staged                           # what will be committed
git log --oneline --graph --decorate --all  # commit graph
git blame FILE                              # who changed each line

Stage and commit:

git add <path>                              # stage changes
git commit -m "msg"                         # local snapshot

Branches:

git switch <branch>                         # switch to existing branch
git switch -c <new-branch>                  # create + switch
git branch                                  # list branches
git branch -d <branch>                      # delete a merged branch
git merge <branch>                          # integrate branch

Sync with remote:

git fetch                                   # download (safe, read-only)
git pull                                    # fetch + rebase
git push                                    # upload

Undo (safe defaults):

git restore <path>                          # discard unstaged edits
git restore --staged <path>                 # unstage (keeps edits)
git revert <commit>                         # undo via new commit (safe)

2. SVN → Git translation (roughly)

Checkout and update:

svn checkout URL             ->  git clone URL
svn update                   ->  git pull
                                 (safer: git fetch; inspect; then merge)

Status and diffs:

svn status                   ->  git status
svn diff                     ->  git diff

History:

svn log                      ->  git log
svn blame FILE               ->  git blame FILE

Branches:

svn copy trunk branches/x    ->  git switch -c x
svn switch x                 ->  git switch x

Merging:

svn merge                    ->  git merge  (or git rebase, see §6)

Undo:

svn revert FILE              ->  git restore FILE
svn merge -c -REV            ->  git revert <commit>

Key differences at a glance:

Concept SVN Git
Repository Central server database Local .git folder (full clone)
Commit Network upload Local snapshot
Publish (same as commit) git push (separate step)
Revision ID r1024 (sequential integer) a1b2c3d (SHA-1 hash)
Branch Directory copy — O(N) Pointer creation — O(1)

3. The biggest gotcha: git commit is local

  • In SVN, “commit” publishes to the shared server.
  • In Git, “commit” records locally. Sharing happens with git push.

Rule of thumb:

  • Commit often locally (small, logical steps).
  • Push when you want others to see your work.

4. Where did r12345 go?

SVN identifies commits by sequential integers: r12345. Git identifies commits by SHA-1 hashes: a1b2c3d4e5f6...

What you use instead:

git log --oneline             # shows: a1b2c3d Fix overflow in foo.c
git show a1b2c3d              # full details of a commit
git log -S "search_string"    # commits that added/removed this string
git log --grep="keyword"      # commits whose message matches
git blame FILE                # who last changed each line

If you need stable names for milestones:

git tag v2.1.0                # lightweight tag
git tag -a v2.1.0 -m "msg"   # annotated tag (recommended)

5. Branches are cheap (and you should use them)

Branch workflow:

git switch -c feature-x
# ... edit, add, commit ...
git switch main
git merge feature-x

Key idea: branches are names/pointers, not server directories. Creating a branch costs almost nothing. Deleting a branch only removes the label; the commits remain until garbage-collected.

Publishing a branch: new branches only exist on your machine until you push:

git push -u origin feature-x   # publish branch and set up tracking
git push                        # subsequent pushes just work

6. Merge vs rebase (simple policy)

On day 1, just use merge. You can learn rebase later when you need it.

  • merge: preserves the true branch structure (adds a merge commit).
  • rebase: replays your commits on top of a new base (creates new commit hashes).

Safe beginner policy:

  • Use merge when integrating shared work.
  • Rebase only your own unpublished branch to clean it up.
  • Never rebase commits that others already pulled.

Rebasing a branch onto the latest main:

git fetch
git rebase origin/main        # replay your work on latest main
git push

Note: pull.rebase true (see §13) makes git pull do this automatically. That is safe because it only replays your unpushed commits.

7. Resolving merge conflicts

When two branches modify the same lines, Git cannot merge automatically. It marks the conflicting sections in the file:

<<<<<<< HEAD
Line 1 (main version)
=======
Line 1 (branch version)
>>>>>>> topic
  • Everything between <<<<<<< and ======= is your current branch
  • Everything between ======= and >>>>>>> is the incoming branch

To resolve:

  1. Open the file in your editor
  2. Delete the marker lines (<<<<<<<, =======, >>>>>>>)
  3. Keep the text you want (or combine both)
  4. Stage and commit:
git add FILE
git commit -m "Resolve conflict in FILE"

Check the state at any time:

git status                    # shows "both modified" for conflicted files

Conflicts are less common than people fear. Git auto-merges successfully most of the time.

8. Remotes: fetch vs pull vs push

Your repo                        Remote (e.g. GitHub)
+-----------+     git push      +-----------+
| main      |------------------->| main      |
|           |                    |           |
| origin/   |<--- git fetch ----|           |
|   main    |                    +-----------+
+-----------+
  git pull = git fetch + git rebase (with pull.rebase true)
  • origin/main is your local record of where the remote’s main was at last fetch. It updates only on fetch (or pull).

Safe sync habit:

git fetch
git log --oneline --graph --decorate --all   # inspect
git merge origin/main                         # or: git pull
git push

9. Undo cookbook (triage)

A) “I edited a file, regret it” (not staged):

git restore <path>

B) “I staged the wrong thing”:

git restore --staged <path>

C) “I committed something bad, but it is already shared”:

git revert <commit>           # creates a new undo-commit (safe)

D) “I committed something bad, not shared yet” (power tool):

HEAD~1 means “one commit before where I am now.”

git reset --soft HEAD~1       # undo last commit, keep changes staged
git reset --mixed HEAD~1      # undo last commit, keep changes unstaged
git reset --hard HEAD~1       # undo last commit, DISCARD changes

WARNING: reset and rebase rewrite history. If others have pulled your commits, use git revert instead. Never force-push to a shared branch unless your team explicitly agrees to it.

E) “I need to switch branches but have uncommitted work”:

git stash                     # shelve your changes
git switch other-branch       # do your work there
git switch -                  # switch back
git stash pop                 # restore your changes

10. .gitignore (set up early)

Create a .gitignore in your repo root. Git will skip matching files.

Starter for C projects:

# Compiled objects and libraries
*.o
*.so
*.a
*.dylib

# Build directories
build/
_build/

# Editor swap/backup files
*~
*.swp
.*.swp

# OS metadata
.DS_Store
git add .gitignore
git commit -m "Add .gitignore"

Important: .gitignore only affects files that Git is not already tracking. If you have already committed .o files, adding *.o to .gitignore will not remove them. To stop tracking a file that was already committed:

git rm --cached file.o        # remove from Git's tracking, keep the file on disk
git commit -m "Stop tracking file.o"

11. Tip for LaTeX collaboration: semantic linefeeds

Git tracks changes by line. In LaTeX, one sentence per line makes diffs and merges much cleaner:

% Hard to review (Git sees 1 changed line for the whole paragraph)
Let $f$ be a function. It is continuous. We show that it is bounded.

% Easy to review (Git sees exactly which sentence changed)
Let $f$ be a function.
It is continuous.
We show that it is bounded.
  • Merge conflicts drop dramatically.
  • git blame shows who wrote each sentence.
  • PDF output is identical (LaTeX ignores single newlines).

12. Common SVN-to-Git pitfalls

  • Treating commit as publish — it is not.
  • Ignoring the index — staging exists and is useful.
  • Fear of branches — in Git, branches are the normal unit of work.
  • Rewriting shared history — avoid unless your team has a clear protocol.
  • Expecting revision numbers — Git uses commit hashes; use tags for named milestones.
  • Forgetting .gitignore — compiled objects pollute the repo fast.
  • Using git add . or git commit -a — these stage everything, including build artifacts, editor backups, and temporary files. Always git add specific files by name.

--global means “apply to all repos on this machine.” Without it, the setting applies only to the current repo.

git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global pull.rebase true          # pull replays your unpushed commits on top (linear history)
git config --global fetch.prune true          # auto-remove local refs to deleted remote branches
git config --global init.defaultBranch main   # new repos start with branch "main"

Optional but handy alias:

git config --global alias.lg "log --oneline --graph --decorate --all"
# then use:  git lg

14. Common error messages

“fatal: not a git repository”

  • You are not inside a git repo. Run git init or cd to the right directory.

“error: Your local changes would be overwritten”

  • You have uncommitted changes that would conflict. Commit them first, or temporarily shelve them with git stash (retrieve later with git stash pop).

“CONFLICT (content): Merge conflict in FILE”

  • Both branches modified the same lines. Edit the file, remove markers, git add, git commit. See §7.

”! [rejected] main -> main (non-fast-forward)”

  • Remote has commits you do not have. Run git pull first, then git push.

“fatal: refusing to merge unrelated histories”

  • Two repos with no common ancestor. Usually means you cloned the wrong thing. Add --allow-unrelated-histories only if you are sure.

“You are in ‘detached HEAD’ state”

  • HEAD points to a commit, not a branch. Create a branch to keep your work: git switch -c new-branch

15. If you remember only 5 things

  1. git status is your compass.
  2. Commit is local, push is sharing.
  3. Branches are cheap pointers — use them.
  4. The staging area lets you choose what the next commit contains.
  5. For shared history, prefer git revert over rewriting.

Resources

Official:

For mathematicians:

Tutorials:

When things go wrong: