2

When you have a feature branch that is long-lived, there are two standard ways to keep it somewhat in sync with your primary release branch without actually putting your code into the primary release branch.

Periodically, either you rebase your feature branch against the primary release branch, or you reverse-merge the changes in your primary release branch to your feature branch.

The rebase converts this:

A - - - PRIMARY BRANCH
 \
  \-1-2-3-FEATURE BRANCH

into this:

A - - - PRIMARY BRANCH
         \
          \-1'-2'-3'-FEATURE BRANCH

where the 1' 2' 3' are rebased versions of the original 1 2 3.

The merge converts it into

A - - - PRIMARY BRANCH
 \          \
  \ -1-2-3-  X - FEATURE BRANCH

Where X is a merge commit that has parents in both PRIMARY and in the original FEATURE branch.

A downside to the rebase solution is that someone with a copy of the feature branch now has a seemingly unrelated set of changes, as their 1 2 and 3 are not the same CLs as the ones after the rebase.

The changes in the rebase case reflect the changes "as if you did it at the moment of rebase" on the primary branch. This has some nice properties, as if you can roll back these changes one at a time and see how they behave in the current primary branch.

The Question

Is there an easy way using standard git tools to do both at once, keeping both the connection to the original feature branch, and a set of CLs that represent doing it against the current primary?

A - - - PRIMARY BRANCH
 \        \ 1'-2'-3' -
  \                   \
   \ -1-2-3----------- X - FEATURE BRANCH

in this case, the X would have the same content as the X in the reverse-merge scenario, except it also states that the original feature branch is a parent CL.

From this, we'd get the rebase workflow, but other people with access to the original feature branch aren't abandoned -- if they remerge against upstream FEATURE, git fully understands what is going on.

A - - - PRIMARY BRANCH
 \        \ 1'-2'-3' -
  \                   \
   \ -1-2-3----------- X - FEATURE BRANCH
           \- 3rd party

The 3rd party CL has a clear merge path to move to the tip of FEATURE BRANCH, instead of the mess it has to work through in the case of

A - - - PRIMARY BRANCH
 \        \ 1'-2'-3' - FEATURE BRANCH
  \                  
   \ -1-2-3-- OLD FEATURE BRANCH
           \- 3rd party

the traditional rebase.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Even though it looks to solve your issues, I think this approach has the major downside of having a messy history. I would rather ensure through a proper process that the rebase is correctly handled by all participants in the first place. – Gaël J Sep 14 '21 at 19:55

1 Answers1

2

Is there an easy way using standard git tools to do both at once... ?

Yes, you can easily create the graph exactly as you proposed:

git fetch
git checkout feature
git branch feature-old-copy # create a copy of feature
git rebase origin/primary
git merge --strategy=ours feature-old-copy

Note that the final merge should have no effect; i.e. it's a pointless merge except to keep the old set of commit IDs in the branch...

However, IMHO I think you've misidentified the primary reason to choose rebase in the first place. Your statement:

The changes in the rebase case reflect the changes "as if you did it at the moment of rebase" on the primary branch. This has some nice properties, as if you can roll back these changes one at a time and see how they behave in the current primary branch.

is also true of a merge. (You can revert a commit regardless of whether it was rewritten with a rebase or merged in, though there is indeed a slight benefit to rebase.*) The primary benefit of rebase is a cleaner history without multiple merge commits that are (typically) unnecessary. As an example, when I use a rebase workflow I typically rebase onto the future target branch multiple times per day. If my branch lasts 2-3 days I have saved maybe 10 unnecessary merge commits on my branch. With your suggested workflow, you aren't really capturing the benefit of rebase, and therefore I don't see it being that useful.

So, yes, you can do it, but I question the value of doing it.


*It's possible that reverting individual commits underneath the merge could require reworking conflicts that occurred at the time of that merge, whereas with rebase they would already be resolved and are more likely to revert cleanly.

TTT
  • 22,611
  • 8
  • 63
  • 69
  • Will `git merge feature-old-copy` actually know it should be an (almost) noop? Won't there be the potential for a bunch of (spurious) conflicts? – Yakk - Adam Nevraumont Sep 14 '21 at 19:54
  • I was just thinking about this for a different reason, which I'll address in another comment. My gut feeling is if you get conflicts on the rebase, then you'll get conflicts again on the merge, and I think the opposite is also true (if no conflicts on the rebase, then no conflicts on the merge). – TTT Sep 14 '21 at 19:56
  • 1
    I don't think it would be if and only if. But I *know* that any such conflicts are resolved by simply taking the `feature` branch's version of the working tree. – Yakk - Adam Nevraumont Sep 14 '21 at 19:58
  • I'm wondering if performing this process might have value for an entirely different reason. Might this be helpful in detecting an [evil merge](https://stackoverflow.com/q/1461909/184546)? – TTT Sep 14 '21 at 20:00
  • Well, you'd have to distribute the "evil" over the "rebase" versions of `1...3`, aka `1'` `2'` `3'`, as `3'` to `X` should be a noop, while `3` to `X` is the merge of `A...3'`. – Yakk - Adam Nevraumont Sep 14 '21 at 20:04
  • @Yakk-AdamNevraumont I agree that you must take `feature` branch's version when conflicts occur from merging in `feature-old-copy`. (Which forces the noop in the case that a conflict tried to prevent it.) – TTT Sep 14 '21 at 20:07
  • I am attempting to find, but not succeeding, in a merge command of "merge these two, but use the working tree of X without conflict resolution". I know how to do that on the git graph itself (it is just a node with one side's working tree, and both as bases), but not through tools. – Yakk - Adam Nevraumont Sep 14 '21 at 20:16
  • @Yakk-AdamNevraumont Yep. I agree with the evil order too. (And also that it's not if and only if for the same reason.) – TTT Sep 14 '21 at 20:17
  • @Yakk-AdamNevraumont I'm not sure about that either, but I think you can basically get the same result by [taking ours](https://stackoverflow.com/q/23762897/184546) anytime there *is* a conflict, and the rest as we've established is already a noop. – TTT Sep 14 '21 at 20:21
  • 1
    `--strategy=ours` might do it. That should apply to the entire working tree if I understand it right. – Yakk - Adam Nevraumont Sep 14 '21 at 20:21
  • @Yakk-AdamNevraumont lol, I just realized the question I linked is exactly that. Hehe. – TTT Sep 14 '21 at 20:22
  • 1
    Can you add `--strategy=ours` to the final `git merge` above? I think it is both correct, and makes any merge conflicts happen exactly once. – Yakk - Adam Nevraumont Sep 14 '21 at 20:24
  • @Yakk-AdamNevraumont I'm thinking we can clean up these comments now... – TTT Sep 14 '21 at 20:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237107/discussion-between-ttt-and-yakk-adam-nevraumont). – TTT Sep 14 '21 at 20:42