Doing a 'Dry Run' Rebase of an StGit Patch Series
I didn't like my previous answer much because it used reset --hard
on the master branch. Here's a workflow that uses a temporary branch to preview the rebase to see how much work it involves. If it turns out to be trivial, you let that branch replace your master branch, a là the 'Sith Master' tip from pp. 25-26 of Git From The Bottom Up.
If the rebase has a ton of conflicts and you want to defer the triage work until a more convenient time, you just switch back to your master
branch and keep working, deleting the temporary branch.
Start Condition
Assume you have a work tree created with stg clone
that contains a stack of patches, all of which are currently applied:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg series
+ add-copyright-notice
+ add-bn-namespace
> fix-tabs
You update your remote ref to the repo you cloned from and see that some changes have come over:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ git remote update
Fetching origin
remote: Counting objects: 9, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 4), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
From c:/d/projects/luasand
b22d65f..ab55ba3 master -> origin/master
You think: "I'd like to stay as current as possible, but I'm not sure I want to deal with this right now..." What to do?
Export the Patch Series
The first thing to do is write out the patch series so it can be imported on the temporary branch created in the next step:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg export --dir=/f/projects/luasand_stg_patches
Checking for changes in the working directory ... done
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ ls /f/projects/luasand_stg_patches
add-bn-namespace add-copyright-notice fix-tabs series
(There's a way to bring over the patches without exporting them like this, but I prefer to do it this way because it serves as a redundant backup.)
Create the Temporary Branch
Next, create a temporary branch to preview the rebase. The only tricky part about this is selecting the base revision to branch from. There are a couple of ways to do this, but my preference is to pop all patches and branch as follows:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg pop --all
Popped fix-tabs -- add-copyright-notice
No patch applied
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg branch --create rbtemp
Checking for changes in the working directory ... done
Recording as a local branch
Branch "rbtemp" created
(Again, there are other ways to do this, but I find this easiest.)
Import Patches on the Temporary Branch
The next command you'll want to execute is:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg import -s /f/projects/luasand_stg_patches/series
Checking for changes in the working directory ... done
Importing patch "add-copyright-notice" ... done
Importing patch "add-bn-namespace" ... done
Importing patch "fix-tabs" ... done
Now at patch "fix-tabs"
The end result is that the rbtemp
branch looks exactly like your master
branch before you started.
Perform the Rebase
Finally, you can perform the rebase on the temporary branch. The command to do this is:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg rebase remotes/origin/master
[Output omitted]
There are a couple different ways this can turn out, which I'll now describe in detail:
Rebase: Rainy Day Path
Since it was the motivation for my original question, I'll start with the case where the rebase has conflicts that you don't care to deal with at the moment. Here's how that scenario looks:
evade@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg rebase remotes/origin/master
Checking for changes in the working directory ... done
Popping all applied patches ... done
Rebasing to "remotes/origin/master" ... done
Pushing patch "add-copyright-notice" ...
CONFLICT (content): Merge conflict in Variant.h
CONFLICT (content): Merge conflict in main.cpp
Error: The merge failed during "push".
Revert the operation with "stg undo".
stg rebase: 2 conflict(s)
If you're in the middle of something you don't want to be pulled away from and would rather not be bothered fixing these conflicts right now, just do the following:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg reset --hard
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg branch master
Checking for changes in the working directory ... done
Switching to branch "master" ... done
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg branch --delete --force rbtemp
Deleting branch "rbtemp" ... done
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg push --all
Pushing patch "add-copyright-notice" ... done (unmodified)
Pushing patch "add-bn-namespace" ... done (unmodified)
Pushing patch "fix-tabs" ... done (unmodified)
Now at patch "fix-tabs"
Now you're right back where you started and can schedule the rebase for a more convenient time.
Rebase: Cloudy Day Path
If the rebase has a few conflicts that look managable, you might decide to triage them right then and there on your temp branch. I'll present what that process looks like, including a few mistakes I made along the way (i.e., forgetting to do git add --update
):
evade@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg rebase remotes/origin/master
Checking for changes in the working directory ... done
Popping all applied patches ... done
Rebasing to "remotes/origin/master" ... done
Pushing patch "add-copyright-notice" ...
CONFLICT (content): Merge conflict in Variant.h
CONFLICT (content): Merge conflict in main.cpp
Error: The merge failed during "push".
Revert the operation with "stg undo".
stg rebase: 2 conflict(s)
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ vim Variant.h main.cpp
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ git add --update
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg refresh
Now at patch "add-copyright-notice"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg series
> add-copyright-notice
- add-bn-namespace
- fix-tabs
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg push --all
Pushing patch "add-bn-namespace" ... done (conflict)
Error: 1 merge conflict(s)
CONFLICT (content): Merge conflict in main.cpp
Now at patch "add-bn-namespace"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ vim main.cpp
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg push --all
stg push: Worktree not clean. Use "refresh" or "status --reset"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg refresh
stg refresh: Cannot refresh -- resolve conflicts first
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ git add --update
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg refresh
Now at patch "add-bn-namespace"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg push --all
Pushing patch "fix-tabs" ... done (empty)
Now at patch "fix-tabs"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg series
+ add-copyright-notice
+ add-bn-namespace
> fix-tabs
The ``stg push --all` command stops after each patch that fails to apply, giving you a chance to fix it. Basically, you keep trying to push all the patches until you get them all cleaned up. After that, your patch stack is clean again, sitting atop the latest upstream revision. You run your tests and they all pass. Woot!
The only problem is: you've done all the work on rbtemp
. Since everything worked out okay, you'd really like it to be on your master branch. Not a problem. Just rename rbtemp
to master and delete your old master branch:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg branch --rename master old_master
fatal: No such section!
Upgraded branch old_master to format version 2
Renamed branch "master" to "old_master"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg branch old_master
Checking for changes in the working directory ... done
Switching to branch "old_master" ... done
evadeflow@GILGAMESH /c/d/projects/luasand_stg (old_master)
$ stg branch --rename rbtemp master
fatal: No such section!
Upgraded branch master to format version 2
Renamed branch "rbtemp" to "master"
evadeflow@GILGAMESH /c/d/projects/luasand_stg (old_master)
$ stg branch master
Checking for changes in the working directory ... done
Switching to branch "master" ... done
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$ stg branch --delete --force old_master
Deleting branch "old_master" ... done
evadeflow@GILGAMESH /c/d/projects/luasand_stg (master)
$
And there you are, sitting back on your master
branch, ready to continue working. (Note: I'm not sure what the fatal: No such section!
errors mean. I've been completely ignoring this error for awhile now with no ill effects.)
Rebase: Sunny Day Path
The final scenario to consider is when your patches apply cleanly, with no
conflicts, to the latest upstream revision. Here's what the rebase
output looks like in that case:
evadeflow@GILGAMESH /c/d/projects/luasand_stg (rbtemp)
$ stg rebase remotes/origin/master
Checking for changes in the working directory ... done
Popping all applied patches ... done
Rebasing to "remotes/origin/master" ... done
Pushing patch "add-copyright-notice" ... done
Pushing patch "add-bn-namespace" ... done (modified)
Pushing patch "fix-tabs" ... done
Now at patch "fix-tabs"
If you run your tests and they all pass, then you just do the 'Sith Master' tip outlined at the end of the 'Cloudy Day Path' section above to to rename rbtemp
to master
and delete your old master branch.
Summary
If you don't already know git pretty well, and have at least some familiarity with StGit, the above steps might seem horrifically complicated. They're not. It just takes a little while to get used to working this way. It's not for everybody, but if you need to maintain a complex set of patches against somebody else's code base it's pretty effective compared to any alternatives I know of. (Please comment if you have a strategy you think is easier; I'm always looking to improve my workflow.)
I don't always bother to make a temporary branch to preview a rebase. Sometimes I just try it on master
first and do stg reset --hard
if there are conflicts---which isn't very often. I do like triaging on a temp branch when there are conflicts. It gives me a certain peace of mind knowing that I'm very unlikely to bork my master branch while I'm trying to unbork my patch series.