A4 — Git Basics: Commit, Branch, Merge, Rebase, Conflict#
"Git isn't a backup tool — it's the record that carries the why of change."
Block: A — Intuition · Duration: ~12h · Prerequisite: A1
🎯 When you finish this module#
- You set up a repo from scratch and build a history with meaningful commits.
- You open a branch, merge it, and resolve a conflict by hand, safely.
- You explain the difference between merge and rebase, and when to use which.
🧠 Why this, why now#
In C2 (CI) and C3 (Terraform), everything will flow through Git; in GitOps (D5), Git will be the single source of truth. Building automation on top of this mechanism before living it by hand, on your own, is flying blind. Once you understand Git not as a "sequence of magic commands" but as a commit graph (DAG), every situation you fear (conflict, a "lost" commit, the wrong branch) turns into an ordinary graph operation.
📖 How to study this#
Read the body and run every command in an empty test repo (open a playground with git init). Learn Git not by memorizing but by continuously seeing the graph with git status and git log --oneline --graph. These two commands are your eyes — look after every step.
📚 Concept map#
| Term | In one sentence |
|---|---|
| Commit | A signed record of the file state at a point in time (snapshot) + message + parent(s) |
| Working directory | The actual files you're working on |
| Staging (index) | The holding area for changes that will go into the next commit |
| Branch | A movable name that points to a commit (main, feature/x) |
| HEAD | Where you currently are — usually the tip of a branch |
| Merge | A commit that joins two histories |
| Rebase | Rewriting commits on top of a different base |
| Conflict | Both sides changed the same line differently; Git can't decide, you do |
| Remote | Another copy of the repo (origin) — GitHub etc. |
1️⃣ Mental model: snapshots and three areas#
Git stores a complete snapshot of the files at every commit — not "diffs" (it computes those itself). Every commit has an identity (hash) and a parent; chained together they form a directed graph (DAG). A branch is nothing more than a movable label in this graph.
A change passes through three areas before it becomes a commit:
git config --global user.name "<FULL_NAME>"
git config --global user.email "<EMAIL>" # puts an author on commits
git init # create an empty repo (creates a .git directory)
git status # what's where: changed, staged, untracked
Run git status often. The answer to "where am I right now?" is always there.
2️⃣ Basic loop: change → stage → commit#
echo "hello" > app.txt
git add app.txt # stage the change
git commit -m "add app.txt: initial version"
git log --oneline # view the history as a one-line summary
# a1b2c3d add app.txt: initial version
Why is git add a separate step? Because you choose which changes go into this commit. Changing two files and committing only one is how you split related changes into separate, meaningful commits.
A good commit message — a letter to your future self, six months from now#
short summary (50 characters, imperative mood: "add", "fix")
After a blank line: WHY you did it. Code already tells you "what";
the commit message carries the "why" — nothing else does.
git logis the only place that will carry the answer to "why is this line like this?" a year from now. Messages like "change", "fix", "update" destroy that answer. For the repo's commit discipline, seeCLAUDE.md— build the foundation for your post-A6 habits right here.
3️⃣ Branch and merge: working in parallel#
A branch lets you work without breaking the main line. When you're done, you merge into the main line.
git switch -c feature/greeting # open a new branch and switch to it (old: git checkout -b)
echo "hi" >> app.txt
git commit -am "add greeting line"
git switch main # go back to the main line
git merge feature/greeting # merge feature into main
git log --oneline --graph --all # see the graph — branching and merging
There are two kinds of merge:
| Case | What happens | Result |
|---|---|---|
| Fast-forward | If main hasn't moved since the feature branched off | The label just slides forward; no extra commit, linear history |
| Merge commit | If both sides have moved forward | A merge commit with two parents is born |
Delete the branch once you're done (the label goes away, the commits stay):
4️⃣ Conflict: Git can't decide, you do#
A conflict happens when two branches change the same line differently. This isn't an error, it's normal — Git can't know which one is correct, so it asks you.
git merge feature/x
# CONFLICT (content): Merge conflict in app.txt
git status # which files conflicted
Git leaves markers in the conflicting file:
The fix is manual: open the file, write the correct result (delete all three marker lines), then:
git add app.txt # means "I resolved this conflict"
git commit # complete the merge (the message comes pre-filled)
Don't be afraid of conflicts. Read what the markers say: the top is your side (HEAD), the bottom is the incoming side. Write the correct combination. If you get confused,
git merge --abortreverts everything to before the merge — there's always a safe way out.
5️⃣ Rebase: rewriting history#
Merge joins two histories with a merge commit; rebase rewrites your commits by moving them onto a different base — the result is a flat, linear history.
git switch feature/x
git rebase main # move feature commits onto the tip of current main
# if a conflict occurs: resolve → git add → git rebase --continue
| Merge | Rebase | |
|---|---|---|
| History | Real (branching visible) | Linear (clean) |
| Commit hashes | Preserved | Change (rewritten) |
| When | Merging shared branches | Keeping your own local branch up to date |
🔒 Golden rule: don't rebase shared history#
Rebasing a branch that others are also working on (pushed) breaks everyone's history — hashes change, and everyone ends up disconnected. Rule: use rebase only on local commits you haven't shared with anyone yet. Merge what's shared.
6️⃣ Remote — briefly (groundwork for C2/D5)#
Local is enough for now, but get to know what a remote is for C2 and GitOps:
git clone <URL> # copy the remote repo locally (sets up origin)
git push origin <branch> # send local commits to the remote
git pull # fetch remote commits + merge (fetch + merge)
origin is the remote repo's alias. In C2, a push will trigger the CI pipeline; in D5, a merge into main will mean a deploy to production. That's why a clean history isn't just aesthetics — it's automation's input.
7️⃣ Undoing: restore, reset, revert, stash#
What scares beginners most is "I did it wrong, how do I undo it". There are three different kinds of "undo," and mixing them up loses data:
| Command | What it does | When |
|---|---|---|
git restore <file> | Discard the change in the working directory (revert to the last commit) | Erasing a mistake you haven't committed yet |
git restore --staged <file> | Remove it from staging (the change stays) | Undoing a wrong add |
git revert <commit> | Adds a new commit that cancels out that commit | Safe undo in shared history |
git reset --soft <commit> | Move HEAD back, leave the changes in staging | Reorganizing recent commits (local) |
git reset --hard <commit> | Move HEAD back, delete everything | ⚠️ Local; used wrong, it loses work |
The key distinction:
revertundoes by adding (history is preserved, safe when shared).resetundoes by deleting/moving (rewrites history, local only). If a commit has already been pushed, userevert, notreset --hard.
Set aside temporarily: stash#
You're in the middle of something but urgently need to do something else. Set the unfinished work aside without committing it:
git stash # clean the working directory, set changes aside
git switch main # do the urgent work
git stash pop # come back, bring back the work you set aside
.gitignore — what NOT to track#
Some files should never enter the repo: secrets (.env), generated output (node_modules/, __pycache__/, site/), local settings. Exclude these with .gitignore:
Once a file has been committed,
.gitignorewon't undo that (it's already tracked). First untrack it withgit rm --cached <file>, then ignore it. For a leaked secret, even this isn't enough: it stays in history — rotate the key.
Not lost: reflog#
"My commit is gone" is almost never true. git reflog keeps every place HEAD has been; you can recover even from a wrong reset:
git reflog # HEAD's full history (most recent moves on top)
git reset --hard <good_hash> # go back to the solid point you saw in reflog
🚫 Anti-pattern table#
| Anti-pattern | Why it's bad | Right |
|---|---|---|
| "change", "fix", "wip" commit messages | Destroys the "why"; history becomes unreadable | Imperative summary + why in the body |
| Everything in one giant commit | Makes undoing/reviewing impossible | Split related changes into separate, atomic commits |
| Rebasing a shared/pushed branch | Breaks everyone's history | Merge what's shared; rebase only locally |
Blindly using --theirs/--ours on conflicts | You pick the wrong side and lose data | Read the markers, write the correct combination by hand |
Committing secrets (passwords, .env, keys) | Permanent in history; hard to remove | .gitignore + secrets management (D3); rotate the key if it leaks |
git push --force (on a shared branch) | Deletes someone else's work | --force-with-lease, and only on your own branch |
Blindly staging everything with git add . | Lets in files/secrets you didn't want | Check what you staged with git status/git diff --staged |
| Putting large binary files in the repo | Bloats the repo, slows down cloning | Artifacts go to the registry (C2); repo holds only source |
📖 Further reading (not now, later)#
| Source | For what | When |
|---|---|---|
01-Git-Workflow/Trunk-Based-Development.md | Team workflow: short-lived branches, frequent merges | Before C2 — read before starting CI |
git help <command> (e.g. git help rebase) | The official, complete reference for every command | When you're curious about a behavior |
🔨 Lab#
👉 labs/build/L04-git/ — (Task outline: a repo from scratch, two branches, deliberately create and resolve a conflict; do the same work once with merge and once with rebase, and see the difference in the graph.)
✅ Acceptance criteria#
Don't move to the next module until all of these are verified: - [ ] You opened two branches, deliberately created a conflict on the same line, resolved it by hand, and showed the result with git log --oneline --graph. - [ ] You moved a branch onto current main with git rebase; you showed how the history (hashes) changes compared to a merge. - [ ] You wrote up the "merge or rebase" decision on an example, including the golden rule (don't rebase what's shared). - [ ] You wrote a commit message in "summary + why" form and explained the difference from a bad example ("fix") in writing.
🧪 Test yourself#
- Why is
git adda separate step fromgit commit? Explain with a useful example. - Scenario:
git merge featuregave a "CONFLICT". No panic — what do you do, step by step, and if you think it's going wrong, what's your safe way out? - Design: You and a teammate are working on a shared
feature/apibranch (you've both pushed). You want to sync it with currentmain. Merge or rebase, and why?
Answers
1. Because **you choose which changes go into the commit.** Example: while fixing a bug, you also notice a typo. You want to commit them separately: `git add bugfix-file && git commit -m "..."`, then `git add typo-file && git commit -m "..."`. Staging makes this separation possible; commits stay atomic and readable. 2. (a) `git status` — which files conflicted. (b) Open each conflicting file, read the `<<<<<<<`/`=======`/`>>>>>>>` markers, write the correct combination, delete the markers. (c) `git add🆘 If you're stuck#
| Symptom | Likely cause | What to do |
|---|---|---|
CONFLICT (content) | Both sides changed the same line | Resolve the markers by hand → git add → git commit |
| Committed on the wrong branch | Forgot to switch | See the commit with git log; undo it with git reset --soft HEAD~1 (the change stays), switch with git switch <correct-branch> and commit again |
| "My commit disappeared" | A branch/HEAD move or a reset | git reflog — HEAD's full history; the commit is right there |
Stuck mid-rebase | A conflict is waiting to be resolved | Resolve → git add → git rebase --continue; exit: --abort |
push rejected (non-fast-forward) | The remote has commits newer than yours | git pull first (fetch+merge), then push |
| Accidentally committed a secret | No .gitignore / git add . | Rotate (invalidate) the leaked secret; removing it from history isn't enough |
💼 Portfolio output#
A clean Git history and the habit of meaningful commit messages — visible and reviewed in every repo output that follows (A6, C2, C3, D5).
⏭️ Up next#
"A good commit history is a letter written to you, six months from now."