mackyle/topgit

Workflow with regular git branches

Opened this issue · 1 comments

I'm frequently working on repositories where I have a dozen branches implementing various features, and frequently have to cooperate with upstream for months before they are merged. Meanwhile, I work on other features which depend on those patches. I was currently merging my branches manually, which was time consuming.

Sounds like topgit does exactly what I was doing manually, but I cannot understand what sort of workflow I should use in a few scenarios.

Let's assume I already created a PR branch that I need to work on before I started using topgit. Can I safely "depend" on it in a [STAGE] branch?

Now let's say upstream makes a new release while the regular branch was started. They ask to rebase on top of the new release. There's no [BASE] branch here, as the branch was created prior to using topgit. Can I rebase normally?

Another scenario involving rebase: let's assume I have two feature branches (feat1/feat2), with feat2 built on top of feat1.

t/base
  t/feat1
    t/feat2

At some point feat1 is implemented by upstream differently (not using the same commits but in a way that would make feat2 still working), making my changes redundant. What I'd do normally would be to drop feat1 and rebase feat2 on top of new master. What should I do with topgit here?

I apologize for the delay in responding.

First off, the example you gave:

   t/base
     t/feat1
       t/feat2

Would actually be output by the tg summary --rdeps command like so:

t/feat2
  t/feat1
    t/base

Showing that t/feat2 logically contains (i.e. has in its .topdeps file) t/feat1 which logically contains (i.e. has in its .topdeps file) t/base.

There’s actually a repository you may want to look at https://repo.or.cz/git/gitweb.git that is currently set up using TopGit with multiple TopGit patch branches and some TopGit stage branches and a TopGit “base” branch (it’s “release” – TopGit branch names do not have to start with “t/”).

It tracks various gitweb patches used by girocco. It contains the entire “Git” repository since that’s what the patches are based on.

It works like this. The “base” branch (as output by tg summary):

0       release                        [BASE] base release branch for girocco

Always points at a Git release tag. For example, using this command:

tg info --leaves girocco

produces this output (yes, we’re not on the very latest gitweb...yet):

refs/tags/v2.11.4

The first few lines of the tg summary output are:

 0       girocco                       	[STAGE] Girocco-specific gitweb changes
 0       gitweb-additions              	[STAGE] gitweb-additions
 0       release                       	[BASE] base release branch for girocco
         t/blame/extra-columns         	[PATCH] gitweb: extra columns in blame
         t/blame/incremental           	[PATCH] gitweb: incremental blame config

Currently all the patches are based on the Git tag v2.11.4.

To update them to be based on, say, tag v2.12.5, I would use this sequence:

tg update --base release v2.12.5 # only changes TopGit branch “release”
tg update girocco                # merges in all v2.11.4..v2.12.5 commits

Then, if I were going to submit one (or more) of the patches to the Git mailing list for consideration, I would use the tg export and/or tg patch command to produce patch files that I could use git send-email with to send to the Git list. For other projects, that want a pull request, tg export can also produce a new “cleaned up’ (aka collapsed) branch in the Git repository itself that can then be pushed somewhere or have any other Git commands used on it that one would normally use to “submit” a patch.

Now to respond to your questions.

Let's assume I already created a PR branch that I need to work on before I started using topgit. Can I safely "depend" on it in a [STAGE] branch?

Yes. PR is a non-TopGit branch (and will show up in the output of tg info --leaves). Whenever the PR branch has been modified, simply running tg update <name-of-stage-branch> will then merge all of its changes up through the TopGit branches (recursively) that depend on it.

You could always manufacture a TopGit base branch for PR thereby turning it into a TopGit branch at any time, but there’s no compelling reason to do so for this particular example.

Now let's say upstream makes a new release while the regular branch was started. They ask to rebase on top of the new release. There's no [BASE] branch here, as the branch was created prior to using topgit. Can I rebase normally?

Maybe. As long as PR remains a non-TopGit branch, it may work. Here’s what would happen.

Say you had this before the rebase:

* -- * -- V1 -- P -- R -- PR

Then upstream gets a new release and you have this:

* -- * -- V1 -- * -- * -- V2
            \
             P -- R -- PR

And then you rebase like so:

* -- * -- V1 -- * -- * -- V2 -- P' -- R' -- PR'
            \
             P -- R -- PR

Where PR' is the newly rebased version of PR.

If you had a TopGit stage branch that contained PR as one of its dependencies, like so:

* -- * -- V1 -- * -- * -- V2
            \
             P -- R -- PR -- STAGE

After the rebase it looks like this:

* -- * -- V1 -- * -- * -- V2 -- P' -- R' -- PR'
            \
             P -- R -- PR -- STAGE

And then you run tg update on that TopGit staging branch and it will attempt this merge:

* -- * -- V1 -- * -- * -- V2 -- P' -- R' -- PR'
            \                                 \
             P -- R -- PR --------------------- STAGE

That may, or may not, behave in a friendly fashion. Git will probably notice that P', R' and PR' are the same “patch” (see git help patch-id) as P, R and PR and skip them and just merge in the V1..V2 changes. Probably.

However, if you get some gnarly conflicts during the rebase of PR, then some or all of those rebased commits might not match up so well anymore leading to more conflicts during the tg update (it will use git rerere info if enabled which may help out).

For an alternative, you could do something like this:

tg create --base --topmsg "patches' base" t/upstream V1
tg create --topmsg 'PR' t/PR t/upstream # leaves t/PR checked out
git merge -m "sync with PR" PR # pre-rebase of PR

Which will create a TopGit t/PR branch based off a new TopGit “base” branch named t/upstream which is currently pointing at the V1 release. In place of V1 above the correct commit to use is the actual fork-point of your PR branch from upstream before rebasing it. Likely shown by:

git merge-base PR V1

Then, instead of rebasing PR you can just do this:

tg update --base --no-edit t/upstream V2
tg update t/PR    

And now the new t/PR branch has been updated to be against V2 instead of V1.
Obviously, if you're only maintaining a single patch branch you can easily use git pull -rebase to do it. But those same two steps can update any number of patches if you use a staging branch that contains them all.

At some point feat1 is implemented by upstream differently (not using the same commits but in a way that would make feat2 still working), making my changes redundant. What I'd do normally would be to drop feat1 and rebase feat2 on top of new master. What should I do with topgit here?

There is a tg annihilate command, but it currently lacks the needed --revert option – I've put it on my TODO list. The way this would be handled by TopGit is equivalent to doing this (which is the workaround until the --revert option shows up):

git checkout t/feat1
tg patch -R | git apply
git commit -am 'remove changes in favor of upstream'
tg update t/feat2
tg annihilate t/feat1 # optional, makes t/feat1 mostly go away

Again, extra work for just a single branch, but if you have a complex set of patches being maintained (such as https://repo.or.cz/git/gitweb.git), it can be a great time saver.