5

How is it possible that when trying to squash/fixup a linear branch I still have to do manual merges? The repo has been converted from Subversion. Every conflict is either "Automatic cherry-pick failed" or "Aborting commit due to empty commit message". The latter I could understand, but a --fixup-empty or something would be useful.

Typical output:

user@machine:/path (master|REBASE-i)$ git add * && git rebase --continue 
[detached HEAD c536940] fixup!
 Author: John Doe <John.doe@example.com>
 2 files changed, 57 insertions(+), 4 deletions(-)
Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply 8854a54... >6d5f180 foo
user@machine:/path (master|REBASE-i)$ git st
# Not currently on any branch.
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#   both modified:      filename.ics
#
no changes added to commit (use "git add" and/or "git commit -a")
l0b0
  • 55,365
  • 30
  • 138
  • 223
  • Promise it's purely in-place squash, not reordering commits or transplanting the branch? – Cascabel Jul 30 '10 at 15:34
  • Yep. Just trying to put lots of commits into one so I can save a bit of space. – l0b0 Jul 30 '10 at 15:53
  • Hm. What sort of conflicts? If they're whitespace-related... I know rebase won't combine --whitespace with --interactive, but maybe apply.whitespace is set in your config? – Cascabel Jul 30 '10 at 15:58
  • Nope, somehow I always get a three-way merge (why "local" and "remote"?) with what seems like the previous and current commit. I'm trying to get through this hell by repeating `git add * && git rebase --continue` and :x Enter 2000+ times. It seems to do the trick :( – l0b0 Jul 30 '10 at 16:04
  • If you can just add the files and continue, then presumably there aren't actually any conflicts in them. What does git status say? Deleted by us or them? – Cascabel Jul 30 '10 at 16:14
  • Well if both modified it, are there conflict hunks in the file? (If there are, then blindly proceeding by adding the file is a pretty bad idea...) – Cascabel Jul 30 '10 at 16:27
  • What do you mean? This happens even a thousand commits after the last squashed commit. And if one commit follows another, and they're not squashed, why does it even check for conflicts? – l0b0 Jul 30 '10 at 17:14
  • 1
    Rebase works by successively applying patches. If the first thing you do introduces a change, or even if it somehow has an erroneous conflit, and you commit that change, then any commits after it will be applied to a tree different from the one they originally applied to. This could mean that each of them now conflicts as well. – Cascabel Jul 30 '10 at 18:14
  • I think the problem is some dangling commits or diverging histories without branches (another one of the mysteries of Git). So I guess the main problem now is linearizing the history. – l0b0 Jul 30 '10 at 19:16
  • @I0b0: Wait, is the history linear or not? A commit which is not on the branch being rebased shouldn't have any effect. – Cascabel Jul 30 '10 at 19:26
  • @Jefromi: Well, it's linear in gitk up until way beyond the point where the conflicts start. – l0b0 Aug 02 '10 at 08:02

3 Answers3

2

Here's my suggestion for achieving your idea of having some kind of --fixup-empty functionality:

git filter-branch --msg-filter "sed 's/^$/Unknown/'"

This replaces empty commit messages with 'Unknown' and is particularly useful if you want to do a rebase after using git-svn to convert a Subversion repository that had some empty commit messages but can't because it fails with "Aborting commit due to empty commit message".

Beau
  • 11,267
  • 8
  • 44
  • 37
2

These work:

git mergetool
git rebase --continue
l0b0
  • 55,365
  • 30
  • 138
  • 223
0

I ran into this same puzzling question. I wanted to turn the changes from a whole bunch of sequential commits into a single commit, but using interactive rebasing with a sequence of squashes was frustratingly giving merge conflicts. This was a while back, and I was not able to reproduce it in a minimal example just now. In any case, I do know how to solve it, so here you go:

I'll present two different ways of doing what the post asked -- one that is clunky but "less scary," because it is clear exactly what you're doing, and a second that is elegant and uses only git commands, but requires you to trust your understanding of git a bit more.

Setup

Let's suppose you have a commit ID idA as the initial state and commit ID idB as the final state, with a bunch of other commits in between. The fact that you want to squash all the in-between commits says that you no longer care how you got from the initial state to the final state -- you just want a commit that moves you from point idA to point idB.

