Branches, Fast-Forward and Three-Way Merges, and Branching Strategies: Guided Hands-On Workflow and Core Operations
Create and inspect branches with modern switch syntax; perform fast-forward and true merges; engineer, abort, resolve, and continue a conflict; and verify reachability before deleting merged branches.
Learning objectives
- Create and switch branches with git branch and git switch without relying on legacy checkout syntax.
- Perform and verify both fast-forward and non-fast-forward merges.
- Inspect merge bases, parent OIDs, refs, and graph shape before and after integration.
- Create a conflict, inspect unmerged index state, abort safely, then resolve and continue.
- Delete merged short-lived branch refs only after proving their commits remain reachable.
1. Create a disposable branch-and-merge lab
Git Bash / Bash / zsh
mkdir git-branch-lab
cd git-branch-lab
git init -b trunk
git config user.name "Branch Lab"
git config user.email "branch-lab@example.invalid"
printf "service=baseline
" > service.conf
git add service.conf
git commit -m "Create service baseline"
git status --short --branch
git log --graph --decorate --oneline --all
PowerShell file-creation alternative
New-Item -ItemType Directory git-branch-lab | Out-Null
Set-Location git-branch-lab
git init -b trunk
git config user.name "Branch Lab"
git config user.email "branch-lab@example.invalid"
Set-Content service.conf 'service=baseline'
git add service.conf
git commit -m "Create service baseline"
git status --short --branch
git log --graph --decorate --oneline --all
2. Create and switch branches with modern commands
git branch
git switch -c feature/healthcheck
git branch --show-current
git show-ref --heads
git switch -c creates a branch at the chosen start
point and attaches HEAD to it transactionally. Older
material often uses
git checkout -b feature/healthcheck; it remains common
compatibility syntax, but switch separates branch
switching from path restoration more clearly.
3. Make feature work while trunk stays still
Git Bash / Bash / zsh
printf "service=baseline
healthcheck=/healthz
" > service.conf
git status --short
git diff -- service.conf
git add service.conf
git commit -m "Add service health check"
FEATURE_FF=$(git rev-parse HEAD)
git log --graph --decorate --oneline --all
Because trunk has not moved, its tip is an ancestor of
the feature tip.
4. Predict and verify a fast-forward
git switch trunk
git merge-base --is-ancestor trunk feature/healthcheck
git rev-parse trunk
git rev-parse feature/healthcheck
git merge --ff-only feature/healthcheck
git rev-parse trunk
git log --graph --decorate --oneline --all
Prediction: trunk moves to the
existing feature commit. No new merge commit is created.
--ff-only turns that graph assumption into an enforced
precondition.
5. Delete the merged branch, then prove the commit remains
git merge-base --is-ancestor feature/healthcheck trunk
git branch -d feature/healthcheck
git show-ref --heads
git cat-file -e "$FEATURE_FF^{commit}"
The feature ref is gone, but the feature commit is now the trunk tip
and remains reachable. On PowerShell capture the OID as
$FEATURE_FF = git rev-parse HEAD and use
git cat-file -e "$FEATURE_FF^{{commit}}".
6. Create deliberate divergence for a true merge
Git Bash / Bash / zsh
git switch -c feature/metrics
printf "metrics=true
" > metrics.conf
git add metrics.conf
git commit -m "Add metrics configuration"
FEATURE_METRICS=$(git rev-parse HEAD)
git switch trunk
printf "runbook: health check is /healthz
" > runbook.md
git add runbook.md
git commit -m "Document service health check"
TRUNK_DOC=$(git rev-parse HEAD)
git log --graph --decorate --oneline --all
git merge-base trunk feature/metrics
Both tips now have unique commits. The merge base should be the earlier health-check commit.
7. Create a non-fast-forward merge commit
git status --short --branch
git merge feature/metrics -m "Merge metrics feature"
MERGE_METRICS=$(git rev-parse HEAD)
git cat-file -p HEAD
git rev-parse HEAD^1
git rev-parse HEAD^2
git log --graph --decorate --oneline --all
The new commit should contain two parent lines. Parent
1 is the previous trunk tip; parent 2 is the merged feature tip in
this simple local case.
8. Engineer a small conflict
Git Bash / Bash / zsh
printf "mode=balanced
" > policy.conf
git add policy.conf
git commit -m "Add policy baseline"
git switch -c feature/strict-policy
printf "mode=strict
" > policy.conf
git add policy.conf
git commit -m "Use strict policy mode"
git switch trunk
printf "mode=conservative
" > policy.conf
git add policy.conf
git commit -m "Use conservative policy mode"
git merge-base trunk feature/strict-policy
git merge feature/strict-policy
The final merge should stop with a conflict because both sides changed the same line differently from the merge base.
9. Inspect the conflict, then abort once
git status
git diff --name-only --diff-filter=U
git ls-files -u
git diff -- policy.conf
git merge --abort
git status --short --branch
git log --graph --decorate --oneline --all
git merge --abort attempts to reconstruct the pre-merge
state. This is why starting from a clean working tree is a strong
safety habit: unrelated uncommitted changes can make recovery from
an integration attempt harder.
10. Re-run, resolve, and continue
git merge feature/strict-policy
printf "mode=strict-with-fallback
" > policy.conf
# PowerShell: Set-Content policy.conf 'mode=strict-with-fallback'
git add policy.conf
git status
git diff --staged
git merge --continue
git merge --continue completes the merge after
conflicts are resolved and staged; Git may open the configured
editor for the merge message. In automation or a teaching validator,
the equivalent final commit can be made non-interactively after
resolution, but humans should review the message and staged result.
11. Delete merged topic refs only after reachability checks
git merge-base --is-ancestor feature/metrics trunk
git merge-base --is-ancestor feature/strict-policy trunk
git branch --merged trunk
git branch -d feature/metrics
git branch -d feature/strict-policy
git log --graph --decorate --oneline --all
The commits remain in the trunk ancestry even though the short-lived branch names disappear.
12. Challenge — choose the graph operation from the requirement
- Verify whether a branch can be integrated without creating a merge commit.
- Find the shared base of two divergent tips.
- Stop an in-progress conflicted merge and return to the pre-merge state.
- List branches whose tips are already reachable from trunk.
- Prove an OID is still reachable after deleting its feature branch.
Select among merge-base --is-ancestor,
merge-base, merge --abort,
branch --merged, and reachability/object inspection
rather than memorizing one sequence.
13. Cleanup
Git Bash / Bash / zsh
git status --short --branch
cd ..
pwd
rm -rf git-branch-lab
PowerShell
git status --short --branch
Set-Location ..
Get-Location
Remove-Item -Recurse -Force git-branch-lab
14. Knowledge check
Question 1. What changed during the fast-forward merge?
Question 2. Why did the metrics merge need a new commit?
Question 3. What does
git merge --abort do?
Question 4. Why is git branch -d safer than
-D?
15. Summary
You created refs without copying history, fast-forwarded one branch, created a true merge for divergent history, inspected merge-base/parents, aborted and then resolved a conflict, and removed merged branch names only after proving reachability.
Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.