Can I rebase a Git branch without modifying my working copy?

Since git 2.5, an even better solution is to use a second worktree.

A git repository can support multiple working trees, allowing you to check out more than one branch at a time.

$ git worktree add ../second-copy experimental
$ cd ../second-copy/
$ git rebase master experimental

And that's it. Afterwards, you can rm -rf second-copy if you want, or keep it for more rebases in the future.

$ git rebase master experimental

Is there a way to do all of the above in one step, without ever modifying anything in the working copy?

This is unfortunately impossible (without creating a modifiable copy of the working copy - see also Petr's answer), because git performs all merge-y operations (real merges, cherry-picks, rebases, patch application) on the work tree. This is mentioned several times before, for example in one of the knowledgeable Jakub Narębski's answers:

There is no way that merge (or rebase) can work without touching the working directory (and index), as there can be merge conflicts that have to be resolved using working directory (and/or index).

Yes, it's a design decision, but it's a pretty understandable one - it'd be a bit of a chore to build up all the structure necessary to attempt a merge in memory, then as soon as it hits a conflict, dump everything into the work tree, when instead you could simply do it in the work tree in the first place. (I'm not a git developer; don't take this as absolute complete truth. There could be other reasons.)

My suggestion, rather than writing a script to do all that mtime manipulation, would be simply to clone the repository, perform the rebase in the clone, then push it back into your original repository:

git clone project project-for-rebase
cd project-for-rebase
git branch experimental origin/experimental
git rebase master experimental
git push origin experimental

That of course assumes that experimental isn't checked out in your original repo. If it is, instead of the push, you'd do something like git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD or more readable, git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental. That will naturally touch whatever files differ between the original and rebased experimental branches, but that's definitely correct behavior. (This wasn't the example you gave, of course, but I want these instructions to be general!)


I've created a small script to do this on linux. It's based on Jefromi's answer and a few additions (mainly, setting up alternates so the object database isn't copied, and only pulling the needed branches). Some of you may find it useful: https://github.com/encukou/bin/blob/master/oot-rebase

If it doesn't do quite what you like, pull requests are welcome :)


Update, since git 2.5 this answer is superseded by the in-built mechanism "worktree" which is basically the same. See above answer: https://stackoverflow.com/a/12481546/1499102

Similar to creating a clone of your repository, I find that it's much tidier to make use of multiple workdirs to do things like this. Also a clone will use a lot of disk space, this will use almost none.

https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir

You create a new workdir like this:

git-new-workdir project-dir new-workdir branch

Then you can treat that as if it was a clone except that fetches and commits in your original workdir will be reflected here (although not on the working branch without recheckout). The only exception to this is if you have submodules as those are done separately for each workdir by default. Frankly I've never looked in to that because I try and avoid submodules.

So basically just:

cd new-workdir
git checkout experimental
git rebase master

Not exactly a single command, but pretty simple.

An advantage of this approach (like the clone approach) over the stash approach below is that if you have code currently executing (or otherwise being used by some processes) from your working directory, it isn't interrupted.


The other option which isn't mentioned here is to do it in your current working directory, but stash your changes so that you can then instantly restore your working directory state.

# current branch is master (with changes to working state)
git stash -u
git checkout experimental
git rebase master
git checkout master
git stash pop

Make sure to use stash -u if you have any new files as otherwise they will not be stashed. Again, not one step, but pretty clean and simple.


As others have said, it is not possible to rebase a branch without touching the working directory (even the suggested alternatives such as creating a new clone or worktree cannot change this fact; these alternatives do indeed not touch your current working directory, but only by creating a new worktree).

For the special case where the branch that you want to update is to be rebased on the current working tree (or a parent thereof), it is possible to "rebase" the other branch without unnecessarily touching files.
This special case often happens if you are having a git workflow where you are working on many branches that are all branched from the main "master" branch (which is regularly updated to the remote master branch).

To illustrate, assume a Git repository with the following structure:

repo
- commitA
- commitB
- commitC <-- master <-- OtherBranch based on master
  - commitD <-- First commit in otherBranch
  - commitE <-- Second commit in OtherBranch
- commitD <-- Unrelated commit in current working tree

For the sake of the example, let's assume that "OtherBranch" is branched off "master", and that your current working tree is also based on "master". Your workflow typically starts with updating your local master branch with the remote version...

# Fetch commits from remote "origin" and update the master branch:

# If your current branch is identical to master
git pull origin master

# If your current branch has extra commits on top of master
git pull --rebase origin master

# If you don't want to touch your current branch
git fetch origin master:master

... and then you fiddle with the current branch and do some time-consuming compilations. Eventually, you decide that you want to work on OtherBranch. This OtherBranch should be rebased on master (preferably with minimal filesystem operations). The following section will show how.

Rebasing other branch (reference example - do NOT do this)

The following solution is the git way to do it:

git checkout OtherBranch
git rebase master    # or git rebase origin/master

The disadvantage of that is that the first command changes the dates of the current worktree, even though the files are going to be restored by the second command.

Rebasing other branch with minimal changes

To minimize the number of touched files, you need to check out to the new base branch and then apply all extra commits in OtherBranch on top of the base branch using git cherry-pick.

Before doing anything, you need to identify the commits in OtherBranch.

  • git log OtherBranch shows the commits on OtherBranch (mainly useful if you haven't changed OtherBranch yet)
  • git reflog shows the changes to branches in your local repository (useful if you have already updated branches and made a mistake).

In the current example, you will discover that the last commit on OtherBranch is commitE. You can see a list of commits before that with git log commitE (or if you want a shorter list, git log --oneline commitE). If you look through the list, you will see that the base commit is commitC.

Now you know that the base commit is commitC and the last commit is commitE, you can rebase OtherBranch (from its previous "master" to the new "master") as follows:

# Replace the old OtherBranch with "master" and switch to it.
git checkout -B OtherBranch master

# Cherry-pick commits starting from commitC and ending at commitE.
cherry-pick commitC^..commitE

Alternatively (if you want to successfully complete the "rebase" before replacing OtherBranch):

# Create new branch NewOtherBranch based off "master" and switch to it.
git checkout -b NewOtherBranch master

# Cherry-pick commits starting from commitC and ending at commitE.
cherry-pick commitC^..commitE

# Replace the old branch with the current branch (-M = --move --force)
git branch -M OtherBranch

Why does this work?

Rebasing branches in git requires one to switch the current branch to the branch that you want to update (OtherBranch).

With the git rebase workflow, the following happens:

  1. Switch to OtherBranch (potentially branched off a very old base branch).
  2. Rebase (internal step 1): Save commits that are not in the upstream branch.
  3. Rebase (internal step 2): Reset current branch to the (new) base branch.
  4. Rebase (internal step 3): Restore commits from step 2.

Step 1 and step 3 touch many files, but ultimately many of the touched files have not actually changed.

My method combines step 1 and 3 into step 3, and as a result the number of touched files is minimal. The only files that are touched are:

  • Files that were changed between the base branch and the current commit in the current working tree.
  • Files that are changed by the commits in the OtherBranch.