Let’s all take a moment of silence… git pull is dead.

Posted on

Well… git pull has been on my mind for a while.

This is great however :

Rebasing deletes merge commits!

So this is explained in great detail here the example below is a summary from envato notes

  • Below is a shortened version of the scenario in which git rebase origin/master or git pull --rebase could BITE YOU HARD

Ok, here is a situation where using git rebase origin/master or git pull -rebase could delete your merge commit and annoy you.

It could also be quite BAD if you don’t notice it, because when someone looks at your history later it will look like that branch was never merged.

  • you have been doing the below just fine for a while
$ [master] git pull --rebase
First, rewinding head to replay your work on top of it...
Applying: little fix
Applying: forgot to push this

then this

$ [master] git push
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 526 bytes, done.
Total 5 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (5/5), done.
To /path/to/your/repo-origin
   f9c3cb8..e4a2e92  master -> master
  • And all is well.

  • Suddenly one day you have been working on a feature branch for a while and you merge to master using --no-ff of course.

[master] git merge --no-ff feature
Merge made by recursive.
 b |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 b
  • but, when you go to push, someone has already pushed before you. So, git pukes and says
[master] git push
To /path/to/your/repo-origin
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '/path/to/your/repo-origin'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again.  See the
'Note about fast-forwards' section of 'git push --help' for details.
  • so without hesitation we of course do
[master] git fetch
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /path/to/your/repo-origin
   49ab1cf..9f3e34d  master     -> origin/master
  • followed by
[master] git pull --rebase
First, rewinding head to replay your work on top of it...
Applying: my work
Applying: my work
Applying: my work

now our merge commit from our feature branch we were trying to push to master has disappeared! Why?! WTF?! well…

  • This is how git rebase works.

  • so we do the following to rescue our merge commit

[master] git reset --hard origin/master
HEAD is now at 9f3e34d sneaky extra commit
[master] git merge --no-ff feature
Merge made by recursive.
 b |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 b

So what to do? Luckily, git rebase -p comes to save the day.

-from the git man page

-p
--preserve-merges
Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it with the --interactive option explicitly
is generally not a good idea unless you know what you are doing (see BUGS below).
  • basically don’t use --interactive flag with the -p flag or weird things will start to happen.

So instead of doing a git pull –rebase you do the following instead

[master] git fetch
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /path/to/your/repo-origin
   49ab1cf..9f3e34d  master     -> origin/master

and then

[master] git rebase -p origin/master
Successfully rebased and updated refs/heads/master.
  • and Voila’ your up to snuff and your precious merge commit from feature branch is preserved!
  1. Caveats of using this method.
  2. ORIG_HEAD is no longer preserved git rebase -p sets ORIG_HEAD for each commit being rebased, so you can’t use it to quickly return to the start of a rebase.
  3. Branch tracking is not used
    Unlike git pull –rebase, which will fetch changes from the branch your current branch is tracking, git rebase -p doesn’t have a sensible default to work from. You have to give it a branch to rebase onto.

Aliases for bash, fish, and zsh to make this painless

here is the OG gist

You can download this gist from your shell using wget or curl like this

-for curl

$ curl -LJO https://gist.github.com/geelen/590895/download

-or if you just want a certain file out of the gist use it like below

$ curl -LJO https://gist.github.com/geelen/590895/raw/ad
75e7bfd9eb08704e90413cf6cad9a4f07f2a71/bash_or_zsh.rc
  • Now for wget
$ wget --no-check-certificate --content-disposition https://gist.github.com/geelen/590895/download

or for a single file from the gist

$ wget --no-check-certificate --content-disposition https://gist.github.com/geelen/590895/raw/ad75e7bfd9eb08704e90413cf6cad9a4f07f2a71/bash_or_zsh.rc
  • here are the aliases for for bash and zsh if anyone is just in the copy paste mood. Place these in your .bashrc for a non-login shell or for a login shell your .bashprofile

  • Edit git_current_branch function below now more efficient thanks to @jussi-kalliokoski 😉

function git_current_branch() { git symbolic-ref --short HEAD 2> /dev/null; }

alias gpthis='git push origin HEAD:$(git_current_branch)'
alias grb='git rebase -p'
alias gup='git fetch origin && grb origin/$(git_current_branch)'
alias gm='git merge --no-ff'
  • gup will do a fetch of origin and rebase -p of the branch on origin with the same name as the current branch.

  • So now you can just type

$ gup
  • and enjoy.

  • I will be writing a series of protips on git. Keep on the lookout.

Credit and thanks for the git revelations and aliases to @glenmaddern

Leave a Reply

Your email address will not be published. Required fields are marked *