4

According to the Github for Mac blog announcement,

Once you're ready to share your commits, or pull in remote commits — just press the Sync Branch button. We'll perform a smarter version of pull --rebase && push that reduces merge commits but doesn't rewrite your merges.

What is "a smarter version" of pull --rebase && push? What exactly are they doing? Is it possible to do this "smarter" thing on the command line?

Dan Fabulich
  • 37,506
  • 41
  • 139
  • 175

2 Answers2

11

The blog post "Rebasing Merge Commits in Git" (from Glen Maddern) illustrates the danger of a git pull --rebase when you have made local merges:

local merge

If you do a git pull --rebase of master on top of origin/master now, you would delete your local merge.
See the next picture after the rebase: no more merge commit.

no more merge commit

This is bad for a whole lot of reasons.

  • For one, the feature commits are actually duplicated, when really I only wanted to rebase the merge. If you later merge the feature branch in again, both commits will be in the history of master.
  • And origin/feature, which supposed to be finished and in master, is left dangling.
    Unlike the awesome history that you get from following a good branching/merging model, you’ve actually got misleading history.
    For example, if someone looks at the branches on origin, it’ll appear that origin/feature hasn’t been merged into master, even though it has! Which can cause all kinds of problems if that person then does a deploy. It’s just bad news all round.

That is what Github for (Mac|Windows) would detect and avoid.

If you didn't detect it in time, the same blog post mentions the following recover:

[master] git reset --hard origin/master
HEAD is now at 9f3e34d sneaky extra commit
[master] git merge --no-ff feature

Actual Solution:

You can achieve the desired result:

desired result

Instead of using git pull --rebase, use a git fetch origin and git rebase -p origin/master

I suppose the "smarter version" of pull --rebase is that combination of "fetch + rebase preserving the merge".

Glen also proposes the following aliases to counter the fact that this sequence of command would no longer use the tracking information associated to a local branch.

function git_current_branch() {
  git symbolic-ref HEAD 2> /dev/null | sed -e 's/refs\/heads\///'
}

alias gpthis='git push origin HEAD:$(git_current_branch)'
alias grb='git rebase -p'
alias gup='git fetch origin && grb origin/$(git_current_branch)'

Jason Weathered posted a "http://jasoncodes.com/posts/gup-git-rebase", but now refers to git-up, from Aanand Prasad.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    Wow, this is an awesome answer, and it's probably correct. But is there any way to tell for sure whether this is what the desktop Github client is doing? – Dan Fabulich Sep 25 '12 at 06:12
  • @DanFabulich: yes, simply make a merge between one of your local branches and `master`, then click on that "`Sync branch`" (provided your local `master` is behind `origin/master`), and check if your local merge is preserved. – VonC Sep 25 '12 at 06:22
  • 1
    Works just as you said. Wow, git's defaults are really terrible! – Dan Fabulich Sep 25 '12 at 08:34
3

Although @VonC’s answer is pretty darn useful, I wanted to mention of newer legit. Basically what legit does is a smart merge (much similar, if not equal, to what GitHub for Mac does):

  1. Check the log for merge commits with git log --merges branch..from_branch
  2. And if there’s any, then do the usual merge, otherwise do a rebase

git pull --rebase doesn’t work as per @VonC’s answer, whereas git rebase --preserve-merges is kinda better, but creates duplicate commits if you have merged other branches in; so you need to decide at some point if “rebase” is actually makes sense, or should you “merge” instead and that’s what legit does precisely (again: just like GitHub for Mac).

Ali
  • 1,396
  • 14
  • 37
  • Very interesting! +1 Maybe a good alternative to my (pure git) answer. – VonC Aug 30 '13 at 13:15
  • Yea, well… I am sure you could alias `sync` to some bash–foo which does the same basically. You don’t have to go the `legit` path at all. I just wanted to point it out to you guys, that’s all. Cheers! – Ali Aug 30 '13 at 13:18