6

I have reviewed several related questions about squashing the most recent commits and squashing a commit at the root, but neither will help me squash non-recent commits that are not at the root.

Here is my starting scenario:

D---E---F---G---H---I---J master

and my desired result:

D---E---Z---I---J master

where Z is the squash of F---G---H, and F---G---H, D---E, and I---J can be an arbitrarily long sequence of non-branching commits.

First approach:

[lucas]/home/blah/$ git rebase -i D
rebase in progress; onto D
You are currently editing a commit while rebasing branch 'master' on 'D'.

No changes
You asked to amend the most recent commit, but doing so would make
it empty. You can repeat your command with --allow-empty, or you can
remove the commit entirely with "git reset HEAD^".

Could not apply F... "Comments from commit F"

[1]+  Done                    gitk
[lucas]/home/blah/$ 

where I select commits F---G---H to be squash, while leaving the oldest commit - the first line in the rebase interactive - as pick. Why doesn't this work?

Update: at the end of the command, a rebase is in progress on D with E being the HEAD commit. To be sure, there was no rebase in progress at the start and calling git rebase --abort while running again has the same result. When I do this at root, or HEAD, according to the links above, everything works fine.

Second approach:

I made another attempt [via merging a new branch (last post on the forum)][http://git.661346.n2.nabble.com/Non-interactive-squash-a-range-td5251049.html) which uses git checkout -b <clean-branch> <start-id> andgit merge --squash `, but I get the following:

[lucas-ThinkPad-W520]/home/.../.Solstice_WS/7K_FGHF$ git checkout -b clean-branch D
Switched to branch 'clean-branch'
[lucas-ThinkPad-W520]/home/.../.Solstice_WS/7K_FGHF$ git merge --squash I
Updating D..I
Fast-forward
Squash commit -- not updating HEAD
 .../GraphUtilities/impl/DAG.java              | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)
[lucas]/home/blah/$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
        asdf/GraphUtilities//DAG.java
Please, commit your changes or stash them before you can switch branches.
Aborting

which seems to have this result:

  -------------------  <clean-branch> with non-committed changes
 / 
D---E---F---G---H---I---J <master>

I am a bit stumped, so how can I squash these commits?

Ultimately, I plan to implement this in JGit, so a JGit implementation would be acceptable as well.

NOTE

There may be a duplicate here, but it has no answers and I think the question is a bit unclear.

UPDATE

This is in response to @ryenus's answer below:

The cherry-pick fails on the commit I2, where I2 is in I---I2---J. When it fails, the state of my work branch has D---E---Z, as intended up until the cherry-pick, and it is followed by uncommitted changes. Calling git cherry-pick --abort clears these uncommitted changes, and I verified that commit Z is correct, which is the squash of F---G---H. After committing Z, then cherry-picking, why does the cherry-pick fail at F?

It seems as though git cherry-pick I...J is trying to cherry-pick I2, which creates the merge conflict and fails. Any suggestions?

Here is my output:

[lucas]/home$ git checkout -b work H
Switched to a new branch 'work'
[lucas]/home$ git reset E             
Unstaged changes after reset:
M       adf/GraphUtilities//graph/impl/DAG.java
[lucas]/home$ git commit -am "squashed commit here!"
[work Z] squashed commit here!
 1 file changed, 2 insertions(+), 5 deletions(-)
[lucas]/home$ git cherry-pick I...J
error: could not apply I2... <Comments from commit I2>
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
[lucas]/home/$ git status
On branch work
You are currently cherry-picking commit I2.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:      3b6863741967406c1888701eb139178187d429487b99787096441d67bed56/Gra
phUtilities/src/edu/washington/cs/utils/graph/impl/DAG.java

no changes added to commit (use "git add" and/or "git commit -a")
[lucas]/home$ 
Community
  • 1
  • 1
modulitos
  • 14,737
  • 16
  • 67
  • 110
  • 1
    Your first approach seems to be the right start, but it looks like git thinks you're in the middle of a rebase. Counterintuitively, rebases and merges are stateful, and git can cede control back to you before a merge/rebase is finished. Try `git rebase --abort` and then do that command again. – Jeff Bowman Sep 25 '14 at 03:42
  • It looks more like the output of `git status` if he specified "edit" for one of the commits. Git will say `I wonder if you are in the middle of another rebase` if you type `git rebase` while a rebase is in progress. – Andrew C Sep 25 '14 at 03:45

1 Answers1

6

First of all, if F---G---H are squashed, then I---J would be I'---J'.

Given that, I'd create a branch first then use vanilla git reset then git cherry-pick:

# D---E---F---G---H---I---J master
  1. git checkout -b work H

    create branch work at H

  2. git reset E

    now work is at E

  3. git commit -am "F-G-H squeezed as Z"

    commit the changes done by F---G---H as Z

  4. git cherry-pick I^..J

    take over I---J

Now the work branch is in the shape you want, just reset master to it:

# D---E---Z---I`---J` work

git checkout master
git reset work
git branch -d work

Note: the commit range has been corrected to I^..J, to represent the commit series I---J (from I to J, inclusive).

ryenus
  • 15,711
  • 5
  • 56
  • 63
  • Interesting approach, but the cherry-pick is failing. It appears to be trying to cherry-pick `F` onto `work` after `Z` has been committed. Details are updated in my answer above. – modulitos Sep 25 '14 at 07:04
  • @Lucas, please try `git cherry-pick I^..J`, if it works I'll update the answer. – ryenus Sep 25 '14 at 07:23
  • After reviewing Git's [revision selection](http://git-scm.com/book/en/Git-Tools-Revision-Selection), this makes sense now. Thank you! Also, the cherry-pick was not failing at `F` but at `I2` where `I2` is in `I---I2---J` (I updated my post accordingly) – modulitos Sep 25 '14 at 07:51
  • Also, would this solution be suitable if `I---J` was a very long sequence of non-branching commits? Say, around 1000 commits? – modulitos Sep 25 '14 at 07:54
  • 1
    @Lucas, yes, it should just works, because here `H` and `Z` have the same content, thus all following commits should apply perfectly. – ryenus Sep 25 '14 at 08:02
  • As you showed above, this also rewrites the commits `I---J` to `I'---J'`. Is it possible to preserve the original commits as well? Can we replace `F--G--H` on the `master` branch with `Z`? Perhaps there is a way to delete `F--G--H` on `master` then cherry-pick `Z` into the same spot? I can make this a separate question, but let me know what you think. – modulitos Sep 25 '14 at 22:11
  • 1
    @Lucas, no, commit hash is a result of the commit itself and its parent commit. With a different parent commit, the new commit would not have the same commit hash. But except the commit hash, `I'---J'` are just the same as `I---J`. – ryenus Sep 25 '14 at 23:28