TT Lab
Get started
Learn Learning paths Courses

Git in Practice

Rebasing and Resolving Conflicts

Continue in TT Lab

Goal

You confirm with hashes that a rebase rewrites rather than moves commits, and you do by hand conflict resolution, aborting, and even squash/drop in an interactive rebase. Put the deliverables under /root/gitx3/.

Why it matters

A commit object contains its parent hash. If the parent changes, the commit hash necessarily changes. Everything else follows from this one sentence. If someone else has stacked work on top of that commit, rewriting breaks that person's history. So the precise form of the golden rule is not "do not do it if you have pushed" but "do not rewrite a commit that has other people's work stacked on top of it". The reason conflicts repeat also comes from the same structure. A merge compares only three points, but a rebase re-applies commits one by one and does a three-way merge each time, so a rebase is not one merge but N merges.

Steps

There is no global git identity. Set a local user.email / user.name for each new repository.

  1. Create a repository at /root/gitx3/repo. Stack 3 commits on main, create a topic branch from there and add 2 commits, then return to main and stack 1 more commit to create a diverged state (topic has at least 4 commits in total). Then in /root/gitx3/diverged.txt write three lines: main_ahead=1, topic_ahead=2, merge_base=<두 브랜치의 공통 조상 해시> (the placeholder stands for the hash of the common ancestor of the two branches).
  2. Rebase topic onto main. Before that, record the tip hash of topic, and in /root/gitx3/rebased.txt write two lines: before=<리베이스 전 해시> and after=<리베이스 후 해시> (the placeholders stand for the hashes before and after the rebase). The two values must differ, after must match the current tip of topic, and the rebase must not be left in progress.
  3. On main, merge topic by fast-forward. Result: 0 merge commits on main, 6 or more total commits on main, and main and topic must point to the same commit.
  4. Create a new repository at /root/gitx3/conflict. On main, put shared.txt, and on a conflict-topic branch modify that file and commit so that the string from-topic is in it. Then on main too, modify the same spot and commit so that from-main is in it. Now if you rebase conflict-topic onto main, a conflict occurs. Resolve it so that both changes are kept. Completion conditions: the rebase must be finished, there must be no unresolved paths in the index, no conflict markers such as <<<<<<< may remain in the file, shared.txt must contain both from-main and from-topic, and the working tree must be clean.
  5. Create a new repository at /root/gitx3/abort and commit so that a risky branch and main modify the same file differently. Before starting the rebase, save the tip hash of risky to /root/gitx3/abort-before.txt (the hash string only). Then start the rebase, hit the conflict and abort. Completion conditions: no rebase in progress, the tip of risky must be exactly the same as the recorded hash, and there must be a rebase record in the reflog.
  6. Create a new repository at /root/gitx3/squash and stack 3 commits on a feature/three branch (adding s1.txt, s2.txt and s3.txt respectively). Combine the 3 into 1 with an interactive rebase. Completion conditions: the commit count of main..feature/three is 1, that commit's subject includes feat: combined, and all three of s1.txt/s2.txt/s3.txt exist.
  7. Create a new repository at /root/gitx3/drop and stack 4 commits on a feature/four branch: d1.txt, d2.txt, then a commit whose subject is debug: temp log and which adds debug.txt, and finally d3.txt. Discard only the debug commit with an interactive rebase. Completion conditions: main..feature/four has 3 commits, no debug: temp log commit, no debug.txt, and all of d1/d2/d3.txt exist.
  8. Write /root/gitx3/rebase.md. It must include: an explanation of --force-with-lease and how it differs from --force, when not to rebase (include one of the expressions shared, pushed or other people), why the hash changes (include one of the expressions hash or rewriting), and in the merge_commits_on_main= line, actually count and write the merge commit count for /root/gitx3/repo, branch main.

Notes

Create two diverged branches

Create a repository at /root/gitx3/repo. Stack 3 commits on main, create a topic branch from there and add 2 commits, then return to main and stack 1 more commit to create a diverged state (topic has at least 4 commits in total). Then in /root/gitx3/diverged.txt write three lines: main_ahead=1, topic_ahead=2, merge_base=<두 브랜치의 공통 조상 해시> (the placeholder stands for the hash of the common ancestor of the two branches).

