89

Here's an example:

>git status
# On branch master
nothing to commit (working directory clean)
>git checkout -b test-branch
>vi test.c
>git add test.c
>git commit -m "modified test.c"
>vi README
>git add README
>git commit -m "modified README"

Now I want to do a 'git rebase -i' that will let me rebase all commits for this branch. Is there something like 'git rebase -i HEAD~MASTER' or similar. I figure I could do 'git rebase -i HEAD~2', but I really don't want to have to count how many commits have been made. I could also do 'git rebase -i sha1' but I don't want to comb through git log to find the first commit sha1. Any ideas?

semmons99
  • 1,134
  • 1
  • 9
  • 13
  • 1
    Please title your question a little better. Perhaps mention you want to do an interactive rebase for all changes in a branch. Preferably in the form of a question (though not always possible). – Dustin Dec 12 '08 at 20:58
  • Do you want to rebase onto a modified `master` or just edit the commits you just made on `test-branch`? – DylanYoung Nov 10 '17 at 18:12

8 Answers8

97

Ok, I'm asuming the branch is called "feature" and it was branched from "master".

There's this little git command called merge-base. It takes two commits and gives you the first common ancestor of both of those. So...

git merge-base feature master

...will give you the first common ancestor of those two commits. Guess what happens when you pass that commit to git rebase -i, like...

git rebase -i `git merge-base feature master`

Interactive rebase from the first common ancestor of both master and feature branch. Profit! ;)

DanNetwalker
  • 1,089
  • 7
  • 3
  • 4
    It's ugly though - is there no syntactic sugar on hand? – Alex Brown Aug 30 '12 at 17:14
  • 25
    That's pretty compared to a lot of git solutions :). – studgeek Nov 19 '12 at 06:58
  • 11
    I would suggestion to use `git merge-base master HEAD` which should always work for the current branch without typing out the current branch name. Alias this command and you've got your nice short git command. – jayeff Sep 25 '13 at 13:19
  • 6
    `git rebase -i $(git merge-base @{u} HEAD)` -- that's assuming that your current branch is set to track the base branch. Example: `git branch feature1 origin/master` would track origin/master. So now you don't even have to type that. – Alexander Bird May 13 '16 at 17:25
  • `@{u}` doesn't work when pushing the new branch with `-u` (which is needed to able to pull it again, but changes the upstream) Example: Create a branch and switch to it `git branch feature1 origin/master && git switch feature1`. The current Upstream is `[origin/master]`. Push the branch `git push -u`. The current Upstream is `[origin/feature1]`. – Philippe Aug 12 '21 at 10:52
72

The issue with all provided solutions is, they do not allow you to rebase from the very first commit. If the first commit hash is XYZ and you do:

git rebase -i XYZ

You only rebase starting from the 2nd commit.

If you want to rebase from the first commit you do:

git rebase -i --root
Joost den Boer
  • 4,556
  • 4
  • 25
  • 39
54

Have you tried: git rebase -i master?

d-_-b
  • 21,536
  • 40
  • 150
  • 256
ididak
  • 5,790
  • 1
  • 20
  • 21
  • 21
    This fails if the master is ahead of the current merge base in your branch. – Alex Brown Aug 30 '12 at 17:14
  • 6
    The problem with `git rebase -i master` is that you may have merge conflicts that you don't necessarily want to deal with at the moment, or you may fix a conflict in one commit, only to fix it again in another commit during the course of the rebase. I've added an answer that provides an alternative to this, and an alternative to specifying the exact commit, or number of commits back that you want to rebase from. – Seth Flowers Jun 24 '15 at 20:47
  • `rerere` is your friend whenever you're rebasing. – DylanYoung Nov 10 '17 at 18:01
  • Doesn't work for me, it results in weird merge conflicts after me doing… nothing, literally! I mean, all I did `git rebase master` *(no `-i` just for tests)*, and it already results in conflicts. – Hi-Angel Sep 17 '18 at 14:30
21

Use gitk (*nix), or gitx (OS X) or similar on other platforms, and have a look at which commit was the root of your branch. Then run:

git rebase -i <the SHA hash of the root commit>

For example, I have a repository that I inspected using gitx:

gitx screencap

Now that I know the root hash I can run this:

git rebase -i 38965ed29d89a4136e47b688ca10b522b6bc335f

And my editor pops up with this and I can rearrange/squash/whatever as I please.

pick 50b2cff File 1 changes.
pick 345df08 File 2 changes.
pick 9894931 File 3 changes.
pick 9a62b92 File 4 changes.
pick 640b1f8 File 5 changes.
pick 1c437f7 File 6 changes.
pick b014597 File 7 changes.
pick b1f52bc File 8 changes.
pick 40ae0fc File 9 changes.

# Rebase 38965ed..40ae0fc onto 38965ed
#
# Commands:
#  pick = use commit
#  edit = use commit, but stop for amending
#  squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

