Lesson 19 · History and integration
Understand fast-forward merges
Move a target branch to an existing descendant dark-palette commit without creating another commit. Contrast that fast-forward with a diverged input that must refuse.
- Level: Intermediate
- 7 min read
- 5 min video
- Updated
- Before this lesson: Merge branches with diverged history
Progress saved in this browser.
Playing loads the video from YouTube (privacy-enhanced mode).
What you will learn
- Recognize the ancestor requirement.
- Prove identical tip IDs after fast-forward.
- Separate ref movement from commit creation.
- Predict refusal on divergence.
Before you start
Start from a separate copy of lesson 18's result (main is the merge, clean): create scenario/ff-target at that commit, then feature/dark-mode at the same commit; the dark palette gets one commit D on the feature. Return to scenario/ff-target before merging.
The video was recorded with Git 2.52.0 and GitTree 2.0 on Ubuntu 24.04: the dark palette, its commit, the checkout and both merges were done in the app; the two scenario branches and the checks ran in the terminal, and the refusal was shown in the app and again in the terminal. Nothing was run on macOS or Windows, and Ubuntu is the only Linux system recorded. GitTree's source Git floor is 2.39.0.
GitTree asks you to sign in; the Free plan then covers every step here. No provider account, Pro/trial, API credential or internet service is needed. A local bare origin is not GitLab or CI. All identities use example.com; never run this lesson inside a source checkout.
Make mutations through either GitTree or the native path at identical inputs, not both consecutively. Read-only verification can follow either. Any preparation described as terminal work is labeled native preparation, never an invented app feature.
Concept and state effects
A fast-forward is possible when the target tip is an ancestor of the incoming tip. Moving the target label to the descendant incorporates the intervening snapshots without inventing a merge commit.
The resulting tip has D's existing identity and parent. The earlier M remains a two-parent merge from lesson 18; do not count its parents and mistake them for a new merge created by this lesson.
Fast-forward only is a policy gate, not a repair for all branches. If a separate target commit exists, both tips have unique history and this mode refuses.
Only the target branch/HEAD/index/work advance M→D. Feature stays D, no new commit is created by merge, server unchanged.
Prepare the exact project state
Begin from a separate ordinary copy of the project as lesson 18 left it. The original gittree-features stays preserved; do not run this scenario in the original or a source checkout. (An earlier plan of the course named a different starting checkpoint; the video uses lesson 18's result and the same steps.) The stylesheet below is the project's styles.css with one dark palette block added. Repo-local demo signing is off and GitTree Demo <demo@example.com> is the identity.
Complete file inputs
Save only at the phase named below. These are full UTF-8/LF bodies with a final newline; never paste Markdown fences. Unchanged runnable files inherit the named input. Versions in separate variants must not overwrite the original.
styles.css — DARK
:root {
color-scheme: light;
--page: #f7f8fa;
--surface: #ffffff;
--text: #17212e;
--link: #154ab5;
--line: #bac4d0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--page);
color: var(--text);
font: 1rem/1.6 system-ui, sans-serif;
}
a {
color: var(--link);
}
.site-header,
main,
footer {
width: min(68rem, 100% - 2rem);
margin-inline: auto;
}
.site-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
padding-block: 1rem;
}
.hero {
max-width: 48rem;
padding-block: 3rem;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.2;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
gap: 1rem;
}
.feature-card {
padding: 1rem;
border: 1px solid var(--line);
border-radius: 0.75rem;
background: var(--surface);
}
section {
margin-block-end: 2rem;
}
button,
input {
font: inherit;
}
button,
input {
padding: 0.5rem 0.75rem;
}
:focus-visible {
outline: 3px solid var(--link);
outline-offset: 3px;
}
form {
display: grid;
max-width: 28rem;
gap: 0.5rem;
}
:root[data-theme="dark"] {
color-scheme: dark;
--page: #121923;
--surface: #1b2635;
--text: #edf2f8;
--link: #a4c6ff;
--line: #7388a1;
}In GitTree
Palette: Command+Shift+P (Ctrl+Shift+P on Ubuntu); type the exact named command and select it. Named-ref actions use that row's context menu, not an argument-free palette shortcut.
- Prepare scenario/ff-target and feature/dark-mode in the separate copy as below; save full DARK on the feature.
- Commit only styles.css as feat: add the dark palette; verify feature D.
- Under Local open the scenario folder, right-click ff-target → Checkout scenario/ff-target; verify it is still at M and clean.
- Under Local open the feature folder, right-click dark-mode → Merge feature/dark-mode into scenario/ff-target; review source and target in the dialog.
- In Record it as choose Fast-forward only; do not select Always a merge commit.
- Choose Merge once and wait. Expect target label to reach the existing D.
- Compare actual full SHAs of target and feature, then inspect DARK and clean work.
- In a separate diverged copy (open it from New Tab and Recent repositories) repeat the merge with Fast-forward only: the app refuses and says the target has changes you do not have yet. Leave the canonical original untouched.
For every ordinary commit: Summary is the stated subject; Description empty; Amend Previous Commit, Skip Hooks, Add Signed-off-by, Different Author, Push After Commit and Allow Empty Commit off; no co-authors. Stage only the named files, then Commit Changes to N Files once (File for one), or Command+Enter as an alternative. Wait for the real result. A lesson that explicitly amends changes only that stated option.
In the terminal
In the separate copy of lesson 18's result:
git switch --create scenario/ff-target
git switch --create feature/dark-modePut the full DARK stylesheet over styles.css, then:
git add -- styles.css
git commit --quiet -m "feat: add the dark palette"
git switch --quiet scenario/ff-target
git merge --quiet --ff-only feature/dark-mode
git rev-parse HEAD feature/dark-mode
git log -1 --format=%s
git status --shortThe SHA query gives two identical actual IDs. Observed subject:
feat: add the dark paletteStatus empty. The fixture asserted that target equals the previously measured D, not a newly created merge node. For changed input, add an independent docs/integration-note.md commit to target in another copy before merging; ff-only refuses, preserving both branches. Do not select a different mode silently to make the policy check green.
Result
Where you end: scenario/ff-target and feature/dark-mode name the same existing D. Target HEAD/index/work include DARK, no new merge node, original main/M unchanged.
Common mistakes
- Calling any graphical line join a new merge commit.
- Treating the alternative pane as the original checkpoint.
- Repeating a completed mutation after app success.
If something goes wrong
If refused, inspect both histories and choose a separate merge/rebase workflow only after review. The refusal itself changes no target files or refs.
Check your understanding
Answer each question, then read why. Questions marked “Required” count toward completing the lesson.
0 of 6 correct
Answer the required questions correctly to complete the lesson.
Checked in this browser.
Practice
Prove target tip equals existing D and work matches full DARK. Predict refusal after an independent target commit in a separate copy; compare old refs after refusal.
Your own confirmation: the site cannot see your computer.
To complete the lesson, answer the required questions correctly and confirm the practical exercise.
Recap
- Ancestor relation permits fast-forward.
- The incoming commit keeps its ID.
- ff-only refuses divergence.
Related
Glossary terms introduced
Fast-forward — تقديم سريع; descendant — تابع; ancestor — سلف.
Video transcript
The narration of the video, cue by cue.
Show the transcript
- Start from the exact checkpoint.
- Name the branch, index and working version.
- Prepare only the isolated course copy.
- Start both branch names at the same commit.
- Record the real palette change.
- Return to the unchanged target.
- Confirm the merge direction.
- Require fast-forward only explicitly.
- Move the label to the existing descendant.
- Verify identities, not only graph shape.
- Preserve a diverged target instead of forcing it.
- What is required?
- Ancestry, not names or colors, determines fast-forward.
- What changes?
- The target moves to an existing D without a push.
- Resulting target SHA?
- The measured target equals the previous feature tip.
- Independent target commit added: ff-only?
- Divergence violates the required ancestry gate.
- Old M has two parents: proof this action merged?
- M's existing ancestry is not a new commit from this action.
- What proves completion?
- Both ref identity and file state are required.
- Verify the actual result, not just a message.
- Predict the changed input before acting.
- Preserve useful work before recovery.
- Prove the checkpoint before continuing.
- Keep the verified result for the next step.