Cherry-pick and squash a range of commits into a subdirectory or subtree
Asked Answered
B

5

130

How can I tell cherry-pick to pick range of commits and squash it?

Or in other words, apply the diff between two commits to the current state of the repository?

The following does not work (cherry-pick has no --squash option):

git cherry-pick --squash e064480..eab48b59c

Note: My use case is within a subtree scenario - before anyone starts arguing that I should not squash.

The following works, but then I have a range of separate commits. I can squash them manually with interactive rebase afterwards.

git cherry-pick -X subtree=vendor/package e064480..eab48b59c

Is there any way to do the squashing as part of the cherry-pick?

Brnaby answered 1/2, 2016 at 4:41 Comment(0)
J
182

Pass -n to git cherry-pick. This will apply all the commits, but not commit them. Then simply do git commit to commit all the changes in a single commit.

Jim answered 1/2, 2016 at 12:4 Comment(4)
what if there are multiple conflicts within range of cherry-picked commits?Dotdotage
@SazzadHissainKhan, while I'm pretty sure it would work the same way (i.e. you resolve each conflict, then use git cherry-pick --continue), it is probably safest to leave out the -n in that case, so that after the operations are complete you can look at each individual commit and confirm all is well. Then, you can squash them via an interactive rebase.Jim
In my case, I had cherry-picked a commit already, and wanted to squash another commit that fixes a typo introduced with the first commit. Because I hadn't cherry-picked the second commit, I cherry-picked it with the -n option (--no-commit), then edited the first commit with git commit --amend --no-edit (the no-edit option re-uses the commit message of the first commit and not prompt for a new one).Derive
@DavidDeutsch is wrong. If you pick commits with --no-commit, get a conflict, resolve it and stage the files, git cherry-pick --continue will not work: error: your local changes would be overwritten by cherry-pick. hint: commit your changes or stash them to proceed. fatal: cherry-pick failed. If you commit the changes to continue, then you'll end up with commits that you didn't want (plus the staged changes in the index that had no conflicts). If you stash them then you ignore the conflicting changes (could apply them from stash later, but with more & harder to resolve conflicts)Derive
N
9

Follow from master → cherry pick two commit from another branch

git checkout master
git cherry-pick :1  
git cherry-pick :2
git reset --soft HEAD~2 (number of cherry pick commits, i.e 2 ) 
git add . 
git commit
Nutrilite answered 22/9, 2020 at 9:28 Comment(2)
This is an underrated answer! Why? What if each cherry-pick has merge conflicts? This technique works very well.Cyprinodont
This is beautiful!! Chery pick all you want, then move the head to before the cherry-pick allows you to include the cherry pick in your code as to what's changed. Amazing!!!Motteo
P
9

Useful alternative vs git cherry-pick -n for some use cases might be git merge --squash - for example, when you want to test a feature branch's changes on top of your integration branch, without rebasing.

Source: What's the difference between git merge --squash and git cherry-pick?

Proudlove answered 14/10, 2020 at 17:6 Comment(0)
B
6

From this 2022 discussion, there are other approaches:

  • git diff A...B | git apply --3wa
    But,

    • it does not show the commit messages,
    • you would not be able to use a graphical merge tool that shows the entire source file.
      When using "git cherry-pick" and there are conflicts, you can see the change in the context of the entire file.
  • Johannes Sixt suggests an interractive rebase

    To transplant the range A..B of the history on top of HEAD, for example, I'd start with (notice ^0 after B, as I do not trust myself so I'd leave the true branch B untouched as I may make mistakes while running rebase):

    $ git checkout --detach HEAD ;# this is only to use @{-1} later
    $ git rebase -i --onto HEAD A B^0
    

    Then if my goal is to squash everything down into a single commit, then replace all 'pick', except for the first one, to 'squash'.
    That will give me one single chance to edit a single commit message, but the editor buffer starts with the log message from all of the original, so I can pick good bits from them while writing new stuff. I'll have the result on detached HEAD.
    If I like the result, I may update the branch I was originally on with it.

    $ git checkout -B @{-1}
    

    Or, if I don't, perhaps because I made mistakes, then I can just discard it and go back to the original branch.

    $ git checkout @{-1}
    

However, Noam Yorav-Raphael objects:

My main problem with using "rebase -i" is that it would require me to fix merge conflicts one by one, on each commit in which they appear, instead of fixing all conflicts at once, treating the change from A to B as one.
It also requires manual editing for every commit between A and B.

Noam proposes:

I think that the best way to do what I want using the existing commands is:

git checkout A
git merge --squash B
git commit --no-edit
git checkout @{2}            # Go back to where we were at the beginning. 
                             # This is not exact, as you're in detached HEAD state.
git cherry-pick --edit @{1}  # cherry-pick the squashed commit A..B

This allows you to fix the merge conflicts in one go, shows the entire files causing the conflicts, and allows you to edit the commit message, starting with the descriptions of all the squashed commits.

I think this also gives a pretty good explanation of what "cherry-pick --squash" will do: it really is the analog of the "merge --squash", but for cherry-pick.

Bufford answered 26/8, 2022 at 20:13 Comment(1)
git diff A...B | git apply --3way is exactly what I was looking for, at least!Realty
W
4
  1. Use git cherry-pick --no-commit <commit>…​.
  2. Resolve any conflicts.
  3. Commit.
  4. Continue with git cherry-pick --continue.
  5. Resolve any conflicts.
  6. Now amend changes to the previous commit.
  7. While git status shows Cherry-pick currently in progress. loop from step 4.
Wolfram answered 13/7, 2022 at 7:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.