I'm sure there's some magic way to convince git to figure out the root of the tree automatically, but I don't know what it is.

EDIT: That magic is this:

git log master..other_feature | cat

Which will show you all the commits on that branch, and piping to cat will disable the pager so you see the first commit immediately.

EDIT: combining the above gives a fully automated solution:

git rebase -i  `git log master..other_feature --pretty=format:"%h" | tail -n 1`~
Laurel
  • 5,965
  • 14
  • 31
  • 57
Otto
  • 18,761
  • 15
  • 56
  • 62
  • 1
    `| cat` seems like a useless use of cat when you can use `git --no-pager`. – Matthieu Moy May 05 '15 at 06:06
  • @MatthieuMoy --no-pager probably wasn't in the version I was using in December 2008. That option hadn't landed in the got codebase until July 2008. https://github.com/git/git/commit/4e10738a9392ad285aca7d3a19e6775d6b7b513e – Otto May 05 '15 at 13:27
  • @Otto Ah, I didn't notice the date of your post. You meant to point to https://github.com/git/git/commit/463a849d004b2a3f3c8254b1e7985d4e986b9f1a actually. – Matthieu Moy May 05 '15 at 14:31
7

The OP's problem as I understand it

This answer applies if you don't actually want to rebase on top of master, but you'd rather just squash all commits into one, since diverging from master.

The problem with rebasing from a different branch

The problem with git rebase -i master is that you may have merge conflicts that you don't necessarily want to deal with at the moment, or you may fix a conflict in one commit, only to fix it again in another commit during the course of the rebase.

The problem with rebasing from a known commit

The whole problem here is that you have to know which commit you have to refer to, either by its SHA, or HEAD~x, etc. This is only a minor annoyance but it is an annoyance.

The better way

If you instead want to rebase all the commits in your current branch, since the most recent commit it shared with its parent branch, you can add the following alias to .gitconfig:

rbi = !sh -c \"git rebase -i `git merge-base $1 HEAD`\" -

Usage

git rbi parentBranch

You can then use an interactive rebase to pick/reword/edit/squash/etc all commits in your branch, since it diverged from the parent branch. My typical flow is to pick the oldest commit (the one at the top), and set all other commits to f (or fixup).

How it works

This alias is just a shell script, that is using an argument which refers to the parent branch. That argument is passed into git merge-base in order to determine the most recent shared commit between that branch, and the current branch.

Seth Flowers
  • 8,990
  • 2
  • 29
  • 42
  • I'm not sure this answer makes much sense at all with the given topology. The merge base here will be `master` at the time `test-branch` was created (say `3hgn45`). So this rebase actually doesn't do anything. It says rebase this branch from (but not including) `3hgn45` to (and including) `HEAD` onto `3hgn45`. But maybe I'm misunderstanding your suggestion... – DylanYoung Nov 10 '17 at 18:08
  • EDIT: I get it; the OP's question just wasn't clear to me. You might consider adding a warning that this won't maintain changes from `master` (i.e. it's not really rebasing, since you're maintaining the same base, just editing some commits on the current branch). – DylanYoung Nov 10 '17 at 18:14
  • 1
    @DylanYoung - yes, my understanding of the OP's question was that they didn't actually want to rebase on top of `master`, but just squash all commits into one, since diverging from `master`. – Seth Flowers Nov 13 '17 at 15:32
  • Here is a similar "rebase common ancestor" alias that allows additional arguments: `rbca = "!git rebase $(git merge-base HEAD \"$1\") ${@:2} #"` – cambunctious Oct 27 '18 at 20:37
4

Since Git v1.7.10, you can just run git rebase without argument, and it will find the fork point and rebase your local changes on the upstream branch.

You need to have configured the upstream branch for this to work (i.e. git pull without argument should work).

For more details, see the docs for git rebase:

If is not specified, the upstream configured in branch..remote and branch..merge options will be used (see git-config[1] for details) and the --fork-point option is assumed. If you are currently not on any branch or if the current branch does not have a configured upstream, the rebase will abort.

Matthieu Moy
  • 15,151
  • 5
  • 38
  • 65
2

A general solution (if you don't know the name of the upstream branch) is:

git rebase -i @{upstream}

Note that if your upstream (probably a tracking branch) has updated since you last rebased, you will pull in new commits from the upstream. If you don't want to pull in new commits, use

git rebase -i `git merge-base --all HEAD @{upstream}`

but that is a bit of a mouthful.

Alex Brown
  • 41,819
  • 10
  • 94
  • 108
  • I have seen suggestions that the symmetric difference HEAD...master (which yields the merge base as the negated third sha) can be used in git rebase, but I can't work out how. – Alex Brown Aug 30 '12 at 17:49
1
git rebase -i --onto @{u}... @{u}

Interactive rebase starting from the single merge point of HEAD and its upstream including all commits in HEAD that are not in its upstream.

In other words exactly what you want.

bbex
  • 41
  • 2