Mental Model
A Gerrit review is a commit, not a branch.
main
└─ A ← Review A
└─ B ← Review B
└─ C ← Review C
└─ D ← Review DEach commit has its own Change-Id.
Inspect History
git log --oneline --graph --decorate --allExample:
* dddddd (HEAD -> feature) Review D
* cccccc Review C
* bbbbbb Review B
* aaaaaa Review A
* 111111 (origin/main, main)Update the Top Review
Current:
A
└─ B
└─ C
└─ DNeed to fix Review D:
# edit files
git add .
git commit --amend
git push origin HEAD:refs/for/mainNo rebase required.
Update a Non-Top Review
Current:
A
└─ B
└─ C
└─ DNeed to fix Review B:
git rebase -i mainChange:
pick A
pick B
pick C
pick Dto:
pick A
edit B
pick C
pick DSave and quit.
Git stops at B.
Fix and amend:
# edit files
git add .
git commit --amendContinue:
git rebase --continuePush:
git push origin HEAD:refs/for/mainResult:
A
└─ B'
└─ C'
└─ D'Rebase Commands
Modify Commit
pick → editExample:
pick A
edit B
pick CReorder Commits
Move lines.
Before:
pick A
pick B
pick CAfter:
pick A
pick C
pick BUse carefully.
Squash Commits
Before:
pick A
pick BAfter:
pick A
squash BResult:
A+Bsingle commit.
Remove Commit
Before:
pick A
pick B
pick CAfter:
pick A
drop B
pick Cor delete the line.
How Much Gets Rewritten?
Stack:
A
└─ B
└─ C
└─ D| Edited Commit | Rewritten |
|---|---|
| D | D |
| C | C, D |
| B | B, C, D |
| A | A, B, C, D |
Rule:
Editing a commit rewrites that commit and everything above it.
Gerrit Change-Id Rule
Always keep:
Change-Id: Ixxxxxxxxinside commit messages.
Good:
git commit --amendand keep existing Change-Id.
Bad:
Delete Change-IdGerrit creates a new review.
Common Gerrit Workflow
Create Review A
git checkout -b feature
# changes
git commit
git push origin HEAD:refs/for/mainCreate Review B on top of A
# more changes
git commit
git push origin HEAD:refs/for/mainResult:
Review B
depends on
Review AFix Review A Later
git rebase -i mainedit A
pick Bgit commit --amend
git rebase --continue
git push origin HEAD:refs/for/mainUseful Aliases
git config --global alias.lg \
"log --oneline --graph --decorate --all"Usage:
git lgQuick Memory Trick
Think of the stack as a tower:
D
C
B
AIf you modify a block:
-
Everything above it must be rebuilt.
-
Everything below it remains unchanged.
Moving Changes Between Commits
Scenario
Current stack:
main
└─ A (Review A)
└─ B (Review B)Reviewer says:
Change X currently in B should belong to A.
Example:
A = Add API
B = Use API
+ change X in file_x.cppReviewer wants:
A = Add API
+ change X
B = Use APICommon Confusion
You are on branch:
featureX
↓
main
└─ A
└─ BWhen editing A:
git rebase -i mainedit A
pick Byou still see change X in your files.
This is normal.
Reason:
The working tree always contains:
A + Bbecause your branch points to the top of the stack.
Important Mental Model
A commit is a diff, not a snapshot.
Current files:
Working Tree = A + BWhat matters is:
git show A
git show Bwhich tells Git:
What changes belong to A?
What changes belong to B?Inspect Commit Contents
Show commit A:
git show AShow commit B:
git show BShow only file_x changes in B:
git show B -- file_x.cppCompare commit against its parent:
git diff B^..BCorrect Procedure
Step 1 - Start Interactive Rebase
git rebase -i mainBefore:
pick A
pick BAfter:
edit A
edit BStep 2 - Move Change X into A
Git stops at A.
Apply change X to A.
Example:
git add file_x.cpp
git commit --amendNow:
A' = A + change XContinue:
git rebase --continueStep 3 - Remove Change X from B
Git stops at B.
Remove the lines that were moved into A.
Amend:
git add .
git commit --amendContinue:
git rebase --continueResult:
main
└─ A'
└─ B'Where:
A' = old A + change X
B' = old B - change XAlternative: Conflict During Replay
Sometimes:
edit A
pick Bis enough.
After amending A:
A'Git replays B.
Because B still contains change X:
Conflictmay occur.
Resolve by:
Keep change X in A
Remove change X from BThen:
git add .
git rebase --continueWhy Cherry-Picking A Worked
You created:
new_branch
└─ Awithout B.
Therefore:
Working Tree = A onlywhich made the change easier to reason about.
This is valid but not required.
Interactive rebase can perform the same surgery inside the stack.
Quick Rule
When reviewer says:
Move code from commit B into commit A
Think:
1. Edit A
2. Add code to A
3. Edit B
4. Remove code from B
5. Continue rebase
6. Push updated stackResult:
Before:
A
└─ B(change X)
After:
A(change X)
└─ B(without change X)Useful Commands
git log --oneline --graph --decorategit show <commit>git diff <commit>^..<commit>git rebase -i maingit commit --amendgit rebase --continuegit push origin HEAD:refs/for/mainKey Lesson
Seeing change X in the files while editing A does not mean change X belongs to A.
Always remember:
Working Tree = cumulative result of the stack
Commit = diff relative to parentReviewers care about the commit diff, not the final file contents.