Alp Bolukbasi

M.Sc. Computer Science @ KIT : hardware-aware systems engineer

Karlsruhe, DE — somewhere between the debugger and the datasheet


How Git Could Have Saved My Friendship

git

I wish I had treated one friendship more like a shared Git history. This note explains commits, branches, conflicts, rebasing, and recovery through the mistakes that made that comparison uncomfortable.

Git coordinates changes in software by recording snapshots, preserving ancestry, exposing conflicts, and keeping recovery paths after mistakes.

While learning those mechanisms, I kept finding names for failures that had happened outside source code too.

The technical details here are real Git behaviour. The friendship is why I remember them, since people keep much worse version histories than computers do.

The premise. Good version control cannot prevent change, disagreement, or mistakes. It keeps them visible while there is still enough context to understand them.

Git is a model of history

Before learning commands, it helps to understand what Git stores. A repository is a content-addressed object database whose commits form a directed acyclic graph. The folder of working files is one view of that database.

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
Git object model showing blobs, trees, commits, tags, and their content-addressed relationships
Git stores content as objects connected by content-addressed references.

I used to argue about history as if one person owned the document. A Git graph keeps the shared ancestry, explicit changes, and eventual divergence in the same record.

Uncommitted changes

A normal Git repository has three states that beginners often collapse into one:

Working treeThe files currently visible and editable on disk.
IndexThe exact snapshot being prepared for the next commit.
RepositoryThe committed object history stored under .git.
git status
git diff
git diff --staged

git add path/to/file
git commit

git add copies the chosen content into the index and prepares that exact version for the next snapshot. Tracking a file is only part of what the command does.

Git working tree, staging index, HEAD, and repository model
Changes move from the working tree to the index and finally into committed history.

My friendship did not end with one catastrophic commit. It accumulated a working tree full of changes that we never inspected, named, or shared.

Branches are movable references

A Git branch is a lightweight name pointing to a commit rather than a separate copy of the project. 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.

Git HEAD and movable branch references pointing to commits
HEAD normally refers to the checked-out branch, while each branch is a movable reference to a commit.
Git detached HEAD state followed by recovery through creation of a new branch
Work created in a detached HEAD state can be preserved by attaching it to a new branch.

Divergence did not make our shared history false. It meant that the same starting point no longer guaranteed the same destination.

Conflict is information

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 represent competing changes that require interpretation. They only show the location where Git had to stop.

git merge other-branch
git status

# resolve the files manually

git add resolved-file
git commit
Three-way Git merge using two branch tips and their common ancestor
A three-way merge compares both branch tips with their shared ancestor.

Git stops at a conflict because two incompatible changes need a human decision. Combining them automatically would hide the disagreement.

Rebase: cleaner history, different identities

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.

Comparison between Git merge and Git rebase commit histories
Merge preserves divergent ancestry. Rebase replays changes onto a new base to produce a linear history.

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
Interactive Git rebase using pick, reword, edit, squash, fixup, and drop
Interactive rebase turns an untidy private sequence into a deliberate published history.
Two software engineers examining and reorganizing a private commit history with interactive rebase
Reviewing, rewording, squashing, and dropping a private sequence before it becomes shared history.

Editing a private draft is reflection, while rewriting a shared past changes history that somebody else already depends on.

Reset, restore, and revert are not synonyms

Git has several ways to undo work because "undo" can mean several different things.

git restoreRestore file content in the working tree or index.
git resetMove a branch reference and optionally update the index and working tree.
git revertCreate 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>
Comparison of Git reset soft, mixed, and hard modes across HEAD, the index, and the working tree
Soft, mixed, and hard reset differ in how they update HEAD, the index, and the working tree.

Some mistakes should remain visible because they explain what happened next without having to define the person who made them.

The reflog remembers where your references were

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 of an apparently lost Git commit using reflog and a new branch
Reflog records previous reference positions, allowing an apparently lost commit to be recovered.
Git recovery decision tree covering reflog, restore, reset, and related tools
The correct recovery tool depends on whether the missing object is a commit, a file, a staged change, or published history.

Recovery depends on evidence that still exists. Memory helped me, but it could not replace a shared record.

Shared history requires coordination

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.

Local Git repository, remote-tracking reference, and remote repository with fetch, pull, and push operations
A local branch, a remote-tracking reference, and the branch on the actual remote repository are related but distinct.

I can rewrite my own draft. Before changing a shared history, I need to know what changed for the other person.

What Git cannot save

Git preserves ancestry, identifies divergence, exposes conflicting changes, and recovers work that appeared lost. It can also show a team exactly how a system reached its current state.

Git cannot decide what a person meant, make an unspoken change visible, or guarantee that two branches still want to merge. Those were the parts I needed, and they had never been committed anywhere.

Git mental model connecting snapshots, history, branches, references, recovery, and confidence
Git turns snapshots, ancestry, references, and recovery mechanisms into an understandable model of change.

Good version control cannot prevent mistakes. It keeps the sequence of decisions readable after they happen.

Humans don't have git status, so good communication really do matters lol.

back to notes