74

So I'm working on a project that sometimes has long build times, and the build is clobbered very often. If I have an older branch with some work going on (which has been committed, but is based on an older parent), running git checkout oldbranch changes the working dir to represent all the old code, which makes me need to run a full build again.

However, usually I've only modified one or two files, and the rest don't need to be reset. What I'd like to do is to rebase this branch to the current master head, and preserve those changes to the files.

Basically, if a.rs and b.rs have been modified, then I need a way of making these changes base themselves onto the current head, without touching any files other than those two.

Is there a git-ish way of doing this? Currently I'm juggling patch files to do this.

Manishearth
  • 14,882
  • 8
  • 59
  • 76
  • This makes no sense. If the old branch has "work going on", why isnt it already checked out? – Zombo Apr 06 '14 at 04:26
  • 8
    @StevenPenny Because I may have switched to another branch for doing more work? The work is committed, fyi. Just that upstream got updated, and I'm working on another fix in a more recent branch. – Manishearth Apr 06 '14 at 04:36
  • Why do you need to rebuild all the code if you're doing a checkout? Did you commit the compiled binaries or something? – Leigh Apr 06 '14 at 04:46
  • 2
    Just do the work in a clone and push the results back. `git clone -b oldbranch . ../oldbranch; cd !$; work work commit commit lalala; git push origin oldbranch; cd -` – jthill Apr 06 '14 at 04:46
  • @Leigh A checkout bumps the timestamps of the rest of the code. In some cases, it clobbers certain large dependencies by touching a status file. I can tweak the makefile code to bypass this, but I'd rather not do that – Manishearth Apr 06 '14 at 04:52
  • @jthill that would work, but it's not really ideal, is it? – Manishearth Apr 06 '14 at 04:52
  • 2
    Why not? You've got a worktree you don't want to interfere with, use another one. One thing -- do you know about `git cherry-pick`? It's the converse of `rebase`, it grabs work from other commits and applies it locally. – jthill Apr 06 '14 at 04:56

5 Answers5

74

Going through the same right now I learned git rebase allows you to specify two branches in one go, essentially making it git rebase <remote> <local>, eg.

git rebase origin/master dev

This performs a more efficient rebase where your files don't get all re-written (which is the case if you checkout the branch first). You still need to resolve merge conflicts first and you end up with a repository where your local dev branch is checked out.

Tomasz W
  • 1,841
  • 1
  • 16
  • 25
  • 1
    Note that this can be combined with the `--onto` flag. Say you accidentally created a feature branch off of the `master` branch in your `master` worktree instead of off the `release` branch in your `release` worktree. `cd ../release-branch-worktree; git rebase --onto origin/release origin/master your_feature_branch` Voilà. The work has been moved with only the minimum necessary recompilation. – Parker Coates Jan 03 '20 at 13:09
  • 16
    Not sure if this really makes a difference. The documentation says _"If is specified, git rebase will perform an automatic git checkout before doing anything else."_ – Andreas Haferburg Feb 18 '20 at 14:33
  • 1
    It helps, because otherwise you'd have to `git checkout dev`, then `git rebase` (which will do another checkout, this time of upstream). The `checkout dev` serves only to tell git which branch to rebase, and if it's far back in history it can touch all sorts of files for no good reason. – Paul Du Bois Jan 12 '21 at 01:54
  • 2
    If you removed a git submodule somewhere between the rebase target and your branch, checking out will(may) just outright fail. The approach in this answer worked to rebase the historic branch onto current master without fixing the checkout issues due to submodule removal. – Irfy Sep 01 '21 at 20:32
5

This sounds like a good use case for git cherry-pick. Instead of checking out the branch and rebasing it onto your current branch, you can stay on the current branch and cherry-pick commits from the old branch.

If the branch you're cherry-picking from only consists of a single commit, you can even refer to it by its branch name, eg:

git cherry-pick old-branch

ie. take the most recent commit from old-branch and apply the changes to create a new commit (preserving the commit message, author, etc) on your current branch.

Nick F
  • 9,781
  • 7
  • 75
  • 90
3

I checked the source code and unfortunately it looks like it always checks out the base commit so unfortunately I think the only way you could do this would be to clone the repo into another directory, rebase it there and then push the changes back into the original repo, and finally delete it.

Note that cloning a local repo to elsewhere on the same file system uses hardlinks for the files under .git so it's not actually as slow as you'd think.

Edit: actually a better alternative is to use a worktree, something like this:

cd <your repo>
git worktree add ../tmp <branch to rebase>
cd ../tmp
git rebase master
cd -
git worktree remove tmp

Note that you can only have a branch checked out in one worktree at a time.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
1

NOTE: For the straightforward solution for the exact problem in the question look for the answers below, they are simpler.

For the more general problem of "I want to make some changes without messing up my working directory", keep reading.

See the comments about git worktree add if your repo is very big.


Clone the repo, make rebase in cloned copy, and push it back.

If you are inside your repo, it should be:

cd ..
git clone <name_of_your_repo_directory> tmp_repo
cd tmp_repo
git checkout origin/oldBranch
git rebase origin/master
git push -f origin HEAD:oldBranch

In tmp_repo origin is name of your local repo, of course (the one you cloned).

Note: the effect will be such as doing

git checkout oldBranch
git rebase master

in your original repo, not as

git checkout oldBranch
git rebase origin/master
Frax
  • 5,015
  • 2
  • 17
  • 19
  • I now see that @jthill already suggested that, and it was yesterday. I overlooked that, sorry. However, my answer is a bit longer, and with full code, so i hope it may be useful fore someone. – Frax Apr 07 '14 at 12:11
  • 3
    Alternative to cloning you can use `git worktree add ../newFolderName branchToCheckout`. – kerhac Jul 30 '19 at 13:38
  • Clone all the project can be slow! rebase solution is better. – Isaac Pascual Feb 20 '20 at 11:21
  • 1
    @IsaacPascual Well, for most code repos cloning works instantly (and I think for local project it would use some hardlinking optimization to be even faster than full copy), so it's not necessarily an issue - and once you have the clone, it's quite a bit more versatile than using the rebase - you can do virtually anything there, without touching any files in your working directory. OTOH, if your repo is indeed that big, `git worktree add` solution solves it as well. – Frax Feb 20 '20 at 22:22
  • @Frax True. I dont see that is a local copy. Anyway rebase solution is better cause OP want to do a rebase, work without copy anything, easy to remember, more adapted to git interface,... – Isaac Pascual Feb 26 '20 at 08:35
  • this is way more complex than checkout – caoanan Sep 01 '20 at 06:28
  • 1
    @caoanan, Of course it is. The OP explicitly asked for a solution that didn't use checkout. – Frax Sep 01 '20 at 08:57
  • It's a bit upsetting to be downvoted for a correct answer. I get it, someone posted a better answer 5 years later, but that doesn't invalidate this solution. Also, it is still way more flexible and solves a more general "I need to change history without messing up the working directory" problem. – Frax Sep 01 '20 at 09:01
  • git worktree is way better for OP's scenario. Ps. care so much about other's up and down? – caoanan Sep 02 '20 at 09:30
1

if you have

-A---o---o---o---o---D
 \---B---o---C

and D is checked out (which I believe is your situation), you can

git cherry-pick B..C

to reach this:

-A---o---o---o---o---D---B---o---C

with C now being head, and without touching the files changed made in A..D

peterchen
  • 40,917
  • 20
  • 104
  • 186