A place to keep my thoughts on programming

February 6, 2011 geek , , ,

faking git merge –strategy=theirs

I've been trying to figure out a workflow in git for resetting my clone of an upstream branch to the current upstream state, but without discarding my history. The reason for not dropping the history is that a) it's antithetical to me to ever discard anything from revision control, and b) i push my local changes to a public repo, which means others might have cloned it and are following my changes, so a git reset or git rebase is a bad thing.

Sure, merge usually does just fine, but in the case of me working on something that is not accepted upstream or was made irrelevant by an upstream change the merge would not get rid of my dead end changes.

One option is to leave my clones of upstream branches alone and always create working branches that i discard once the task is completed and each new working branch is a new branch off the upstream master.

After digging around a while, I found almost what i wanted:

git merge --strategy=ours <branch>

which brings in history from <branch> but adds one more commit recording the changes required to keep the current branch at its pre-merge state. Except i want to do the opposite

git merge --strategy=theirs <upstream/branch> // does not exist!

which would bring in the history from <upstream/branch> and record a commit with the changes required to make the current branch a replica of <upststream/branch>. While there is something that looks like it would do that, i.e.

git merge --strategy=recursive -X theirs <upstream/branch>

but that will not discard local changes that do not conflict with upstream changes.

A workflow to fake git merge –strategy=theirs

Assuming i must just be overlooking a command or switch, I asked on stackoverflow and with the help of users kelloti and jefromi (update: VonC updated his answer to more precisely reflect this workflow) was able to put together a workflow that fakes --strategy=theirs:

get a temp copy of the upstream branch
git co -b temp <upstream/branch>

merge our version of the branch into the upstream with ours strategy
git merge --strategy=ours <branch>

commit if necessary (i.e. auto-commit or fast forward didn't happen)
git commit ...

checkout our version of the branch
git co <branch>

merge temp (which will be a fast forward)
git merge temp

push the changes to our origin repo
git push

get rid of the temp branch
git branch -D temp

It's a bit convoluted but does leave us with our history and a re-freshed local copy of the upstream master.

Update: Why do i want this again?

I'm setting up the worflow for MindTouch DReAM right now. Up until now, we'd been collobrating via SVN without development branches. I had kept my own private git repo, because I am rather particular about committing frequently and wanting those commits backed up remotely.

As long as my repo had nothing to do with the public version, this was all fine, but since now I'd want the ability to collorate on WIP with other team members and outside contributors, I want to make sure that my public branches are reliable for others to branch off and pull from, i.e. no more rebase and reset on things I've pushed to the remote backup, since it's now on github and public.

So that leaves me with how i should proceed. 99% of the time my copy will go into the upstream master, so i want to work my master and push into upstream most of the time. But every once in a while what i have in wip will get invalidated by what goes into upstream and i will abandon some part of my wip. At that point I want to bring my master back in sync with upstream, but not destroy any commit points on my publicly pushed master. I.e. i want a merge with upstream that ends up with the changeset that make my copy identical to upstream. And that's what git merge --strategy=theirs should do.

7 to “faking git merge –strategy=theirs”

  1. bjorg says...

    For a moment there, I was worried it would be complicated…

  2. daK says...

    Thanks for compiling this precious info. Probably the most usefull tip I'll use from no on. but mindsetting, I can't see how will I manage branch sanity in the following situation or if it's exactly your situation. But I understand that all this is now possible with your tips.
    Scenario: A public fork (a wellknown github fork), a my-colab-fork (my github repo), and my-other-fork (a local clone I wish to make different new repo, but will be have pareantage relationship).
    Currently I only push to my-colab-fork if I'm going to pull-request on github. Meaning that until now all my SHAs are identical to public-fork. Until now I used to wait for my pull-request public integration to than –ff merge public-fork into my-colab-fork, thus picking the new public merge-commit (the integrator has a rule to always creates a merge-commit).
    Because of that, If pull request is denied, I quickly reset –hard and push my-colab-repo. Bad I know. With you post, I hope it will be all gone, since now it's looks more easy to merge between branches frequently and upstream them.
    Could you tell me in the following scenario:

    My local repo (my-future-repo) is 5 commits ahead of upstream/my-colab-fork, meanwhile my-colab-fork is 2 commits behind public-fork.
    Means it's time to synchronize things between the 3 forks. Problem areas that arize:

    I want the latest public changes (2 commits) merged into upstream/my-colab-fork (easy! the usual -ff merge)
    I want the latest public changes (2 commits)merged into my-future-repo (may need to edit – ours?)
    At the same time, I'm interested in some changes of those 5 commits from my-future-repo, and would like to cirurgically merge them into my-colab-fork, to later pull-request. Let's say I need one and a half commit from my-future-repo current, meaning I need to edit commits (and SHAS)
    Those picked changes I want them pushed into my-colab-fork so that I can github pull-request and be merged in public-fork (easy! step)
    Later, after accepted pull request, and more public development, I will need to merge new public-changes into my-future-repo. But in those changes there will be the 1 and 1/2 commits (with a new SHA) from point number 3.

    You strategy I think will answer most of the problem areas, although I still don't know how to handle the external commits. If I temporarly stash or interactivly rebase them, the commits will yell when I merge new upstream changes (due to the changes coming from a temp SHA – or I'm confused?).
    I think I just need to define between the 3 forks, what strategy will be applied (ours or theirs) in a way that I can have a development cycle between 3 without much coalisions… mas first have to understadn better merge strategy.
    If you have any appreciations to add, thanks, or some link to something related U could take a look.
    Again thanks

  3. arne says...

    Since i push WIP to github frequently it is my policy to avoid hard resets and rebases, so i don't change history on things i've already pushed. My current strategy for dealing with upstream repos is that i mirror their master for me to use, but never commit changes to. If i have changes i create a new feature specific branch off upstream/master. That way, i can still pick up upstream changes in my master and push only specific changes upstream with a dedicated branch that if not accepted upstream is easily deleted.

  4. Nick Coghlan says...

    I'm fairly sure I found a simpler way to do this:

        git diff other-branch | patch -R  # Makes our local checkout match the remote one
        git merge -s ours other-branch # Adds all the remote changes to our history

  5. Nick Coghlan says...

    One slight correction, using "-s ours" will cause problems with failing to copy *new* files from the remote branch. The default merge strategy is a better option.

  6. Nick Coghlan says...

    Never mind, you have to use "-s ours" or it will complain about the uncommited changes. Just be aware that it may need an additional couple of commands to get any new files cloned correctly.

  7. Philip Oakley says...

    A set of commit diagrams would also be useful to complement the text. It’s easy to miss some of the nuances, and they are worth a thousand words, apparently ;-)

Leave a comment