M.Sc. Computer Science @ KIT : hardware-aware systems engineer
somewhere between the debugger and the datasheet
01 // Note
A practical guide to commits, branches, conflicts, rebasing, recovery, and the uncomfortable similarities between version control and human relationships.
Git was designed to coordinate changes in software. It records snapshots, preserves ancestry, exposes conflicts, and gives us tools for recovering from mistakes.
Somewhere along the way, I began to notice that many of its most useful ideas had very little to do with source code.
This is not a relationship-advice article disguised as a Git tutorial. It is a real Git tutorial, written after learning that humans are considerably worse at version control than computers.
The premise. Good version control does not prevent change, disagreement, or mistakes. It makes them visible while they are still possible to understand.
Before learning commands, it helps to understand what Git is actually storing. Git is not merely a folder of old file versions. It is a content-addressed object database whose commits form a directed acyclic graph.
A commit points to a tree representing a project snapshot, metadata describing the change, and one or more parent commits. The parent links are what turn isolated snapshots into history.
commit
├── tree
├── parent
├── author
├── committer
└── message
We often argue about history as though it were a document owned by one person. Git treats it as a graph: shared ancestry, explicit change, and paths that may eventually diverge.
A normal Git repository has three states that beginners often collapse into one:
| Working tree | The files currently visible and editable on disk. |
|---|---|
| Index | The exact snapshot being prepared for the next commit. |
| Repository | The committed object history stored under .git. |
git status
git diff
git diff --staged
git add path/to/file
git commit
git add does not merely “tell Git to track a file.” It
copies the chosen content into the index, preparing part of the next
snapshot.
Most friendships do not end because of one catastrophic commit. They accumulate a working tree full of changes that were never inspected, named, or shared.
A Git branch is not a separate copy of the project. It is a lightweight name pointing to a commit. As new commits are created, the active branch reference moves forward.
git branch
git switch -c feature/new-direction
git log --oneline --graph --decorate --all
HEAD normally points to the currently checked-out branch.
In a detached HEAD state, it points directly to a commit instead.
Divergence does not mean the shared history was false. It means the same starting point no longer guarantees the same destination.
A three-way merge compares two branch tips with their best common ancestor. When both branches changed compatible regions, Git can combine them automatically. When they changed the same region incompatibly, Git stops and asks for a human decision.
<<<<<<< HEAD
our version
=======
their version
>>>>>>> other-branch
The conflict markers are not the conflict itself. They are Git's representation of competing changes that require interpretation.
git merge other-branch
git status
# resolve the files manually
git add resolved-file
git commit
A conflict is not Git failing. It is Git refusing to pretend that two incompatible changes are the same change.
Rebasing takes a sequence of commits, temporarily removes them, moves the branch to a new base, and replays equivalent changes one by one.
git switch feature
git rebase main
The replayed commits have new parents and therefore new commit hashes. The content may appear equivalent, but the commits are new objects.
Interactive rebase provides a controlled way to edit a private sequence before sharing it:
git rebase -i HEAD~5
pick a1b2c3 first coherent change
fixup d4e5f6 typo in the previous change
reword a7b8c9 explain the decision properly
squash d0e1f2 combine related work
Editing a private draft is reflection. Rewriting a shared past is something else entirely.
Git has several ways to undo work because “undo” can mean several different things.
git restore | Restore file content in the working tree or index. |
|---|---|
git reset | Move a branch reference and optionally update the index and working tree. |
git revert | Create a new commit that inverses an earlier commit. |
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1
git restore path/to/file
git restore --staged path/to/file
git revert <commit>
Some mistakes should remain visible, not because they define us, but because they explain what happened next.
The reflog records local updates to references such as
HEAD. A commit that appears lost after a reset or rebase may
still be reachable through the reflog.
git reflog
git switch --detach <old-commit>
git branch recovered-work <old-commit>
Recovery is possible when the evidence still exists. Memory is useful, but it is not the same thing as a shared record.
Remote-tracking branches such as
origin/main represent the last known state of a branch in
another repository. They are updated by fetching.
git fetch origin
git branch -vv
git log --oneline --graph --decorate --all
git pull --ff-only
git push
git push --force-with-lease
--force-with-lease is safer than an unconditional force
push because it refuses to overwrite the remote branch when it has
changed in an unexpected way.
You can rewrite your own draft. You cannot safely rewrite a shared history without first knowing what changed for the other person.
Git can preserve ancestry, identify divergence, expose conflicting changes, and recover work that appeared to be lost. It can even help a team describe exactly how a system reached its current state.
It cannot decide what a person meant. It cannot make an unspoken change visible. It cannot guarantee that two branches still want to merge.
Good version control does not prevent mistakes. It keeps the sequence of decisions readable after the fact.
git status. That's why good communication matters.