Rebasing and Resolving Conflicts
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.
- Create a repository at
/root/gitx3/repo. Stack 3 commits onmain, create atopicbranch from there and add 2 commits, then return tomainand stack 1 more commit to create a diverged state (topichas at least 4 commits in total). Then in/root/gitx3/diverged.txtwrite three lines:main_ahead=1,topic_ahead=2,merge_base=<두 브랜치의 공통 조상 해시>(the placeholder stands for the hash of the common ancestor of the two branches). - Rebase
topicontomain. Before that, record the tip hash oftopic, and in/root/gitx3/rebased.txtwrite two lines:before=<리베이스 전 해시>andafter=<리베이스 후 해시>(the placeholders stand for the hashes before and after the rebase). The two values must differ,aftermust match the current tip oftopic, and the rebase must not be left in progress. - On
main, mergetopicby fast-forward. Result: 0 merge commits onmain, 6 or more total commits onmain, andmainandtopicmust point to the same commit. - Create a new repository at
/root/gitx3/conflict. Onmain, putshared.txt, and on aconflict-topicbranch modify that file and commit so that the stringfrom-topicis in it. Then onmaintoo, modify the same spot and commit so thatfrom-mainis in it. Now if you rebaseconflict-topicontomain, 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.txtmust contain bothfrom-mainandfrom-topic, and the working tree must be clean. - Create a new repository at
/root/gitx3/abortand commit so that ariskybranch andmainmodify the same file differently. Before starting the rebase, save the tip hash ofriskyto/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 ofriskymust be exactly the same as the recorded hash, and there must be a rebase record in the reflog. - Create a new repository at
/root/gitx3/squashand stack 3 commits on afeature/threebranch (addings1.txt,s2.txtands3.txtrespectively). Combine the 3 into 1 with an interactive rebase. Completion conditions: the commit count ofmain..feature/threeis 1, that commit's subject includesfeat: combined, and all three ofs1.txt/s2.txt/s3.txtexist. - Create a new repository at
/root/gitx3/dropand stack 4 commits on afeature/fourbranch:d1.txt,d2.txt, then a commit whose subject isdebug: temp logand which addsdebug.txt, and finallyd3.txt. Discard only the debug commit with an interactive rebase. Completion conditions:main..feature/fourhas 3 commits, nodebug: temp logcommit, nodebug.txt, and all ofd1/d2/d3.txtexist. - Write
/root/gitx3/rebase.md. It must include: an explanation of--force-with-leaseand 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 themerge_commits_on_main=line, actually count and write the merge commit count for/root/gitx3/repo, branchmain.
Notes
- Counting how far ahead:
git rev-list --count topic..main(how far main is ahead),git rev-list --count main..topic(how far topic is ahead), and the common ancestor isgit merge-base main topic. - Interactive rebase in an environment without an editor: you can work around it with
GIT_SEQUENCE_EDITOR='sed -i "2,3s/^pick/fixup/"' git rebase -i mainfor the list, andGIT_EDITOR=truefor editing the commit message. You can also combine with fixup instead of squash and then set the subject withgit commit --amend -m "feat: combined". - For drop, change the
pickof that line in the list todrop, or delete the line. - After resolving a conflict, do
git add <파일>and thengit rebase --continue(the placeholder stands for the file). If an editor appears, putGIT_EDITOR=truein front. - Common mistake 1: resolving a conflict by picking one side with
--oursor--theirs. The other side's work silently disappears. Moreover, during a rebase the side of the commit being replayed is theirs, so the meaning feels opposite to intuition. - Common mistake 2: trying to find the
beforehash after doing the rebase first in step 2. After a rebase you have to look atgit reflog, so savegit rev-parse topicfirst before starting.
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.