Reputation: 58958
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 <[email protected]>
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")
Upvotes: 5
Views: 2358
Reputation: 609
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.
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
.
git checkout master
(you might already be on master
)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.git checkout -b squashed idA
to make a new branch called squashed
with idA
as its latest commit, and make it your current branch.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.git add .
and then git commit
. Your new commit will have all the changes that take you from idA
to idB
.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!
Upvotes: 0
Reputation: 11378
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".
Upvotes: 2