Alp Bolukbasi
Karlsruhe, DE

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

somewhere between the debugger and the datasheet


01 // Note


git

How Git Could Have Saved My Friendship

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.

Git is a model of history

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

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.

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 does not merely “tell Git to track a file.” It copies the chosen content into the index, preparing part of the next snapshot.

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

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.

Branches are movable references

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.

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 does not mean the shared history was false. It means the same starting point no longer guarantees 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 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
Three-way Git merge using two branch tips and their common ancestor
A three-way merge compares both branch tips with their shared ancestor.

A conflict is not Git failing. It is Git refusing to pretend that two incompatible changes are the same change.

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. Rewriting a shared past is something else entirely.

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, not because they define us, but because they explain what happened next.

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 is possible when the evidence still exists. Memory is useful, but it is not the same thing as 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.

You can rewrite your own draft. You cannot safely rewrite a shared history without first knowing what changed for the other person.

What Git cannot save

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.

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 does not prevent mistakes. It keeps the sequence of decisions readable after the fact.

Humans don't have git status. That's why good communication matters.