Module 1 · Foundations that hold everything up

Lesson 06 — Professional Git

Branches, rebase, conflicts and team workflows: the trade's collaboration tool.

Published
In this lesson
  1. Objectives
  2. 1. The commit is the unit of intention
  3. 2. Branches: cheap, disposable, purposeful
  4. 3. Merge vs rebase: the decision with judgement
  5. 4. Conflicts: mechanics, not drama
  6. 5. Reflog: the safety net nobody knows
  7. 6. Bisect: binary search for bugs
  8. 7. Team flow: GitHub Flow (the one we use in this course)
  9. Self-assessment (answer me in the chat)

Stack: Git on Linux · Project: TicketFlow Status: Published — taught after Lesson 05 is corrected Prerequisite: Lesson 05 — Linux and the terminal


Objectives

By the end of this lesson you will be able to:

  1. Write commits that tell the intention (and use git add -p to split them properly).
  2. Choose between merge and rebase with judgement and resolve conflicts without panic.
  3. Recover from any disaster with reflog (nothing is truly lost).
  4. Find the commit that broke it with git bisect.
  5. Work with a team flow (GitHub Flow / trunk-based) with reviewable PRs.

1. The commit is the unit of intention

A commit is not "saving": it is telling one step of the project. The message rule (conventional commits, the de facto convention):

<type>: short description in imperative

optional body: the why, not the what (the diff shows the what)
refs: #123

Types you will use: feat, fix, refactor, test, docs, chore, perf. Real TicketFlow examples:

feat(reservations): lock seats with select_for_update when reserving

The previous check-then-act allowed a race between CHECK and ACT.
Now the seat row is locked inside the transaction, and the unique
constraint stays as the safety net.

refs: #42
fix(payments): respond 409 when the gateway retries a confirmed payment

The duplicated webhook created a second Payment for the same reservation.
With idempotency_key as unique, the retry returns the original result
instead of failing.

And the tool that splits the "all together" into clean commits: git add -p (patch mode) — you decide hunk by hunk what goes in. If one commit mixes "refactor + fix + feature", none of them can be reverted alone: the disaster arrives at revert time.

2. Branches: cheap, disposable, purposeful

bash
git switch -c feat/reservation-with-locking # create and switch (the modern checkout)
git branch -m old-name new-name    # rename
git branch -d finished-feature     # delete merged (-D if you force it)

One change = one branch = one PR. If your branch lives more than two days without a PR, it is rotting: the merge gets complicated, the conflict grows and nobody does the review. Short branch, small PR, fast feedback.

3. Merge vs rebase: the decision with judgement

Both integrate changes; they differ in the history they leave:

git mergegit rebase
Historyfaithful: branching and joininglinear: "as if it was always like this"
Commitsadds a merge commitrewritten with new hashes
Whenintegrating into the main branch (honest history)updating YOUR working branch (clean history)

The team's golden rule: rebase your own branches onto main; merge (or squash) when integrating into main. And the law that is never broken: never rebase what is already shared (commits others already have) — you rewrite other people's history and the team bills you for it.

bash
# update your branch with main's new commits (the right rebase)
git switch feat/reservation-with-locking
git fetch origin
git rebase origin/main

# if the rebase goes sideways: aborting is free and safe
git rebase --abort

4. Conflicts: mechanics, not drama

A conflict says "two branches touched the same lines and Git cannot guess". The process:

bash
git status                        # lists the conflicted files
# edit: choose between <<<<<<< yours / ======= / >>>>>>> theirs
git add resolved_file
git rebase --continue             # or git commit if it was a merge

Avoidable conflicts: small PRs, short branches, and frequent git fetch + rebase (the fresher your branch, the less accumulated conflict). Structural conflicts (two people changing the same function): that is a team-communication signal, not a Git problem.

5. Reflog: the safety net nobody knows

git reflog records every movement of HEAD (commits, resets, rebases) for ~90 days. The "deleted" commit almost always lives here:

bash
git reflog
# a1b2c3d HEAD@{0}: reset: moving to origin/main
# e4f5g6h HEAD@{1}: commit: feat: lock seats...
git reset --hard HEAD@{1}         # back to the "lost" commit

Before any destructive reset --hard: git branch backup-before (a branch costs nothing). Reflog turns "I lost my work" into "I lost 30 seconds".

6. Bisect: binary search for bugs

"It worked last week." Git finds it by itself:

bash
git bisect start
git bisect bad                    # the current commit is broken
git bisect good v1.2.0            # this tag worked
# Git checks out the middle commit; you test:
#   if it fails: git bisect bad
#   if it works: git bisect good
# ...in ~log2(n) steps it points at the culprit commit
git bisect reset

With 1,000 commits that is ~10 tests. And it automates: git bisect run make test runs your tests for you.

7. Team flow: GitHub Flow (the one we use in this course)

The most common one in small/medium teams:

main (always deployable)
  └── feat/short-branch → PR → review → green CI → merge (squash) → deploy

The rules that make it work:

  1. main always deployable: if it breaks, revert is the first tool (not "hotfix in place").
  2. Small PR (< 400 lines): real review happens there; nobody reads a 3,000-line PR.
  3. Mandatory CI before merge: tests, lint (Lesson 41 builds it).
  4. Squash on merge: the branch may hold "wip, fix typo, again"; on main there is one commit with intention (section 1).
  5. Delete branch after merge: history lives on main; dead branches confuse.

Key: the flow is not religion; it is the agreement on how the repository's history reflects the work. The team decides (GitHub Flow, trunk-based with feature flags, GitFlow for packaged releases) and writes it down — exactly what we did with the course ADRs.


Self-assessment (answer me in the chat)

  1. Why is a commit mixing refactor and fix an operational problem (revert, bisect, review)? Split it with git add -p: which hunk would go into each commit?
  2. Your teammate rebased a branch you had already cloned and now pull gives you duplicated history. What happened and how is it avoided in a team?
  3. What is the real difference between git merge main and git rebase main from your feature branch? Which would you pick for this project's PRs and why?
  4. A bug appears in production; the last good tag is 300 commits back. Describe the exact bisect flow to locate it.
  5. Why does "main always deployable" demand revert as the first response rather than hotfix? Which squash/merge policy makes that revert viable?

Continue with the exercises. The solutions only after trying it yourself.