Make main and topic in /root/gitx3/repo have different commits from each other. Only when each side has its own commits after the branch point is the state 'diverged'. You can count how far ahead with git rev-list --count A..B.

Rewrite topic on top of main

Rebase topic onto main. Before that, record the tip hash of topic, and in /root/gitx3/rebased.txt write two lines: before=<리베이스 전 해시> and after=<리베이스 후 해시> (the placeholders stand for the hashes before and after the rebase). The two values must differ, after must match the current tip of topic, and the rebase must not be left in progress.

You must record the tip hash of topic before the rebase so that you can compare. When the rebase finishes, the common ancestor becomes the same as the tip of main, and since the parent changed, the hash changes too.

Make a straight line by fast-forward

On main, merge topic by fast-forward. Result: 0 merge commits on main, 6 or more total commits on main, and main and topic must point to the same commit.

After the rebase, main is an ancestor of topic, so it merges without a merge commit. If you use --ff-only, it refuses when the condition does not hold, which is safe.

Resolve a conflict preserving both sides

Create a new repository at /root/gitx3/conflict. On main, put shared.txt, and on a conflict-topic branch modify that file and commit so that the string from-topic is in it. Then on main too, modify the same spot and commit so that from-main is in it. Now if you rebase conflict-topic onto main, a conflict occurs. Resolve it so that both changes are kept. Completion conditions: the rebase must be finished, there must be no unresolved paths in the index, no conflict markers such as <<<<<<< may remain in the file, shared.txt must contain both from-main and from-topic, and the working tree must be clean.

In /root/gitx3/conflict, have both sides modify the same spot of the same file and then rebase. Resolving is not picking one side but keeping both changes, and you are done only when the conflict markers are gone too.

Abort the rebase and restore the original state

Create a new repository at /root/gitx3/abort and commit so that a risky branch and main modify the same file differently. Before starting the rebase, save the tip hash of risky to /root/gitx3/abort-before.txt (the hash string only). Then start the rebase, hit the conflict and abort. Completion conditions: no rebase in progress, the tip of risky must be exactly the same as the recorded hash, and there must be a rebase record in the reflog.

In /root/gitx3/abort, write the pre-start branch tip hash to a file, start a rebase that conflicts, and then abort. Aborting restores exactly the state before the start.

Combine 3 commits into 1

Create a new repository at /root/gitx3/squash and stack 3 commits on a feature/three branch (adding s1.txt, s2.txt and s3.txt respectively). Combine the 3 into 1 with an interactive rebase. Completion conditions: the commit count of main..feature/three is 1, that commit's subject includes feat: combined, and all three of s1.txt/s2.txt/s3.txt exist.

Interactively rebase feature/three in /root/gitx3/squash relative to main. Even when combined, the changes are not discarded, so all three files must remain. If launching an editor is hard, you can change the list with GIT_SEQUENCE_EDITOR.

Discard one commit

Create a new repository at /root/gitx3/drop and stack 4 commits on a feature/four branch: d1.txt, d2.txt, then a commit whose subject is debug: temp log and which adds debug.txt, and finally d3.txt. Discard only the debug commit with an interactive rebase. Completion conditions: main..feature/four has 3 commits, no debug: temp log commit, no debug.txt, and all of d1/d2/d3.txt exist.

In feature/four of /root/gitx3/drop, remove only the debug commit. If you drop a commit, the file that commit added disappears with it. The files of the other three commits must remain.

Summarize the golden rule

Write /root/gitx3/rebase.md. It must include: an explanation of --force-with-lease and how it differs from --force, when not to rebase (include one of the expressions shared, pushed or other people), why the hash changes (include one of the expressions hash or rewriting), and in the merge_commits_on_main= line, actually count and write the merge commit count for /root/gitx3/repo, branch main.

In /root/gitx3/rebase.md, write the difference between --force-with-lease and --force, when not to rebase, and why the hash changes, and in the merge_commits_on_main= line, actually count and write the result of step 3.