Skip to content
GitTree

Lesson 21 · History and integration

Merge versus rebase: choose with a graph

Run merge and rebase from two identical copies of the same divergence, compare the two graphs, the parents and the file trees, and learn the one rule that decides between them: who else already has your commits.

Progress saved in this browser.

Playing loads the video from YouTube (privacy-enhanced mode).

English narration · English and Arabic captions

Open the video pageWatch on YouTube

What you will learn

  • Start merge and rebase from identical copies, so the comparison is fair.
  • Read a merge commit's two parents and a rebased commit's single new parent.
  • Explain why two copies can end with the same files and still have different histories.
  • Choose with one rule: rewrite only commits that nobody else has.

Before you start

You need two separate copies of the divergence from lesson 20, named gittree-merge and gittree-rebase. In each copy the branch feature/signup-form holds one commit of yours (call it S), the branch updated-main holds one different commit (call it N), both started from the same commit M, and rescue/signup-before-rebase points at S. The topic branch is checked out and the work tree is clean. Do not make the second copy from the result of the first.

Everything here is local and on the Free plan (GitTree asks you to sign in): no hosting account, no provider, no internet. Every identity is fictional (example.com), and you should never run this lesson inside a real project.

Make the changes in GitTree or with the native Git commands, not both in the same copy. Reading the result afterwards is always safe. The menu labels come from GitTree 2.0's own strings; the native commands were run with Git 2.43 in disposable copies. The GitTree steps were not run on macOS, Windows or Linux; the app's shortcuts use Command on macOS where this page writes Ctrl.

Concept and state effects

A comparison is only fair when both copies start from the same refs and the same files. If you merge first and then rebase that result, you are asking a different question: the replay now has a merge commit in its range.

Merge keeps both lines of work. It adds one new commit, K, with two parents: S first, then N. S and N keep their IDs.

Rebase replays your commit on top of N. The result, S′, has the same change as S but a new parent, and a commit's ID includes its parent, so S′ has a new ID. The old S survives only because the rescue branch still points at it.

When nothing conflicts, both copies end with the same file tree. Equal trees do not mean equal history: one graph keeps a diamond, the other a straight line, and the commit IDs differ.

Which one is right is not a matter of taste. If other people already have your commits, replaying them breaks their copies, so merge. If the commits are private, a rebase is safe and gives a linear history if your team prefers one. Neither makes the code better.

In each copy only the topic branch moves. The server, updated-main and the original main are not touched.

Prepare the exact project state

Make one divergence, then copy it twice. Start from a separate ordinary copy of the project as lesson 18 left it (main is the merge commit M). Leave your original gittree-features untouched, and never run this in a source checkout. The demo identity is GitTree Demo, demo@example.com, with signing turned off in this repository only.

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.

index.html — SIGNUP

File excerpt · html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GitTree Features — course demo</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <header class="site-header">
    <a href="#main">GitTree Features</a>
  </header>
  <main id="main" tabindex="-1">
    <section class="hero" aria-labelledby="hero-title">
      <p class="eyebrow">GitTree Learn demo project</p>
      <h1 id="hero-title">See your work clearly.</h1>
      <p id="hero-copy">Choose one change, then check what Git saved.</p>
    </section>
    <section class="features" aria-labelledby="features-title">
      <h2 id="features-title">Follow your work</h2>
      <div class="cards">
        <article class="feature-card">
          <h3>History</h3>
          <p>Read commits and branches together.</p>
        </article>
        <article class="feature-card">
          <h3>Changes</h3>
          <p>Review the exact content selected for a commit.</p>
        </article>
        <article class="feature-card">
          <h3>Recovery</h3>
          <p>Inspect the scope of a recovery point before using it.</p>
        </article>
      </div>
    </section>
    <section aria-labelledby="plans-title">
      <h2 id="plans-title">Choose the workflow you need</h2>
      <div class="cards">
        <article class="feature-card">
          <h3>Local Git</h3>
          <p>Local Git workflows work without signing in.</p>
        </article>
        <article class="feature-card">
          <h3>Connected workflows</h3>
          <p>Provider integrations and Nour require Pro or an applicable trial.</p>
        </article>
      </div>
    </section>
    <section aria-labelledby="signup-title">
      <h2 id="signup-title">Try the demo form</h2>
      <form id="signup-form" novalidate>
        <label for="demo-email">Demo email</label>
        <input id="demo-email" name="email" type="email" autocomplete="off" aria-describedby="signup-hint">
        <p id="signup-hint">Use a fictional example.com address. Nothing is sent.</p>
        <button type="submit">Check the address locally</button>
        <p id="signup-result" role="status"></p>
      </form>
    </section>
  </main>
  <footer><p>Built by a fictional team for GitTree Learn. Review one change at a time.</p></footer>
  <script type="module" src="site.js"></script>