Let's suppose that idB corresponds to the current HEAD and say that it's also your master branch. We're going to make a new branch called squashed that has the same working tree contents as master, but has just one single commit after commit ID idA.

Solution 1

  1. git checkout master (you might already be on master)
  2. Use bash or your file explorer to copy the whole working tree to somewhere else. Just make sure you leave out the .git directory -- which is the default behavior on most operating systems. So, to be clear -- open the repo folder, select all, copy, and paste it to some new folder, on your desktop or wherever you want.
  3. git checkout -b squashed idA to make a new branch called squashed with idA as its latest commit, and make it your current branch.
  4. Paste the contents of the master (that you put somewhere else in Step 2) back into the repo folder. If your file explorer asks, tell it to replace all the files that have changed.
  5. In the top level of your repo, git add . and then git commit. Your new commit will have all the changes that take you from idA to idB.

Solution 2

git branch squashed idA # make a new branch called `squash` with `idA` as its latest commit 
git checkout master # master is "idB" in this case.
git symbolic-ref HEAD refs/heads/squashed
git commit

And voilà. When you use symbolic-ref to move the HEAD to the tip of the squash branch, the set of diffs between the working tree state and the new HEAD location are computed and staged.

I want to respond to the concern that some have expressed that this is a "dangerous", "low-level" git hack. I will try to explain just enough about how it works that you can feel at ease. Your .git folder is where all the versioned file contents live. Two of the folders in it are objects, and refs. The refs folder contains files that tell git the names of your branches and what commits they correspond to. So if you open up .git/refs/heads/master, for example, you'll see the commit ID of the latest commit to master. The objects folder contains a bunch of files that are in subfolders with two-character hex names. The objects can be several different things including patches, commits, and whole files. Also in the top level of the .git folder is an index file. This is a great post on the contents of the index file. What you need to know for the present discussion is that the index file tells you what object (in the objects folder) corresponds to the latest committed version of each file on the current branch and commit.

Against that background, here is what the solution does: the symbolic-ref command simply tells git "suddenly" that you're on the squash branch instead of the master branch without touching the working tree. That means that your files all correspond to the state of master you know and love, but git is seeing this as idA with a bunch of uncommitted changes (that is, the index file specifies that the current checked out version of all the files are those of idA, and this is the reference against which working tree changes are measured). The exact changes, in fact, that get you to state idB. This is exactly what Solution 1 does as well. In this case, because of the way symbolic-ref is implemented, all of the said changes are already staged (i.e. git add-ed), so all you have to do is git commit. There is nothing "dangerous" about this, because it doesn't change master or any of the objects that git is keeping track of. You've just created a new file in the refs folder called squashed, and one new file in the objects folder corresponding to that new combined commit. Your master is right there where you left it, you just have a new branch called squashed with the same contents but a shorter commit history. If you aren't sure what's going on, rest assured you can git checkout master and it will be right there where you left it.

If you want to adopt squashed as your new master, you can go ahead and

git branch -m master old-master
git branch -m squashed master

And then you'll have the old branch with all the superfluous commits saved as old-master, and master will be just what you wanted.

Hope this helps!

Community
  • 1
  • 1
scottgwald
  • 579
  • 4
  • 9
  • That's an interesting solution, but does sound like black magic. It seems like this kind of low-level hack might result in other problems down the line. Do you have a reference for this being a sound solution to the problem? – l0b0 Aug 14 '16 at 09:17
  • @l0b0 do you think adding more explanation of how/why it works would be helpful? I am quite confident that it's not introducing any unexpected behaviors later down the road -- it just happens to be a really elegant and simple solution to this problem. – scottgwald Aug 15 '16 at 17:06
  • More information would be useful, as would any references to others having discovered this hack (because it still feels like a dangerous hack). – l0b0 Aug 22 '16 at 15:44
  • Edited to address questions/concerns by @l0b0 – scottgwald Nov 12 '16 at 15:49
  • @l0b0 did you see this? Would appreciate acknowledgement or feedback. Thanks! – scottgwald Nov 24 '16 at 15:39
  • @l0b0 would greatly appreciate acknowledgement. Please let me know if you see this. – scottgwald Jan 24 '17 at 14:48
  • @l0b0 you're leaving me hanging here :,-( – scottgwald Nov 05 '18 at 03:45