I have been working on a git repo. Before publishing my changes, I'd like to remove some commits - a023b43
, 315424b
and 7b51754
- as they do not reflect any meaningful changes. The only important points in time are the first (70f72ca
) and last (6937ddd
) commits.
git rebase -i 4c802c1
I chose:
pick 70f72ca Remove repetitions from Makefile
d a023b43 Separate target for image conversion
d 315424b Remove image conversion (#1)
d 7b51754 Fix Makefile
pick 6937ddd CV 2019.01
The final goal is to have a git log that looks like:
6937ddd CV 2019.01
4c802c1 Initial commit
Auto-merging src/Adam_Matan.tex
CONFLICT (content): Merge conflict in src/Adam_Matan.tex
error: could not apply 6937ddd... CV 2019.01
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 6937ddd... CV 2019.01
As I understand it, the two commits are just snapshots of my working directory. I want to have two snapshots - the original commit and the CV 2019.01
one. I don't want to merge or combine them in any way. Why do I get a merge conflict message?
How can I delete all intermediate commits from a branch, and leave only the first and last commits?
Squashing creates merge conflicts as well:
git rebase -i 4c802c1
Auto-merging Makefile
CONFLICT (content): Merge conflict in Makefile
error: could not apply 315424b... Remove image conversion (#1)
Rebase acts by taking a series of commits and re-applying them, perhaps on a different "base" object. It does either by creating a patch for each commit, and re-applying them, or by cherry-picking them in-order.
Regardless of the mechanism, by removing a commit from the list of rebase instructions, you have asked for those changes not to be included.
Imagine if you had some file, and in its initial revision, it has a single line with the contents one
. Imagine that in the next commit, you change one
to two
. In the subsequent commit, two
to three
. Then three
to four
, and finally four
to five
. So you now have five commits, each changing this line.
Imagine that in each commit you gave it a message indicating the change in contents, and that magically the commit ID (hash) also reflected the contents. Running rebase -i --root
would give you this script:
pick 1111111 add one
pick 2222222 change one to two
pick 3333333 change two to three
pick 4444444 change three to four
pick 5555555 change four to five
In other words, the first commit picks the change from nothing to one
. The second one will cherry-pick the change from one
to two
, etc, until the last instruction cherry-picks the change from four
to five
.
If you remove some of those lines, giving you:
pick 1111111 add one
pick 5555555 change four to five
You will have two cherry-picks. The first will add a file with contents one
. The second will try to change those contents from four
to five
.
Alas, those contents are not four
. Thus, you have a conflict.
If your goal is to get from one
to five
with no intermediate commits, then you want to squash
them or mark them as fixup
commits.
pick 1111111 add one
pick 2222222 change one to two
squash 3333333 change two to three
squash 4444444 change three to four
squash 5555555 change four to five
In this case, the first change will be cherry-picked, so the file one
will be created. The subsequent change will be cherry-picked, so the contents will be changed from one
to two
. And subsequent changes will be cherry-picked, further updating the file, but those changes will be "squashed" into the prior commit. Thus you'll get the full contents, but you'll only end up with two commits.
(Had you marked them as fixup
commits, you would get the same contents, but their commit messages would not be included in your suggested commit message.
The problem is that git rebase
uses contradictory terminology to how git as a whole defines commits. A commit is a snapshot, but rebase
treats a commit as a patch (a set of changes) between its parent commit and itself.
So if you tell rebase
to "delete a commit", it thinks you want to undo the changes applied by that commit's patch.
If you want to remove some snapshots while keeping later snapshots the same, you tell rebase
to squash
the commits. In rebase
's terms, this means you're combining the changes from multiple commits into the latest of those commits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With