Replies: 1 comment 1 reply
-
Maintaining Git Branches to Represent a PatchsetThe goal is to maintain a set of branches where each branch represents a cohesive feature or patch. This allows us to:
Workflow Overview
Git Workflow Steps1. Initial Setup
2. Ongoing DevelopmentWhen adding a new patch ( 3. Periodic Upstream Updates (Expanded Example)You will periodically want to rebase your patchset branches on the latest
4. Merging Back to Preserve Review HistoryAfter rebasing the patches, you’ll want to merge the rebased branches back into the original patch branches (
By merging instead of rebasing directly on the patch branches, you avoid rewriting history, which keeps the pull request history intact and GitHub’s review system functional. Visualization with MermaidJSHere’s how the branch structure looks, including the steps for gitGraph
branch upstream/master
commit id: "upstream/master"
branch patch0
commit id: "patch0"
branch patch1
commit id: "patch1"
branch patch2
commit id: "patch2"
branch patchN
commit id: "patchN"
checkout upstream/master
commit id: "upstream/master (new)" tag: "new upstream commit"
branch rebased-patch0
commit id: "rebased-patch0"
branch rebased-patch1
commit id: "rebased-patch1"
branch rebased-patch2
commit id: "rebased-patch2"
branch rebased-patchN
commit id: "rebased-patchN"
checkout patch0
merge rebased-patch0
checkout patch1
merge rebased-patch1
checkout patch2
merge rebased-patch2
checkout patchN
merge rebased-patchN
ConclusionThis approach allows you to maintain your patchset efficiently while periodically updating it from upstream. By rebasing onto intermediate branches and merging back into the original branches, you:
Merge + Patch branches AlternativegitGraph
branch upstream/master
commit id: "upstream/master"
branch patch0
commit id: "patch0"
branch patch1
commit id: "patch1"
branch patch2
commit id: "patch2"
branch patchN
commit id: "patchN"
checkout upstream/master
commit id: "upstream/master (new)" tag: "new upstream commit"
checkout patch0
merge upstream/master
checkout patch1
merge patch0
checkout patch2
merge patch1
checkout patchN
merge patch2
Note both strategies refer to the patchset preserving merge/rebase workflows, not the previous comment
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Tip
TL;DR A merge strategy is my recommended approach because it (a) preserves history in full; while (b) encoding merge-conflict resolution in the tree such that it can be reviewed with a single command. A rebase strategy loses history and makes it difficult to review conflict resolution because it (a) isn't encoded in the tree; and (b) requires multiple steps.
Background
This document opens a discussion around possible git strategies for updating
libevmto incorporate the latest upstreamgethcommits. It analyses a merge strategy and a rebase strategy.Requirements
a.
libevmandgethcommits in their original state; andb. Modifications due to resolution of merge conflicts.
Branches and tags
mastertracks theethereum/go-ethereumequivalent branch;libevmis our default branch.libevm-baseis a floating tag that we update to point to the latest geth version incorporated inlibevm;libevm-baseand in the future it will mean all commits up to the last one included in an update.syncrefers to some intermediate branch being used to bring commits frommasterintolibevmvia a PR for review.Caution
When performing a sync of
master, triple check the branch. GitHub displays the option for ourlibevmbranch as well and this would be a destructive action that loses all history!Current state
We branched off
gethat some historical version, tagged aslibevm-base. Both we and they have since committed to our respective branches and their latest release isv1.14.11(the specific value is irrelevant). We wish to incorporate this release intolibevm.--- config: gitGraph: mainBranchName: master --- gitGraph commit id: "G-1" commit id: "G0" tag: "libevm-base" branch libevm commit id: "L1" checkout master commit id: "G1" checkout libevm commit id: "L..." checkout master commit id: "G..." checkout libevm commit id: "Ln" checkout master commit id: "Gm" tag: "geth-v1.14.11"Merge strategy
A rough outline of the (untested)
gitrecipe:$ git checkout libevm $ git checkout -b sync $ git merge master # Resolve conflicts $ git push --set-upstream origin syncThen open a PR to merge
syncintolibevm.--- config: gitGraph: mainBranchName: master mainBranchOrder: 0 --- gitGraph commit id: "G-1" commit id: "G0" tag: "libevm-base" branch libevm order: 2 commit id: "L1" checkout master commit id: "G1" checkout libevm commit id: "L..." checkout master commit id: "G..." checkout libevm commit id: "Ln" checkout master commit id: "Gm" tag: "geth-v1.14.11" checkout libevm branch sync order: 1 merge master id: "M" type: HIGHLIGHT tag: "PR"Note
All "PR" tags should be interpreted as labels for easy reference, not as literal tags.
Reviewing a merge strategy
Mcarries the merge-conflict resolutions.a. These can be inspected with
git show, which usesgit diff-tree -ccunder the hood for merge commits.b. A UI for inspection +/- code review is an open question.
Post review
Incorporating commit
Mintolibevmin a non-destructive fashion is important. A squash commit MUST NOT be used.Merge + fast-forward (preferred)
Note that since development is paused on
libevm, the branch can be fast-forwarded toM.Whether GitHub's PR merging via merge commit will perform a fast-forward is an open question.If this were to be performed locally, the recipe would be as follows, whichapparentlyresults in automatic PR merging (GitHub documentation) while respecting branch-protection rules:That would result in
Msimply being the next commit in thelibevmbranch before further development recommences withLn+1:--- config: gitGraph: mainBranchName: master --- gitGraph commit id: "G-1" commit id: "G0" tag: "(old) libevm-base" branch libevm commit id: "L1" checkout master commit id: "G1" checkout libevm commit id: "L..." checkout master commit id: "G..." checkout libevm commit id: "Ln" checkout master commit id: "Gm" tag: "geth-v1.14.11" tag: "(new) libevm-base" checkout libevm merge master id: "M" type: HIGHLIGHT tag: "Approved PR" commit id: "Ln+1" checkout master commit id: "Gm+1"Although this doesn't result in a linear history, that is actually the true history of concurrent development on the
masterandlibevmbranches.MergeM+ merge-commitMCThis is now redundant as merge + fast-forward has been demonstrated to work.
If pushing after local fast-forward doesn't work as expected, a regular PR merge can be performed, but it would result in an effectively emptyMCcommit being added. This is ugly but still preserves all history.--- title: "Redundant strategy: do not use" config: gitGraph: mainBranchName: master mainBranchOrder: 0 --- gitGraph commit id: "G-1" commit id: "G0" tag: "(old) libevm-base" branch libevm order: 2 commit id: "L1" checkout master commit id: "G1" checkout libevm commit id: "L..." checkout master commit id: "G..." checkout libevm commit id: "Ln" checkout master commit id: "Gm" tag: "geth-v1.14.11" tag: "(new) libevm-base" checkout libevm branch sync order: 1 merge master id: "M" type: HIGHLIGHT tag: "PR" checkout libevm merge sync id:"MC" tag: "Approved PR" commit id: "Ln+1" checkout master commit id: "Gm+1"Reverting a merge strategy
If the incorporated changes need to be reverted, either the
MorMCcommits are simple targets forgit revert, assuming no further development on the branch.Rebase strategy
A completely different approach is to rebase all of the
L*commits to instead branch off theGmcommit that would have otherwise been merged. Each of theLicommits now has a respectiveRicommit that introduces the same functionality/fix but with conflicts resolved on a per-commit basis.Opening a PR to merge
sync(Rn) intolibevm:--- config: gitGraph: mainBranchName: master mainBranchOrder: 0 --- gitGraph commit id: "G-1" commit id: "G0" tag: "libevm-base" branch libevm order: 2 commit id: "L1" checkout master commit id: "G1" checkout libevm commit id: "L..." checkout master commit id: "G..." checkout libevm commit id: "Ln" checkout master commit id: "Gm" tag: "geth-v1.14.11" checkout master branch sync order: 1 commit id: "R1" commit id: "R..." commit id: "Rn" tag: "PR"Reviewing a rebase strategy
a.
ncomparisons viagit diff Li Riwould have to be performed.i. This requires external knowledge of the PR pairings, not captured in the commit tree.
b. A UI for inspection +/- code review is an open question.
Post review
Rebase + merge is ambiguous
Even if there were no conflicts when merging
syncintolibevm, future commits would have an ambiguous history. If there were conflicts and they had to be resolved, this would indicate a broken process because resolution was already performed during the rebase.--- config: gitGraph: mainBranchName: master mainBranchOrder: 0 --- gitGraph commit id: "G-1" commit id: "G0" tag: "(old) libevm-base" branch libevm order: 2 commit id: "L1" checkout master commit id: "G1" checkout libevm commit id: "L..." checkout master commit id: "G..." checkout libevm commit id: "Ln" checkout master commit id: "Gm" tag: "geth-v1.14.11" tag: "(new) libevm-base" checkout master branch sync order: 1 commit id: "R1" commit id: "R..." commit id: "Rn" tag: "PR" checkout libevm merge sync id: "M" type: REVERSE tag: "Approved PR" commit id: "Ln+1"Rebase + force push loses history
The
libevmbranch could be forced to point toRn.--- config: gitGraph: mainBranchName: master --- gitGraph commit id: "G-1" commit id: "G0" tag: "(old) libevm-base" commit id: "G1" commit id: "G..." commit id: "Gm" tag: "geth-v1.14.11" tag: "(new) libevm-base" branch libevm commit id: "R1" commit id: "R..." commit id: "Rn" tag: "Approved PR" commit id: "Ln+1"There are a lot of resources online explaining the problems with force pushes. Using the commit tree alone, history regarding the original development of the
L*branches will be lost, which doesn't meet our requirements.Reverting a rebase strategy
Reverting a merged rebase can be done with
git revert M. Reverting a force-pushed rebase requires knowledge of the oldlibevmbranch, which would itself be force-pushed.Beta Was this translation helpful? Give feedback.
All reactions