2

I've been maintaining a sizable stack of patches against an upstream Perforce repo using Stacked Git. I did a 'git init' in the root directory of the checked out Perforce tree and committed the 'pristine' upstream sources there. Then I cloned that git repo locally to create my patch series.

Periodically, I pull down updates from the Perforce server, commit them to the 'pristine' git mirror, and then do:

$ git remote update
$ stg rebase remotes/origin/master

This is usually straightforward, but occasionally someone touches dozens of upstream files in a trivial way (e.g., using uncrustify) that generates a large number of conflicts. When this happens, it's not always convenient to triage my patches right then and there; sometimes I'd like to forget the whole thing and just keep working.

To handle this situation with standard git, I would create a temp branch to pull/merge (or rebase) onto, and, if I like the end result, delete my master branch and rename the temp branch to master. I haven't quite figured out how to achieve the same thing using stgit.

I came across this SO question about undoing a standard git rebase, but can the same technique be used with stgit?

Update [12/15/2011]: I feel compelled to state something that is perhaps non-obvious--stg undo does not (apparently) do what I want:

$ stg status
$ stg series
+ add-copyright-notice
+ add-bn-namespace
> fix-tabs
$ git remote update
Fetching origin
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From c:/d/projects/luasand
   57afdac..6b4b209  master     -> origin/master
$ 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
Error: The merge failed during "push".
       Revert the operation with "stg undo".
stg rebase: 1 conflict(s)
$ stg status
UU Variant.h
M  main.cpp
$ stg undo
Error: Need to resolve conflicts first
stg undo: Command aborted (all changes rolled back)

In the scenario shown above, I just want to pretend I never typed stg rebase ... and keep working, deferring the rebase until a more convenient time. It tells me to revert using stg undo, then tells me I have to resolve conflicts first(?!) How can I tell StGit to just forget the whole thing?

Community
  • 1
  • 1
evadeflow
  • 4,124
  • 31
  • 45

2 Answers2

2

Okay, I'll take a stab an answering this myself, even though I don't like my own answer much. stg reset --hard seems to do what I want:

$ stg series
+ add-copyright-notice
+ add-bn-namespace
> fix-tabs
$ git remote update
Fetching origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 3), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From c:/d/projects/luasand
   57afdac..d340a1a  master     -> origin/master
$ 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
Error: The merge failed during "push".
       Revert the operation with "stg undo".
stg rebase: 1 conflict(s)
$ stg log
f2af21a   Thu, 15 Dec 2011 13:23:54 -0500   rebase (CONFLICT)
ebe140c   Thu, 15 Dec 2011 13:23:53 -0500   rebase
a04604e   Thu, 15 Dec 2011 13:22:29 -0500   refresh
a83f169   Thu, 15 Dec 2011 13:22:28 -0500   refresh (create temporary patch)
c2a57d8   Thu, 15 Dec 2011 13:21:50 -0500   new
1770c17   Thu, 15 Dec 2011 13:21:44 -0500   refresh
7613544   Thu, 15 Dec 2011 13:21:44 -0500   refresh (create temporary patch)
bf19372   Thu, 15 Dec 2011 13:20:49 -0500   new
7a67f4c   Thu, 15 Dec 2011 13:20:43 -0500   refresh
ae42ad2   Thu, 15 Dec 2011 13:20:42 -0500   refresh (create temporary patch)
8c91906   Thu, 15 Dec 2011 13:20:12 -0500   new
2b75e5f   Thu, 15 Dec 2011 13:20:11 -0500   start of log
$ stg reset --hard a04604e
Now at patch "fix-tabs"
$ stg series
+ add-copyright-notice
+ add-bn-namespace
> fix-tabs

This appears to work, but I'd prefer a solution that doesn't rely on doing reset --hard on the master branch. I've shot myself in the foot a few times with this command, so now I always use the “Sith Master” trick described on p. 25–26 of Git From The Bottom Up.

So... this is an answer to my original question, but I'm hoping somebody will post a better one.

Guildenstern
  • 1,185
  • 1
  • 11
  • 27
evadeflow
  • 4,124
  • 31
  • 45
  • 1
    I'm accepting this answer since it's the simplest one and I've already found myself doing it a lot. Feel free to vote up my other answer if you find it useful. :-) – evadeflow Dec 17 '11 at 19:36
0

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.

evadeflow
  • 4,124
  • 31
  • 45