</body>
</html>

docs/integration-note.md — DOC_MERGE

Output
# Integration review

This note was written on main while signup work was private.

In GitTree

Open the palette with Command+Shift+P (Ctrl+Shift+P on Ubuntu), type the exact command name and choose it. Actions on a named branch use that branch's right-click menu.

  1. Open gittree-merge. Then choose New Tab and open gittree-rebase from its Recent list. Each tab is titled with its folder name, so you can tell the two apart.
  2. In the gittree-merge tab keep feature/signup-form checked out. Right-click updated-main and choose Merge updated-main into feature/signup-form.
  3. Under Record it as choose Always a merge commit, then choose Merge once. The new commit has two parents: S first, then N.
  4. Switch to the gittree-rebase tab. It is still at S and N, exactly as you saved it. Do not use the merge copy.
  5. Right-click updated-main and choose Rebase feature/signup-form onto updated-main. Read Options for this rebase and leave both options off, then choose Rebase feature/signup-form once.
  6. Select the replayed commit. Its parent is N and its ID is new. The old S is still named by rescue/signup-before-rebase.
  7. In each tab open Show Files → index.html and docs/integration-note.md. They are the same page and the same note in both copies. Then return with Back to Graph.
  8. Compare the two graphs, then say which rule decides the choice for this branch before you pick a result. Keep both copies as they are.

For every ordinary commit in the course: Summary is the stated subject, Description is empty and the options Amend Previous Commit, Skip Hooks, Add Signed-off-by, Different Author, Push After Commit and Allow Empty Commit stay off. This lesson makes no ordinary commit.

In the terminal

Build the divergence in a separate clean copy of lesson 18's result (the work you did in the app, written as native commands):

Command · bash
git switch --create feature/signup-form

Save the full SIGNUP page as index.html, then:

Command · bash
git add -- index.html
git commit --quiet -m "feat: add the local signup demonstration"
git branch rescue/signup-before-rebase
BASE_SHA="$(git rev-parse HEAD~1)"
git switch --quiet --create updated-main "$BASE_SHA"

On updated-main save the full DOC_MERGE note as docs/integration-note.md, then:

Command · bash
git add -- docs/integration-note.md
git commit --quiet -m "docs: add the integration review"
git switch --quiet feature/signup-form

BASE_SHA is the commit M that both lines grow from. Now make the two independent copies from this exact state (cd .. && cp -R gittree-features gittree-merge && cp -R gittree-features gittree-rebase). In the merge copy:

Command · bash
git merge --quiet --no-ff --no-edit updated-main
git rev-list --parents -n 1 HEAD
git rev-parse 'HEAD^{tree}'
git status --short

In the untouched rebase copy:

Command · bash
git rebase --quiet --no-autostash --no-update-refs --no-autosquash updated-main
git rev-list --parents -n 1 HEAD
git rev-parse 'HEAD^{tree}'
git status --short

git rev-list --parents -n 1 HEAD prints the commit's ID followed by its parents: three IDs in the merge copy, two in the rebase copy. The two HEAD^{tree} lines are the same ID, and git status --short prints nothing in both. The two HEAD commit IDs are different. Your IDs will differ from anyone else's; compare yours with each other, not with this page.

If the topic had no commit of its own and updated-main were simply ahead of it, git merge would fast-forward by default and create no merge commit. That is a different input and a different result; choose it on purpose, never fake a two-parent merge.

Result

Where you end: the merge copy has a new commit K with parents S and N; the rebase copy has S′ with parent N, and the old S is kept by the rescue branch. The file trees are equal, both statuses are clean, and the original main, M and the server are untouched.

Common mistakes

  • Assuming a straight graph means the old commit ID survived. A rebase always creates new commits.
  • Starting the rebase from the merge result. That is a different experiment, and the replay can include the merge commit.
  • Comparing the two HEAD IDs and expecting them to agree. Compare the trees instead.
  • Repeating the merge or the rebase after it has already finished.

