PRACTICAL TECHNICAL HANDBOOK / 19 SEPTEMBER 2026
Git Branching & Merging
Branch types, integration choices, conflicts and safe recovery
Understand what Git changes, choose the right method, and verify the result. The scenario atlas pairs before-and-after graphs with runnable commands, trade-offs and the failure modes that matter.
Navigate the handbook
01. The mental model and terminology
02. Branch types and branching workflows
03. Everyday commands and remote branches
04. Choose an outcome and prepare the lab
05. Merge scenario atlas: S01-S15
06. Conflict resolution playbook
07. Collaboration, pull requests and releases
08. Recovery and troubleshooting
Appendix A. Copyable Mermaid diagram source
Appendix B. Official references and verification
The five choices to recognize immediately
| Desired outcome | Reach for |
|---|---|
| Advance a target that has not diverged | git merge --ff-only topic |
| Keep both histories and record integration | git merge --no-ff topic |
| Deliver one commit per short-lived topic | git merge --squash topic, then commit |
| Replay a private topic into a clean series | git rebase main, then fast-forward |
| Move one independent fix to another line | git cherry-pick -x FIX_SHA |
Reading key. Commands in the scenario atlas are reproducible after loading the lab helper. Other command blocks are named recipes: adapt the stated branch, remote, file or commit names. Bracketed numbers point to references in Appendix B.
01. The mental model
A commit records a snapshot and its parent commit(s). A branch is a movable name for a commit, not another copy of the project. A commit can be reachable from many branches; it does not permanently belong to the branch on which you first created it. [1, 2]
flowchart LR
A["A"] --> B["B<br/>main"] --> C["C<br/>feature/login"]
Here, main points to B and feature/login points to C. With HEAD attached to feature/login, a new commit advances feature/login, not main. Diagram arrows show older-to-newer ancestry; actual commit objects refer back to their parents. [1]
| Term | Meaning that matters in practice |
|---|---|
| HEAD | Your current position. Usually it refers to the checked-out local branch; in detached HEAD it names a commit directly. |
| Working tree | The files you are editing. Uncommitted edits are not permanently attached to a branch. |
| Index / staging area | The proposed next snapshot. git add updates it; git commit records it. |
| Reference / ref / tip | A ref names an object; a branch tip is the commit currently named by that branch. |
| Ancestor / descendant | A commit is an ancestor when you can reach it by walking parent links from another commit. |
| Merge base | A best common ancestor used for comparison. Complex histories can have more than one. |
| Source / target branch | Source supplies changes; target receives them. git merge topic modifies the currently checked-out target. |
| Merge commit / first parent | A merge commit has multiple parents. The first is normally the previous tip of the branch receiving the merge. |
| DAG / tree | The history is a directed acyclic graph. A tree describes a snapshot of paths and content; identical trees need not imply identical history. |
| Tag / release | A tag identifies a chosen revision. Publishing or deploying a release is a separate workflow, not something a branch name proves. |
Terminology and ancestry: [1, 2, 11, 12].
edit files -> git add -> index -> git commit -> local branch
local branch -> git push -> branch on the remote
The rule to remember. Switch to the destination before merging. git switch main followed by git merge topic means topic into main. Reversing the switch reverses the integration direction.
02. Branch types: mechanism versus convention
Git has reference mechanisms and branch states. Teams add names such as feature, release and hotfix as conventions. The name alone does not change Git behavior. [1, 2]
| Form or state | What it is | Important distinction |
|---|---|---|
| Local branch | A writable local ref such as main or feature/login. | Committing advances the checked-out branch. |
| Branch on a remote | A branch stored in another repository, commonly a server. | Push updates it subject to permissions and policy. |
| Remote-tracking ref | Your local last-observed view, such as origin/main. | Fetch refreshes it; it is not a live view of the server. |
| Tracking local branch | A local branch with an upstream association. | The association informs status and pull; it is not automatic synchronization. |
| Unborn / orphan branch | An unborn branch has no commit yet. An orphan start creates a new root history. | After its first commit it is a normal branch with independent ancestry. See S11. |
| Protected branch | A hosting-service rule applied to selected branches. | Review, checks and push restrictions are server policy, not a special Git branch object. |
| Detached HEAD | HEAD names a commit rather than a local branch. | Not a branch type. Name new work with git switch -c rescue/work before leaving it. |
Reference mechanisms: [6, 9, 10]. Server protection: [20].
Read the branch state
git branch # Local branches; * is current
git branch -r # Remote-tracking refs
git branch -a # Both views
git branch -vv # Upstream and ahead/behind summary
git remote -v # Remote names and URLs
Create a tracking branch or set its upstream
These are separate recipes. The remote branch must already exist for the first one.
git fetch origin
git switch --track -c feature/login origin/feature/login
# Existing local branch: change its upstream association.
git branch --set-upstream-to=origin/feature/login feature/login
Three meanings people confuse. origin is a conventional remote name. An upstream branch is a configured tracking relationship. A remote literally named upstream is merely another remote, often the original project in a fork workflow. [6, 9]
A fork is another repository, not another branch. A worktree is another working directory attached to the same repository; it is useful for keeping two branches checked out separately. [19, 22]
Branch roles used by teams
Choose roles because the delivery process needs them, not because Git requires them. main, master and trunk can all be names for the principal line. The lifecycle below is a recommended naming convention, not a built-in taxonomy.
| Role / example | Start from -> integrate into | Benefit and cost |
|---|---|---|
| Main / trunk | Principal integration line; protect it. | One authoritative integration point; requires a clear quality gate. |
| Develop / integration | Usually main initially; receives features in Gitflow. | Separates next-release work; adds another permanent line to maintain. |
| Feature / topic | Current main, or develop in Gitflow -> that base. | Isolates one change; long-lived topics accumulate integration risk. |
| Bugfix / fix | The affected active line -> the same line. | Focused repair; choosing the wrong base can import unrelated code. |
| Hotfix | Exact deployed tag / supported release -> affected release and active development. | Fast, isolated production repair; every affected line still needs the fix. |
| Release | Approved integration point -> release and necessary back-merges. | Allows stabilization; needs a freeze policy and fix propagation. |
| Maintenance / support | Released tag -> supported version line. | Supports older versions; each maintained line adds backport and testing cost. |
| Experiment / spike | Any explicit base -> merge only if adopted. | Safe exploration; abandoned experiments must not become hidden dependencies. |
| Chore / docs / refactor / test | The active integration line -> that line. | Useful intent labels; they are still ordinary topic branches. |
| Environment branch | A team-defined line such as staging. | Can support a deliberate promotion model; risks drift and environment-only fixes. |
Creation recipes
Run these independently. main must exist; v1.2.0 must be a real released tag. In Gitflow, use develop rather than main where the team requires it. [6, 10, 24]
git switch -c develop main
git switch -c feature/login main
git switch -c bugfix/login-error main
git switch -c hotfix/1.2.1 v1.2.0
git switch -c release/1.3 main
git switch -c support/1.2 v1.2.0
git switch -c experiment/cache main
git switch -c chore/dependencies main
git switch -c docs/install main
git switch -c refactor/auth main
git switch -c test/login main
git switch -c staging main
Choose a branching workflow
A branching workflow decides which lines exist and how long they live. A merge method decides how one integration changes history. Choose them separately; Gitflow does not require every merge to use the same flag.
| Workflow | Structure and good fit | Strength | Cost / risk |
|---|---|---|---|
| Trunk-based | One principal line; very short-lived changes, often with feature flags. Useful for frequent delivery. | Small integration gaps and rapid feedback. | Requires strong checks, small changes and discipline around incomplete work. |
| GitHub flow | Short-lived branch -> review / checks -> main. Useful for simple continuous-delivery teams. | Easy to explain and automate. | Supported old releases need an additional explicit policy. |
| Gitflow | main + develop + feature, release and hotfix branches. Useful for planned, versioned releases. | Clear release-stabilization structure. | More synchronization paths; often excessive for continuously deployed services. |
| Release / maintenance lines | main plus selected supported version branches. Useful when multiple versions remain active. | Isolates version-specific fixes. | Backport selection and per-version testing are ongoing work. |
Workflow definitions and trade-offs: [22, 23, 24]. The Gitflow author also recommends simpler workflows for continuously delivered web applications. [24]
A practical default policy
For a new web application, start with protected main and short-lived topics. Choose squash when a pull request is the delivery unit, or preserve well-structured commits through merges or rebase-and-fast-forward. Add release branches only when actual release support requires them. This is a starting recommendation, not a universal rule.
Branch names are not deployment evidence
A branch called production can move without a deployment, and a deployment can use a tag or commit without moving a branch. Record the deployed commit and artifact identity. Where practical, promote the same built artifact through environments rather than rebuilding different code from environment branches.
Keep the model small
| Question | Decision to record |
|---|---|
| Where does new work start? | main, develop, or an explicitly supported release line. |
| What is the review unit? | One logical commit, one topic branch, or one pull request. |
| Who may rewrite a topic? | Its owner, with explicit coordination if it is already shared. |
| How do fixes propagate? | Merge the shared history or cherry-pick an independent fix; test each target. |
| When is a branch deleted? | After integration is verified and no other work depends on it. |
03. Everyday branching and remote commands
Start one piece of work
Recipe for an existing clone with origin and main. Start clean; do not run this over unrelated uncommitted work.
git status --short
git fetch --prune origin
git switch main
git merge --ff-only origin/main
git switch -c feature/login
# Edit the intended files, then stage only those files.
git add -- path/to/changed-file
git diff --cached
git commit -m "feat: add login flow"
git push -u origin feature/login
Replace path/to/changed-file with a real changed path. -u sets the upstream after a successful push. In a protected repository, finish through a reviewed pull request rather than pushing directly to main. [6, 9, 20]
| Action | What changes | What does not happen automatically |
|---|---|---|
| fetch | Downloads objects and refreshes applicable remote-tracking refs. | Does not integrate those changes into your current local branch. |
| pull | Fetches, then integrates into the current branch using the chosen mode. | Does not choose the correct branch or policy for your team. |
| push | Requests remote ref updates using your local commits. | Does not first fetch and merge someone else’s changes. |
Use explicit pull modes rather than relying on machine-specific defaults. [9, 15]
git pull --ff-only # Refuse if the local branch diverged.
git pull --rebase # Replay local work; changes history.
git pull --no-rebase # Integrate by merge; FF if permitted.
Rename and clean up deliberately
# Rename an existing local branch; coordinate shared names.
git branch -m old-name new-name
git push -u origin new-name
# After integration has been verified, leave the topic first.
git switch main
git branch -d feature/login
git push origin --delete feature/login
git fetch --prune origin
Deletion is two separate actions. git branch -d is local; git push origin --delete changes the server. Fetch pruning removes stale remote-tracking refs, not your local branches. After a squash, verify delivery and dependencies before deliberately using -D; do not force deletion simply to silence the safety check. [6, 9]
04. Inspect history and choose the outcome
Read the graph, not just the file diff
git log --oneline --graph --decorate --all
git log main..topic # Commits only on topic
git log --left-right --oneline main...topic
git diff main topic # Compare the two tip trees
git diff main...topic # Merge base -> topic tree
git diff --cached --check # Staged whitespace/marker checks
For log, two dots select commits reachable from the right but not the left; three dots select the symmetric difference. For diff, three dots compare the merge base with the right-hand tip. Do not assume log and diff interpret the notation identically. [12, 16]
| Method | Original source commits | New integration commit | Best reason to choose it |
|---|---|---|---|
| Fast-forward | Kept unchanged | None | Advance an already-compatible target. |
| Normal / –no-ff merge | Kept unchanged | A multi-parent merge, unless no-op / permitted FF | Preserve shared history and integration ancestry. |
| Squash merge | Remain on source; not linked as merged parents | One ordinary commit after you commit | Make one topic the delivery unit. |
| Rebase + FF | Replayed commits get new IDs when rewritten | No merge commit | Publish a clean, private patch series. |
| Cherry-pick | Source stays; selected patch is copied | Usually one ordinary commit per pick | Move a particular independent fix. |
A fast-forward does not flatten merge commits that already exist in the source history. The table describes the integration step, not a guarantee that the entire graph is linear. [3-7]
Methods are not algorithms
| Algorithm / option | Meaning |
|---|---|
| ort | Current default for a two-head merge; handles common-base comparison and rename detection. |
| recursive | Historical implementation; since Git 2.50 it is an alias for ort. Older installations differ. |
| resolve | Older two-head strategy without rename handling; rarely needed for routine work. |
| octopus | Combines multiple topic heads; refuses complex manual conflict resolution. |
| ours / subtree | Special history-preserving or layout-aware strategies. See S13 / S14. |
| -s versus -X | -s selects a strategy; -X supplies an option to it. Squash, –ff and –no-ff are not strategy names. |
Algorithm details: [8].
Prepare the disposable learning lab
Run the helper below once in Bash or Zsh: macOS, Linux, or Git Bash on Windows. Every lab MODE call creates and enters a new temporary repository. It never reuses your project directory. Git 2.34+ covers the commands used here; the examples were executed with Git 2.47.3.
lab() {
case "$1" in
base|ff|split|conflict) ;;
*) echo 'Use: lab base|ff|split|conflict'; return 1 ;;
esac
cd "$(mktemp -d "${TMPDIR:-/tmp}/git-lab.XXXXXX")" || return
git init -q -b main
git config user.name 'Git Learner'
git config user.email 'learner@example.invalid'
git config commit.gpgsign false
git config merge.ff true
git config rerere.enabled false
printf 'color=blue\n' > app.txt
git add app.txt; git commit -qm A
git branch base
[ "$1" = base ] && return 0
git switch -q -c topic
if [ "$1" = conflict ]; then
printf 'color=green\n' > app.txt
else
printf 'feature\n' > feature.txt
fi
git add .; git commit -qm B
printf 'tests\n' > test.txt
git add test.txt; git commit -qm C
git switch -q main
if [ "$1" != ff ]; then
if [ "$1" = conflict ]; then
printf 'color=red\n' > app.txt
else
printf 'guide\n' > guide.txt
fi
git add .; git commit -qm D
fi
printf 'Disposable lab: %s\n' "$PWD"
}
| Mode | Starting history |
|---|---|
lab base | Only A on main. A helper branch named base stays at A. |
lab ff | main at A; topic adds B and C. |
lab split | main adds D; topic adds B and C in separate files. |
lab conflict | main changes app.txt to red; topic changes it to green, then adds test.txt. |
All graphs omit the helper base ref. Letters are commit-message labels, not literal hashes. The scenarios are independent: always run their first lab command. Expected conflicts and refusals are intentional; do not run those blocks inside a shell configured to exit on the first non-zero command.
05. Merge scenario atlas
S01 | Fast-forward: move the branch pointer
EVERYDAY
Use this when main has not gained its own commits since topic branched off. The target can move from A to C without creating a new commit.
flowchart LR
subgraph Before
bA["A<br/>main"] --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aB["B"] --> aC["C<br/>main + topic"]
end
Older commits point toward newer commits. Mermaid source: S01
lab ff
git switch main
git merge --ff-only topic
git log --oneline --graph --decorate --all
Result. main and topic now point to C. B and C keep their identities; no merge commit exists. Only main moved during the merge.
| Pros | Cons |
|---|---|
| Small, linear history; original commits remain intact; refusal protects a fast-forward-only policy. | No explicit integration boundary. It cannot combine two diverged tips. |
Watch out. Fast-forward does not mean one commit or squashed commits. Every commit already on topic remains in history.
git rev-parse main topic
Expected: Both lines print the same commit ID.
Reference: [3, 4]
S02 | Force a merge commit with –no-ff
EVERYDAY
Use this when the history is fast-forwardable, but the team wants an explicit record that a topic was integrated.
flowchart LR
subgraph Before
bA["A<br/>main"] --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aB["B"] --> aC["C<br/>topic"] --> aM["M<br/>main"]
aA --> aM
end
Older commits point toward newer commits. Mermaid source: S02
lab ff
git switch main
git merge --no-ff topic -m "M: integrate topic"
git show -s --format='%h %p %s' HEAD
Result. M has two parents: A first, C second. The source branch stays at C. The snapshot includes the topic changes.
| Pros | Cons |
|---|---|
| Preserves original commits and a visible feature boundary. First-parent logs give a concise integration timeline. | Adds a commit even when pointer movement would suffice; heavy use can clutter a small repository. |
Watch out. –no-ff does not manufacture a merge when the input is already an ancestor of HEAD. That case is still a no-op.
git log --first-parent --oneline main
Expected: M appears as the integration step on main.
Reference: [3, 4]
S03 | Three-way merge: both branches advanced
EVERYDAY
main contains D while topic contains B and C. A is their common ancestor. Git combines the changes from A to D with those from A to C.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"] --> aM
end
Older commits point toward newer commits. Mermaid source: S03
lab split
git switch main
git merge topic -m "M: combine both histories"
git show -s --format='%h %p %s' HEAD
Result. M joins D and C. Both histories remain reachable. This lab merges cleanly because the sides add different files.
| Pros | Cons |
|---|---|
| Keeps shared history stable and preserves both lines of development; appropriate for collaboratively edited branches. | Creates a non-linear history; unrelated-looking edits can still interact badly at runtime. |
Watch out. A three-way merge is not a three-parent merge. It compares a base and two tips, usually producing a two-parent commit.
git diff HEAD^1 HEAD --stat
Expected: The diff shows what this integration added relative to the previous main tip.
Reference: [3, 4, 12]
S04 | Three-way merge with a conflict
EVERYDAY
Both branches change the same setting differently: main wants red; topic wants green. Git stops for a human decision. The after graph exists only after resolution and commit.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"] --> aM
end
Older commits point toward newer commits. Mermaid source: S04
lab conflict
git merge topic
# Expected conflict: inspect before deciding.
git status --short
cat app.txt
# Lab decision: the agreed final value is purple.
printf 'color=purple\n' > app.txt
git add app.txt
git diff --cached --check
git commit -m "M: resolve color deliberately"
Result. During the conflict, HEAD remains at D and no M exists. After committing, M has D and C as parents and contains the chosen resolution.
| Pros | Cons |
|---|---|
| Preserves both histories and makes the integration decision reviewable. | Requires judgment and regression tests; choosing a side blindly can discard valid behavior. |
Watch out. git add marks a path resolved; it does not prove the result is correct. To abandon an ordinary in-progress merge, use git merge –abort.
git status --short
git show HEAD:app.txt
Expected: The status is clean and the file contains color=purple.
Reference: [4, 13]
S05A | Already up to date: nothing to merge
GUARDRAIL
Every commit reachable from topic is already reachable from main. Repeating the merge changes nothing; the tips need not be equal for this rule to hold.
flowchart LR
subgraph Before
bA["A"] --> bB["B"] --> bC["C<br/>main + topic"]
end
subgraph After
aA["A"] --> aB["B"] --> aC["C<br/>main + topic"]
end
Older commits point toward newer commits. Mermaid source: S05A
lab ff
git merge --ff-only topic # Prepare the BEFORE graph.
git merge topic # Already up to date.
Result. No commit is created and neither pointer moves.
Pros: Safe and idempotent. Cons: It checks ancestry, not whether a past change was later reverted.
Watch out. After a revert or an ours-strategy merge, Git can say this even though the files do not contain the topic behavior.
Reference: [4, 11]
S05B | Fast-forward-only refuses divergence
GUARDRAIL
The target and source each have unique commits. –ff-only refuses rather than choosing a merge or rebase for you.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"]
end
Older commits point toward newer commits. Mermaid source: S05B
lab split
git merge --ff-only topic # Expected non-zero exit.
git status --short
Result. The committed graph and branch pointers stay unchanged. This is a policy refusal, not a merge conflict.
Pros: Prevents accidental merge commits. Cons: You must deliberately select another integration method.
Watch out. Do not answer this error with reset –hard or force-push. Choose a normal merge or a permitted rebase after inspecting the graph.
Reference: [4]
S06 | Squash merge: one delivery commit
EVERYDAY
Use this when a short-lived topic contains useful work but noisy intermediate commits. The destination receives one ordinary commit containing the combined result.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aS["S<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"]
end
Older commits point toward newer commits. Mermaid source: S06
lab split
git merge --squash topic
git diff --cached
git diff --cached --check
git commit -m "S: deliver feature and tests"
Result. S has only D as its parent. There is deliberately no ancestry edge from C to S. B and C still exist on topic, but not as its merged ancestors.
| Pros | Cons |
|---|---|
| One reviewable delivery unit; concise main history; easy to revert the complete change as an ordinary commit. | Loses per-commit detail on main and does not record topic ancestry. Reusing the old topic can repeat changes or conflicts. |
Watch out. –squash does not commit for you and does not set MERGE_HEAD. Use a fresh branch from updated main for the next task. git branch -d may refuse the old topic.
git show -s --format=%p HEAD
Expected: Exactly one parent ID is printed. Do not treat ancestry checks as squash-verification checks.
Reference: [4, 21]
S07 | Rebase, then fast-forward
EVERYDAY – REWRITES TOPIC
Rebase replays the topic commits on top of current main; then a fast-forward integrates the replayed series. Rebase itself is not a merge.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aBP["B'"] --> aCP["C'<br/>main + topic"]
end
Older commits point toward newer commits. Mermaid source: S07
lab split
git switch topic
git branch backup/topic-before-rebase
git rebase main
git switch main
git merge --ff-only topic
Result. D is followed by B’ and C’. These replayed commits have new identities. The backup still retains B and C; it is omitted from the diagram.
| Pros | Cons |
|---|---|
| Linear main history while retaining separate logical commits; useful for a privately owned, well-organized topic. | Rewrites the topic; conflicts can recur per replayed commit; coordinating already-published history takes extra care. |
Watch out. Do not rebase shared main or surprise collaborators on a shared topic. A no-op rebase need not recreate commits; the changed-parent example here does.
git log --oneline --graph main
Expected: The main line is A, D, B’, C’ with no merge commit.
Reference: [5, 6]
S08 | Cherry-pick: bring across one change
SELECTIVE INTEGRATION
Use this to backport a specific fix without importing its entire source branch. Here B adds feature.txt independently; C adds unrelated tests and is intentionally not selected.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aBP["B'<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"]
end
Older commits point toward newer commits. Mermaid source: S08
lab split
git switch main
git show topic~1 --stat
git cherry-pick -x topic~1
Result. A new B’ follows D. The source still points to C. There is no merge-parent edge between the two lines; cherry-pick is not a history merge.
| Pros | Cons |
|---|---|
| Precise selection; useful across supported release lines; -x records the source commit for traceability in the clean example. | Can omit dependencies or tests; creates parallel patch histories and extra maintenance work. |
Watch out. Inspect prerequisites first. A fix may require earlier commits or a release-specific adaptation. -x does not make the source an ancestor.
git show --stat HEAD
git log -1 --format=%B
Expected: Only B’s selected change is applied and the message includes its source commit.
Reference: [7]
S09 | Merge main into a feature branch
EVERYDAY – SYNC DIRECTION
Use this to update a shared topic without rewriting it. The target is topic, so the merge belongs on topic, not on main.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D<br/>main"] --> aM["M<br/>topic"]
aA --> aB["B"] --> aC["C"] --> aM
end
Older commits point toward newer commits. Mermaid source: S09
lab split
git switch topic
git merge main -m "M: sync main into topic"
git show -s --format='%h %p %s' HEAD
Result. M has C as its first parent and D as its second. main remains at D. Integrating main into topic does not deliver topic into main.
| Pros | Cons |
|---|---|
| Safe for shared topic history; conflicts are addressed before final review; no forced update is needed. | Repeated syncs add merge commits to the feature history; frequent small integrations are easier to review. |
Watch out. In a real clone, fetch and merge origin/main when local main may be stale. Recheck the destination branch before every merge.
git rev-parse main
git rev-parse topic^1 topic^2
Expected: main still names D; the two parent lines name C and D, respectively.
Reference: [4, 9]
S10 | Octopus: merge several independent topics
SPECIALIZED
An octopus merge bundles multiple topic heads into one integration commit. Use it only for independent changes that merge without complex manual resolution.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B<br/>feature/ui"]
bA --> bC["C<br/>feature/api"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aA --> aB["B<br/>feature/ui"] --> aM
aA --> aC["C<br/>feature/api"] --> aM
end
Older commits point toward newer commits. Mermaid source: S10
lab base
git switch -c feature/ui
printf 'ui\n' > ui.txt
git add ui.txt; git commit -m B
git switch -c feature/api base
printf 'api\n' > api.txt
git add api.txt; git commit -m C
git switch main
printf 'ops\n' > ops.txt
git add ops.txt; git commit -m D
git merge --no-ff feature/ui feature/api -m M
Result. M has three parents in this example: D, B and C. Both feature branch pointers remain unchanged.
| Pros | Cons |
|---|---|
| One integration point for several independent topics. | Harder to review or partially revert. The octopus strategy refuses complex merges needing manual conflict resolution. |
Watch out. Several regular two-branch merges are usually clearer for application teams. This is not the meaning of a three-way merge.
git show -s --format=%p HEAD
Expected: Three parent IDs are printed.
Reference: [8]
S11 | Merge histories with no common ancestor
SPECIALIZED
Use this only for an intentional repository-history import. Git normally refuses histories that share no ancestor.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bX["X<br/>imported"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aX["X<br/>imported"] --> aM
end
Older commits point toward newer commits. Mermaid source: S11
lab base
git switch --orphan imported
printf 'Imported project\n' > IMPORTED.md
git add IMPORTED.md; git commit -m X
git switch main
printf 'Local guide\n' > guide.txt
git add guide.txt; git commit -m D
git merge --allow-unrelated-histories --no-ff \
imported -m "M: import independent history"
Result. M connects the formerly separate roots. The example keeps both projects at the repository root because their file names do not collide.
| Pros | Cons |
|---|---|
| Preserves the imported history in one repository. | Can create add/add conflicts or a confusing layout; licensing, provenance and sensitive-history review are separate requirements. |
Watch out. Do not use this flag to bypass a wrong remote, wrong repository, or incomplete shallow fetch. An orphan branch is not automatically safe to merge.
git rev-list --max-parents=0 main
Expected: Two root commit IDs are reachable from main.
Reference: [4, 10]
S12 | Prefer a side for conflicting hunks: -X
SPECIALIZED – REVIEW CAREFULLY
-X ours or -X theirs changes how the normal merge resolves conflicting hunks. Non-conflicting changes from both sides still participate. Both choices produce the same ancestry graph below.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"] --> aM
end
Older commits point toward newer commits. Mermaid source: S12
lab conflict
git merge --no-ff -s ort -X ours topic -m M
cat app.txt # color=red
cat test.txt # tests: incoming clean change kept
# Alternative: repeat in a NEW lab, not the merged one.
lab conflict
git merge --no-ff -s ort -X theirs topic -m M
cat app.txt # color=green
cat test.txt # tests: still present
Result. With ours, M keeps red for the conflicting line. With theirs, M keeps green. Both retain test.txt from the incoming topic.
| Pros | Cons |
|---|---|
| Useful when a reviewed policy genuinely decides conflicts of this kind. | Can silently choose the wrong behavior. Some structural conflicts still require intervention. |
Watch out. -X ours is not -s ours, and -s theirs is not a built-in strategy. During rebase, the usual intuitive meaning of ours/theirs is reversed; see the conflict playbook.
git show -s --format=%p HEAD
Expected: The selected variant has a normal two-parent merge commit.
Reference: [8, 13]
S13 | Record ancestry but keep our whole tree
SPECIALIZED – HIGH RISK
The ours strategy records the other history as merged while keeping the destination snapshot unchanged. It is a deliberate history-management operation, not normal conflict resolution.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"] --> aM
end
Older commits point toward newer commits. Mermaid source: S13
lab split
git merge --no-ff -s ours topic \
-m "M: supersede topic without taking its files"
git diff HEAD^1 HEAD
git merge-base --is-ancestor topic HEAD
Result. M has two parents, but its tree equals D exactly. Neither feature.txt nor test.txt is taken from topic. Future merges consider C already integrated.
| Pros | Cons |
|---|---|
| Can retire or supersede a history deliberately while preserving the ancestry record. | Drops all incoming content, including non-conflicting work; may prevent later merges from introducing changes you actually wanted. |
Watch out. An ancestry check returning success is not proof that the source behavior exists in the target. Never use -s ours as a convenient conflict shortcut.
git diff --exit-code HEAD^1 HEAD
Expected: Exit status 0 and no output: the destination tree did not change.
Reference: [8, 11]
S14 | Subtree-aware merge: align directory layouts
SPECIALIZED
Use a subtree-aware merge when the incoming project root corresponds to a subdirectory in the destination. Here app.txt lives at the vendor root, but under vendor/widget on main.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bV["V<br/>vendor"]
end
subgraph After
aA["A"] --> aD["D"] --> aM["M<br/>main"]
aA --> aV["V<br/>vendor"] --> aM
end
Older commits point toward newer commits. Mermaid source: S14
lab base
git switch -c vendor base
printf 'color=green\n' > app.txt
git commit -am "V: update vendor file"
git switch main
mkdir -p vendor/widget
git mv app.txt vendor/widget/app.txt
git commit -m "D: place library in a subdirectory"
git merge --no-ff -s ort -Xsubtree=vendor/widget \
vendor -m "M: align vendor layout"
cat vendor/widget/app.txt
Result. M keeps the destination layout and applies the vendor update under vendor/widget. The file there contains color=green; no root app.txt is created.
| Pros | Cons |
|---|---|
| Accommodates intentional directory-layout differences while retaining history. | Requires correct prefix mapping and review of rename/path behavior. A wrong mapping can produce an unwanted tree. |
Watch out. -s subtree guesses alignment; -Xsubtree=PATH makes the mapping explicit with ort. Neither is the separate git subtree add/pull workflow or a submodule.
git ls-tree -r --name-only HEAD
Expected: The file appears at vendor/widget/app.txt.
Reference: [8]
S15 | Interactive rebase: polish before integration
HISTORY EDITING – PRIVATE TOPIC
Use interactive rebase to reorder, edit, combine or remove private topic commits intentionally. In this example, keep B as pick and change the line for C from pick to fixup in the editor, then save and close.
flowchart LR
subgraph Before
bA["A"] --> bD["D<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"]
end
subgraph After
aA["A"] --> aD["D"] --> aS["S<br/>main + topic"]
end
Older commits point toward newer commits. Mermaid source: S15
lab split
git switch topic
git branch backup/topic-before-edit
git rebase -i main
# Save the todo list: pick B, then fixup C.
git switch main
git merge --ff-only topic
Result. S combines the changes from B and C on top of D and keeps the B message. Both main and topic move to this rewritten series. The backup still retains the old topic and is omitted above.
| Pros | Cons |
|---|---|
| Produces intentional, reviewable commits before publication; fixup removes correction noise without keeping every intermediate message. | Rewrites the topic. Dropping or reordering commits can remove behavior or violate dependencies; reviewers must inspect the rewritten result. |
Watch out. This differs from git merge –squash: interactive rebase rewrites the source branch itself. Use squash instead of fixup in the todo list when you need to combine and edit commit messages.
git log --oneline main
git show --stat HEAD
Expected: The main line is A, D, S; S contains both feature.txt and test.txt.
Reference: [5, 29]
06. Conflict resolution: inspect, decide, finish
A conflict means Git cannot safely choose a result mechanically. It does not mean either branch is wrong. First establish the intended behavior; then edit, stage, test and complete the correct operation. [13, 30]
Use the base to understand both changes
git config merge.conflictStyle diff3
# Set before starting the merge; diff3 includes the base text.
<<<<<<< HEAD
color=red
||||||| base
color=blue
=======
color=green
>>>>>>> topic
In an ordinary merge, the top section is your current target, the middle is the common base, and the bottom is the incoming source. The correct result may be either side or a new combined solution. Remove all markers after deciding. [4, 13]
A reliable resolution sequence
git status
git diff --name-only --diff-filter=U
git ls-files -u
git show :1:app.txt # Base version, if present
git show :2:app.txt # Ours in this ordinary merge
git show :3:app.txt # Theirs in this ordinary merge
# Edit app.txt to the intended final content.
git add -- app.txt
git diff --cached
git diff --cached --check
# Run the relevant build and tests before completion.
git merge --continue
Some conflict types do not have all three index stages. git diff --cached --check can catch whitespace errors and conflict markers; it cannot validate application behavior. [13, 16]
| Operation in progress | Complete after resolving | Cancel |
|---|---|---|
| Ordinary merge | git merge --continue | git merge --abort |
| Rebase | git rebase --continue | git rebase --abort |
| Cherry-pick | git cherry-pick --continue | git cherry-pick --abort |
| Revert | git revert --continue | git revert --abort |
Do not skip a commit to escape a conflict. git rebase --skip and git cherry-pick --skip omit the current patch. Use them only after proving that omission is intentional. --quit is not equivalent to aborting and restoring the previous state. [5, 7, 14]
Conflict types and side-selection traps
| Conflict | What you must decide | Typical action after deciding |
|---|---|---|
| Content / same lines | Which behavior should survive, or how to combine it. | Edit the result, remove markers, then git add -- file. |
| Add / add | Two sides created the same path differently. | Combine or select the intended file, then stage it. |
| Modify / delete | One side deleted a path that the other changed. | Keep and stage it, or confirm deletion with git rm -- file. |
| Rename / rename or rename / delete | Which final path and content are intended. | Resolve paths, stage additions and removals, and inspect the full diff. |
| Binary file | Which complete version should be used. | Select or rebuild a valid file; do not expect line-by-line merging. |
| Generated file / lockfile | Which source inputs and tool version should define the output. | Resolve inputs, regenerate using project tooling, then test. |
| Submodule pointer | Which referenced submodule commit contains the intended work. | Resolve inside the submodule if needed, then stage its selected commit in the parent repository. |
Conflict mechanics and content strategies: [8, 13, 30, 31].
Ours and theirs depend on the operation
| Context | Ours / stage 2 | Theirs / stage 3 |
|---|---|---|
| Merge topic into main | The checked-out main side. | The incoming topic side. |
| Rebase topic onto main | The so-far rebased result, starting at main. | The topic commit currently being replayed. |
During a conflict, the commands below choose a whole file, not just its conflicting hunks. They overwrite the working copy of that conflicted path. Pick one only after reviewing the correct side. [5, 13]
git restore --ours -- app.txt # Choose one, not both.
# OR: git restore --theirs -- app.txt
git add -- app.txt
Reuse a resolution, but still review it
git config rerere.enabled true
git config rerere.autoupdate false
git rerere diff
Rerere records and reuses matching conflict resolutions. Leaving autoupdate false lets you inspect the reused working-tree result before staging it. It saves repeated editing, not the obligation to review and test. [25]
Avoid preventable conflict noise. Keep formatting-only changes separate from behavior changes. Standardize line endings in .gitattributes; where branches genuinely use different normalization rules, a reviewed -Xrenormalize merge may help. Do not ignore whitespace blindly in whitespace-sensitive files. [8, 31]
07. Collaborating without losing someone else’s work
Shared topic: preserve the history
Recipe for a local feature/login tracking a shared remote branch. Fetch first, inspect both sides, then merge the fetched remote tip. A push rejection means the remote may have moved again; inspect it rather than forcing over it. [9, 32]
git switch feature/login
git fetch origin
git log --left-right --oneline HEAD...origin/feature/login
git merge origin/feature/login
# Review the combined result and run the relevant tests.
git push origin HEAD:feature/login
Private published topic: rewrite only with permission
This recipe first incorporates the remote tip without discarding local commits, records the exact remote value, then rebases. Stop on any failed step. It assumes a clean tree, a topic you are allowed to rewrite, and an unused backup name. [5, 9]
git switch feature/login
git fetch origin
git merge --ff-only origin/feature/login
expected=$(git rev-parse origin/feature/login)
git branch backup/feature-before-rebase
git rebase origin/main
# Inspect the rewritten diff and rerun the relevant tests.
git push \
--force-with-lease=refs/heads/feature/login:"$expected" \
origin HEAD:refs/heads/feature/login
Why the explicit lease matters. The push succeeds only if the remote branch still matches the recorded commit. An IDE’s later fetch cannot silently change that saved value. A lease is a concurrency check, not proof that your rewritten content is correct. Never substitute an unrestricted force-push on a shared protected branch. [9]
Inspect the ancestry relationship precisely
git merge-base --is-ancestor topic main # Is topic included?
git merge-base --is-ancestor main topic # Is FF possible?
git merge-base main topic # Find a merge base
For --is-ancestor, exit 0 means yes, 1 means no, and another non-zero status signals an error. If no common base is visible, check for a shallow clone or wrong remote before concluding the histories are genuinely unrelated. [11]
Work on a hotfix without disturbing your current checkout
git worktree add -b hotfix/login ../project-hotfix main
# Work in ../project-hotfix; the original working tree stays put.
# After its work is committed and safe:
git worktree remove ../project-hotfix
A branch normally cannot be checked out in two linked worktrees at once. Removing a clean worktree is separate from deleting its branch. [19]
Pull requests, release lines and the final quality gate
A pull request / merge request is a hosting-service review object, not a native Git commit type. Its final history depends on the repository’s enabled integration method. [20-22]
| Review choice | Result to expect | Important caveat |
|---|---|---|
| Create a merge commit | Preserves source commits and adds a merge boundary. | A linear-history rule may disallow it. |
| Squash and merge | One destination commit for the reviewed change. | Do not continue stacking new work on the old squashed head without realigning it. |
| Rebase and merge | Separate rewritten commits on the destination without a merge commit. | GitHub always creates new commit IDs for this option; it is not identical to every local rebase case. |
Hosting details above describe GitHub; other products can differ. [20, 21]
Release and hotfix recipe: Gitflow-style mechanics
Assume main is the stable released line at v1.3.0, develop holds the next release, and you have approval to integrate. These commands show the local mechanics; use equivalent reviewed pull requests when branch protections require them. [24]
git switch -c hotfix/1.3.1 v1.3.0
# Implement, test and commit the focused repair HERE.
git switch main
git merge --no-ff hotfix/1.3.1 -m "Merge hotfix 1.3.1"
# Tag only the approved, tested release snapshot.
git tag -a v1.3.1 -m "Release 1.3.1"
git switch develop
git merge --no-ff hotfix/1.3.1 -m "Bring hotfix to develop"
# Push approved branch updates and the tag explicitly.
git push origin main develop
git push origin v1.3.1
With an active release branch, ensure the repair reaches it too. For an older supported line, cherry-pick the reviewed independent fix rather than importing an entire newer development line. Tags identify snapshots; do not move a published release tag to hide a correction. [7, 24, 28]
What must be true before integration is declared complete
Confirm the correct target and reviewed diff, no unresolved paths, and a clean or deliberately understood working tree. Run the relevant build, unit, integration and user-interface/end-to-end tests for the changed behavior. The required CI checks must pass on the intended integration result, not merely on an outdated topic commit. Record the chosen merge method and verify the resulting history.
A clean merge is only a text/history result. Two changes can merge without conflict and still break an API contract, database migration, permission check or user flow. Branch protection and tests address different risks; neither replaces review of the actual combined behavior.
08. Recover according to publication state
First run git status. Preserve uncommitted work before trying recovery commands. A backup branch protects committed history only; it does not preserve unstaged edits or untracked files. [17, 18, 26]
| Situation | Preferred recovery | Why |
|---|---|---|
| Ordinary merge still in progress | git merge --abort | Cancels the in-progress merge; a clean starting tree makes recovery more predictable. |
| Unpublished completed merge / FF | Move the local target back to a verified pre-operation backup, using reset --keep where appropriate. | No collaborators depend on the discarded branch movement. |
| Published merge commit | git revert -m PARENT MERGE_SHA | Adds an explicit inverse commit instead of rewriting shared history. |
| Published squash commit | git revert SQUASH_SHA | A squash result is an ordinary one-parent commit. |
| Published fast-forward | Revert the selected ordinary commits in a reviewed, dependency-aware order. | There is no merge commit to revert as a single merge. |
| Lost tip / mistaken rebase | Inspect the reflog and create a rescue branch at the verified old commit. | Names the recoverable history before further edits. |
Prepare before a risky local change
git status --short
git branch backup/pre-integration
# Only when there is work in progress to preserve:
git stash push -u -m "before integration"
git stash list
Stash -u includes untracked files but not ignored files. Later, inspect the intended entry and use git stash apply before dropping it; conflicts can occur while applying it. [26]
Find and name a lost commit
git reflog --date=iso
git show RECOVERED_SHA
git branch rescue/lost-work RECOVERED_SHA
Replace RECOVERED_SHA with the exact commit you inspected. Reflogs are local, expire, and are not a backup of every file edit. Do not assume that HEAD@{1} is always the state you need. [18]
| Reset mode | Index and working-tree consequence |
|---|---|
--soft COMMIT | Moves the branch; leaves index and working files as they are. |
--mixed COMMIT | Moves the branch and resets the index; leaves working files. |
--hard COMMIT | Overwrites tracked state and can overwrite/remove obstructing untracked paths. Destructive. |
--keep COMMIT | Updates the branch and relevant paths, but refuses when affected local changes would be overwritten. |
Reset semantics: [17]. Do not use a hard reset as a routine response to a rejected push or confusing graph.
R01 | Undo a published merge with revert
RECOVERY – PRESERVES HISTORY
For a merge that others may already have fetched, create a compensating commit rather than rewriting the shared branch.
flowchart LR
subgraph Before
bA["A"] --> bD["D"] --> bM["M<br/>main"]
bA --> bB["B"] --> bC["C<br/>topic"] --> bM
end
subgraph After
aA["A"] --> aD["D"] --> aM["M"] --> aR["R<br/>main"]
aA --> aB["B"] --> aC["C<br/>topic"] --> aM
end
Older commits point toward newer commits. Mermaid source: R01
lab split
git merge topic -m M # Prepare the BEFORE graph.
git show -s --format='%h %p %s' HEAD
# In THIS lab, parent 1 is previous main (D).
git revert -m 1 --no-edit HEAD
git diff HEAD~2 HEAD # Empty in this simple lab.
Result. R reverses the change from D to M. M and the topic ancestry remain in history. Here, with no intervening commits, R has the same tree as D.
| Pros | Cons |
|---|---|
| Safe for shared history; the rollback is explicit and reviewable. | Can conflict with later edits. Merging the same topic again does not automatically restore the reverted changes. |
Watch out. -m 1 selects the parent to keep as the baseline; it is not a commit message. Inspect the parents, do not guess. Reintroduce deliberately with a revert of R or genuinely new changes.
git merge-base --is-ancestor topic main
Expected: Still exits 0: reverting content does not remove ancestry.
Reference: [14]
Troubleshooting and the commands worth remembering
| Symptom | What to check / do next |
|---|---|
| Push rejected: non-fast-forward | Fetch and compare local versus remote. Merge a shared line, or rebase only where rewriting is allowed. Do not force first. |
| Already up to date, but code is missing | Inspect earlier reverts, squash commits or an ours-strategy merge. Ancestry is not a content guarantee. |
| Fast-forward impossible | Inspect the unique commits on each side. Choose a deliberate merge or authorized rebase; see S05B. |
| Local changes would be overwritten | Commit a coherent change, stash it, or use another worktree. Do not discard edits just to switch branches. |
| Branch is not fully merged after squash | Expected when original source commits are not ancestors of the destination. Verify the delivered patch and dependent work before deletion. |
| No merge base / unrelated histories | Verify the remote and whether the clone is shallow. Fetch missing history before using the exceptional unrelated-histories flag. |
| Merge still seems unfinished | Use status and inspect unmerged paths. Stage the resolution and continue the operation that is actually active. |
| Rebase keeps stopping | It replays a series, so later commits may conflict separately. Resolve each; do not skip patches without understanding the loss. |
Two easily missed command traps
Review before creating a merge commit: –no-commit alone does not stop a fast-forward. Use –no-ff together with –no-commit when you need an inspectable, not-yet-committed merge result. [4]
git merge --no-ff --no-commit topic
git diff --cached
# Review and test, then git commit; or git merge --abort.
Abandoning a squash attempt: squash does not set MERGE_HEAD, so merge –abort is not its general undo command. Only after a clean start, with no other edits to keep, the following discards its staged/working-tree result. [4, 13]
git restore --source=HEAD --staged --worktree -- .
Danger: the restore command discards edits. It also removes paths added to the index by that squash if they do not exist in HEAD. Never run it over unrelated work you need to keep. Save that work separately first.
Daily inspection
git status --short
git branch -vv
git log --graph --oneline --decorate --all
git diff --stat main...topic
git diff --cached --check
git show -s --format="%h %p %s" HEAD
Appendix A. Copyable Mermaid source
The complete BEFORE and AFTER Mermaid source appears with each corresponding scenario above. Use this index to jump directly to a diagram. Copy one complete Mermaid block into a Mermaid-capable editor. Labels name the branch pointers; arrows run from older to newer commits. [27]
S01 | Fast-forward: move the branch pointer
S02 | Force a merge commit with –no-ff
S03 | Three-way merge: both branches advanced
S04 | Three-way merge with a conflict
S05A | Already up to date: nothing to merge
S05B | Fast-forward-only refuses divergence
S06 | Squash merge: one delivery commit
S07 | Rebase, then fast-forward
S08 | Cherry-pick: bring across one change
S09 | Merge main into a feature branch
S10 | Octopus: merge several independent topics
S11 | Merge histories with no common ancestor
S12 | Prefer a side for conflicting hunks: -X
S13 | Record ancestry but keep our whole tree
S14 | Subtree-aware merge: align directory layouts
S15 | Interactive rebase: polish before integration
R01 | Undo a published merge with revert
Appendix B. Official references and verification
Documentation checked on 19 September 2026. Git commands are supported by Git’s manuals and Pro Git; hosting-specific claims use GitHub’s documentation. Workflow guidance additionally uses DORA and the original Gitflow author. All titles below are clickable.
[1] Pro Git: Branches in a Nutshell
[2] Git glossary
[3] Pro Git: Basic Branching and Merging
Fast-forward and three-way examples
[4] git merge
Modes, conflicts and merge state
[5] git rebase
Replay, interactive editing and conflict handling
[6] git branch
Local refs, tracking and deletion
[7] git cherry-pick
Selective replay and provenance
[8] Merge strategies
ort, recursive, resolve, octopus, ours and subtree
[9] Remote synchronization
git fetch | git push and force-with-lease
[10] git switch
Branch creation, detached HEAD and orphan starts
[11] git merge-base
Common ancestors and ancestry predicates
[12] Git revisions
[13] git restore
Index stages, ours/theirs and restoring paths
[14] git revert
Mainline selection and reverted-merge behavior
[15] git pull
[16] git diff
Tip comparison, three-dot comparison and checks
[17] git reset
Reset modes and recovery consequences
[18] git reflog
Local reference history and expiration
[19] git worktree
[20] GitHub: Protected branches
Review and status-check restrictions
[21] GitHub: Pull request merges
Merge, squash and rebase behavior
[22] GitHub flow
Branch, review and integration lifecycle
[23] DORA: Trunk-based development
Short-lived work and continuous integration
[24] Vincent Driessen: A successful Git branching model
Gitflow and its later applicability note
[25] git rerere
Recorded conflict resolution reuse
[26] git stash
Saving and restoring work in progress
[27] Mermaid: Flowcharts
[28] git tag
Annotated tags and release references
[29] Pro Git: Rebasing
Replayed commits and publication caution
[30] Pro Git: Advanced Merging
Conflict inspection and exceptional merges
[31] Git attributes
Normalization and content merge behavior
[32] Pro Git: Remote Branches
Tracking refs and synchronization
Executable verification
The 17 scenario and recovery recipes were executed in isolated disposable repositories with Git 2.47.3. Checks covered branch tips, parent counts, intended refusals, resolved file contents, squash and cherry-pick ancestry, rewritten commits, three-parent octopus history, separate roots, both -X variants, unchanged ours-strategy trees, subtree paths, and revert behavior. Interactive rebase was checked with the specified pick/fixup todo list.
The lab tests validate Git mechanics, not application correctness. Run project-specific tests and honor repository policy before integrating real work. Statements about newer Git behavior, such as the recursive alias beginning in 2.50, come from the current official manuals.
One durable habit. Before every integration, know the target, the source, the intended history shape, and the recovery path. After it, verify both the graph and the behavior.