0

Using git, is there a good way to get the latest from upstream and re-apply our downstream changes? Instead of merging the upstream branch into the downstream branch that already contains our downstream changes, re-applying our downstream changes to the new upstream would be more efficient.

I have a situation I imagine isn't unique: we have changes in our downstream branch that the upstream won't or hasn't yet accepted, but we need to keep periodically getting (very large) upstream changes from a team much much larger (by orders of magnitude) than ours.

It is much better for us adapt our downstream code changes to the upstream changes - and not vice versa - since the other way would be much more work in the long run each merge. Re-applying our downstream changes (and adapting them to the new upstream) is much cleaner and safer than merging the upstream changes into our downstream changes, and allows us to better review how our downstream changes modify the new upstream.

The traditional merge upstream into downstream method would be:

  1. git checkout origin/master
  2. git merge upstream/master, resolving conflicts between new upstream changes and our downstream changes

A labor-intensive method to re-apply downstream changes over the new upstream could be:

  1. Create a list of downstream changes to keep via git log - let's call it the keep list
  2. Replace contents of origin/master with upstream/master: git command for making one branch like another
  3. git cherry-pick each old change from the keep list back into origin/master, resolving conflicts

One way of thinking about it - we need something similar to git rebase but that works with "public" non-local branches, rebasing downstream/master using latest upstream/master. git rebase itself is usually not acceptable since it would break the golden rule of rebasing - never rebase a "public" branch.

Anyone have a better method? Thanks in advance!

Community
  • 1
  • 1
Jason Spangler
  • 111
  • 1
  • 6

2 Answers2

1

Assume branches as follows:

A - B - C - D - E - F - G <- upstream/master
        |
         \
          - X - Y - Z <- origin/master <- HEAD

1st, replace contents of origin/master with upstream/master by git merge upstream/master -Xtheirs. Call this merge commit "R". (If you created new files in your branch you need to delete them.)

A - B - C - D - E - F - G <- upstream/master
        |               |
         \              |
          - X - Y - Z - R <- origin/master <- HEAD

2nd, run git rebase -i --onto origin/master upstream/master origin/master^. This command opens an editor with contents like:

pick 5db64e4 X
pick d0744fe Y
pick 550d34b Z

# Rebase  0cbeed6..550d34b onto 86590a3 (3 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Just close the editor, then changes made by X, Y, and Z will be merged onto origin/master. (You need to resolve conflicts here if any.)

A - B - C - D - E - F - G <- upstream/master
        |               |
         \              |
          - X - Y - Z - R <- origin/master
                        |
                         \
                          - X' - Y' - Z' <- HEAD

3rd, run git branch -f origin/master HEAD to move origin/master to the tip.

A - B - C - D - E - F - G <- upstream/master
        |               |
         \              |
          - X - Y - Z - R
                        |
                         \
                          - X' - Y' - Z' <- origin/master
                                         <- HEAD

Last, run git checkout origin/master to attach the HEAD.

A - B - C - D - E - F - G <- upstream/master
        |               |
         \              |
          - X - Y - Z - R
                        |
                         \
                          - X' - Y' - Z' <- origin/master <- HEAD
kaitoy
  • 1,545
  • 9
  • 16
0

I'm not sure that I understand motivation behind your question correctly. I'll assume that you are aware of all posibillities of rebase strategies and options due to overflow answer that you pointed out.

Maybe patches will help you. If you want to simulate cherry picking from 'keep list' make patch that contains each commit you need to apply to other branch:

git format-patch --stdout first_commit_hash^..last_commit_hash > commits.patch

Checkout to your desired branch that is in state that you want and apply patch

git am -3 --signoff < commits.patch

-3 option will do a three-way merge if there are conflicts.

Signoff option will add additional data about who made a patch

Applied commits will contain metadata

Zildyan
  • 1,261
  • 9
  • 11