If something goes wrong

Do not discard either result to force them to match. If the trees differ, compare the starting refs, any conflict resolution and any extra commit, then start again from the saved divergence. A rescue branch keeps committed history only; it does not keep uncommitted files.

Check your understanding

Answer each question, then read why. Questions marked “Required” count toward completing the lesson.

  1. Question 1 of 6

    From the same start, you merge updated-main into feature/signup-form in one copy and rebase feature/signup-form onto updated-main in another. How many parents does the topic's new tip commit have in each copy?

    Choose one answer.

  2. Question 2 of 6

    Which of these are the same in both copies once the merge and the rebase have finished without conflicts?

    Choose every answer that applies.

  3. Question 3 of 6
    Required

    After the rebase, what happened to your original topic commit S?

    Choose one answer.

  4. Question 4 of 6

    Change the input: your topic has no commit of its own, and updated-main is simply ahead of it. You merge with the default mode, "Fast-forward if possible, else a merge commit". What happens?

    Choose one answer.

  5. Question 5 of 6
    Required

    A colleague already pulled feature/signup-form and is building on it. You want main history to stay tidy. What is the safe choice?

    Choose one answer.

  6. Question 6 of 6
    Required

    Which readbacks show the comparison was fair and show how it ended?

    Choose every answer that applies.

0 of 6 correct

Answer the required questions correctly to complete the lesson.

Checked in this browser.

Practice

Report the two parent lists, the two HEAD IDs and the two tree IDs. Explain why equal tree IDs next to different HEAD IDs is the expected result. Then predict, without doing it, what a default merge does when the topic has no commit of its own.

Your own confirmation: the site cannot see your computer.

To complete the lesson, answer the required questions correctly and confirm the practical exercise.

Recap

  • Same start, different history.
  • Equal trees do not mean equal history.
  • Merge when others have your commits. Rebase only private ones.

Glossary terms introduced

Rebase — إعادة تأسيس; replay — إعادة تطبيق; rescue branch — فرع حفظ.

Video transcript

The narration of the video, cue by cue.

Show the transcript
  1. Merge or rebase? The same work can leave two different histories.
  2. We will run both from the same starting point and compare.
  3. Your branch has one commit. The updated branch has another.
  4. Both grew from the same commit, so neither contains the other.
  5. First, prepare the divergence and make two identical copies.
  6. A fair test needs the same refs and files in both copies.
  7. Here is the first copy. We will merge in this one.
  8. And this is the second copy. We keep it untouched for the rebase.
  9. Right-click updated-main and choose its Merge command.
  10. Under Record it as, choose Always a merge commit.
  11. Choose Merge once, and wait for the real result.
  12. The new commit has two parents: your commit, then updated-main's.
  13. Now the second copy. It still has the two original commits.
  14. Right-click updated-main again, and choose Rebase this time.
  15. The dialog names the branch and the target. Leave both options off.
  16. Rebase once. Git replays your commit on top of updated-main.
  17. The replayed commit has a new parent, so it gets a new ID.
  18. The old commit is not lost: the rescue branch still points to it.
  19. Now compare the files. The page is the same in both copies.
  20. So is the integration note. Same files, different history.
  21. Look at the two graphs: a diamond with a merge commit, and a straight line.
  22. So which one should you use? Ask who else has your commits.
  23. If others have them, merge. A rebase would rewrite their history.
  24. If the commits are private, rebase is safe, and gives a straight line.
  25. Neither makes your code better. The rule is about ownership.
  26. Now prove it in the terminal. First, the parents of each tip.
  27. Three IDs for the merge copy: the commit, and its two parents.
  28. Two IDs for the rebase copy: the commit, and its one parent.
  29. Then the file trees: one ID, printed twice. Same files.
  30. But the two commit IDs differ. Equal trees, different histories.
  31. Change the input: suppose your branch had no commit of its own.
  32. A default merge would then fast-forward, with no merge commit.
  33. Never rebase a branch that someone else already has.
  34. If the trees differ, compare the starting refs first.
  35. Do not delete a result to force the two to match.
  36. Practice: report the parents, the IDs and the trees from your own copies.
  37. Next, reorder, squash and reword your local commits.