2

I've started looking into interactive rebasing a little more and I've come across some issues. They are about the "fixup" option. I am interested in what is the difference between fixup and drop + reword option. For example, let's say we have:

pick 577dab2 add navbar
pick 432fda1 fix navbar bug

What is the difference between these two:

pick 577dab2 add navbar
fixup 432fda1 fix navbar bug

and

drop 577dab2 add navbar
reword 432fda1 fix navbar bug -> reword it to 'add navbar'

It would make a difference if the fixup option provided resolution of conflicts if there were any when "merging" (I know it's not real merging, but let's call it that) with the previous commit (in my example, if there is any conflicts between 577dab2 and 432fda1 commit). However, as far as I have noticed, the implementation is only "meld into", that is, everything that is within the fixup commit (432fda1) will only be "transferred" to the previous commit (577dab2) and the name of that previous commit (577dab2) will be taken. In other words, even if there is conflicts, they won't be resolved because everything was just "transferred" from one commit to another.

So from what I understand so far, fixup just allows the same thing to be done faster... Can anyone tell me what the real difference (if any)?

Someone asked this question over 8 years ago, but no one answered, at least not completely. Here's link.

Filip
  • 401
  • 8
  • The problem you're having is a conceptual one of what rebase does. By chance are you familiar with `cherry-pick`? If yes, think of rebasing as just a shorthand for cherry-picking a specific range of commits. (In fact, in an interactive rebase, "pick" is sort of like saying "cherry-pick this commit".) If you know how cherry-picking works, then I suppose it'll be easier to comprehend what will happen if you only cherry-pick 1 commit instead of 2 commits. – TTT Jan 11 '23 at 03:14

4 Answers4

1

What is the difference between these two

All the difference in the world. drop is very powerful option that propagates to all later commits a major alteration such that the "changes" represented by the dropped commit in relation to its parent will never have happened.

pick is just the opposite. It keeps those "changes" in the history.

Indeed, the two choices you compare have nothing substantial in common. I don't see why you ask for the difference between them; what they lack is any similarity.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 2
    I am asking about difference between `fixup` and `drop + reword` not between `drop` and `pick`. Please read my question again, you missed something. – Filip Jan 11 '23 at 02:42
1

They are not equivalent.

I think the confusion is that when working with an interactive rebase you're working with individual sets of changes, not the complete content up to that commit. Think of it like rearranging a series of patches.

When you say drop 577dab2 add navbar it is as if that commit never happened. There is no navbar for 432fda1 to fix.


pick 577dab2 add navbar
fixup 432fda1 fix navbar bug

You're saying "pretend I never made that mistake and did it right the first time".

This turns the changes in both commits into a single commit, and uses the log message for the picked commit. You wind up with the changes in both 577dab2 and 432fda1 in a single new commit with only the "add navbar" log message.

drop 577dab2 add navbar
reword 432fda1 fix navbar bug -> reword it to 'add navbar'

You're saying "pretend 577dab2 never happened, I never added a navbar" (I'm ignoring the reword, its not relevant).

This throws out the changes made in 577dab2 (adding a navbar) and you're left with just the changes in 432fda1. Since 432fda1 is trying to fix a navbar that was never added, you'll probably get a conflict.


the implementation is only "meld into", that is, everything that is within the fixup commit (432fda1) will only be "transferred" to the previous commit (577dab2) and the name of that previous commit (577dab2) will be taken.

Side note: Git never changes a commit. It only creates new commits.

Let's say your repo looks like this.

A - B - C - D - E [main]

Let's say you fixup C. Git creates a new commit with the changes in both B and C and rewrites the changes of D and E on top of the new commit.

A - B - C - D - E
 \
  BC - D1 - E1 [main]

The old B, C, D, and E commits will be garbage collected.

For more on why it works this way, see What is a Git commit ID?.

But if you say drop B Git will now try to rewrite C on top of A. It's as if B never happened.

A - B - C - D - E
 \
  C1 - D1 - E1 [main]

The changes in C are now applied to A. If C's changes depend on B (as in fixing the navbar introduced by B) then you will get a conflict.


Let's look at it another way. Let's say 577dab2 is this.

--- a/dir1/file
+++ b/dir1/file
@@ -11,3 +11,8 @@ and some more
 and here is more
 
 and then this
+
+from the first commit
+
+Back Home Escape Next
+

And 432fda1 is this.

--- a/dir1/file
+++ b/dir1/file
@@ -14,5 +14,6 @@ and then this
 
 from the first commit
 
-Back Home Escape Next
+Back Home Leave Next
 
+from the second commit

And you do a fixup...

pick dedbeef something else
pick 577dab2 add navbar
fixup 432fda1 fix navbar bug

The result is a new commit containing both 577dab2 and 432fda1's changes with 577dab2's commit message.

--- a/dir1/file
+++ b/dir1/file
@@ -11,3 +11,9 @@ and some more
 and here is more
 
 and then this
+
+from the first commit
+
+Back Home Leave Next
+
+from the second commit

But if you drop 577dab2 and reword 432fda1...

pick dedbeef something else
drop 577dab2 add navbar
reword 432fda1 fix navbar bug

This is the same as deleting 577dab2 and trying to apply the changes in 432fda1 on top of dedbeef.

pick dedbeef something else
reword 432fda1 fix navbar bug

dedbeef doesn't have a "Back Home Escape Next" line to alter, so this will result in a conflict.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • `The result is a new commit containing both 577dab2 and 432fda1's changes with 577dab2's commit message.` But it won't. Even in your example it took `Back Home Leave Next` instead of `Back Home Escape Next`. So whatever is inside `fixup` commit will be taken, or, using other words, will get over of content from previous commit. – Filip Jan 11 '23 at 03:35
  • @Filip No, I've updated the examples to make it clear. I encourage you to make a repository and experiment,. Think of it like this. `pick dedbeef pick 577dab2 fixup 432fda1` is like 1) `git reset --hard dedbeef` (the branch is now at the commit before 577dab2) 2) apply the 577dab2 patch 3) apply the 432fda1 patch 4) `git commit -a`. Whereas `pick dedbeef drop 577dab2 reword 432fda1` is like 1) `git reset --hard dedbeef` (the branch is now at the commit before 577dab2) 2) apply the 432fda1 patch (getting a conflict) 3) `git commit -a`. – Schwern Jan 11 '23 at 04:08
1

Your misunderstanding is conceptual about how rebase works. Rebasing is basically just cherry-picking each of the changes contained in a range of commits (thus the word "pick" in an interactive rebase). Note your thought about reaching the original state of a commit isn't relevant when cherry-picking or rebasing.

Given that, consider the differences:

pick aaaaaaa Add file A
pick bbbbbbb Add file B
# RESULTING COMMITS: 2 commits with those titles
# RESULTING STATE:   File A and File B both exist.

pick aaaaaaa Add file A
fixup bbbbbbb Add file B
# RESULTING COMMITS: 1 commit with title "Add file A"
# RESULTING STATE:   File A and File B both exist.

drop aaaaaaa Add file A
reword bbbbbbb Add file B
# RESULTING COMMITS: 1 commit with default title "Add file B", but pops up editor
#                      and allows you to change it
# RESULTING STATE:   Only File B exists.

The same concept applies for 2 commits editing a single file, except now we introduce the possibility of conflicts. Suppose we have a file called file1 which contains a single character 'x'. And now we have two commits that simply change that character:

pick aaaaaaa Change file1 to 'a'
pick bbbbbbb Change file1 to 'b'
# RESULTING COMMITS: 2 commits with those titles
# RESULTING STATE:   file1 is 'b'

pick aaaaaaa Change file1 to 'a'
fixup bbbbbbb Change file1 to 'b'
# RESULTING COMMITS: 1 commit with title "Change file1 to 'a'"
# RESULTING STATE:   file1 is 'b'

drop aaaaaaa Change file1 to 'a'
reword bbbbbbb Change file1 to 'b'
# CONFLICT! This is because commit bbbbbbb only knows how to change
#   from 'a' to 'b', but right now the starting point is 'x' instead of
#   'a', so it pauses and asks for your help. Most likely you'd want to 
#   take 'b' in this case, and then:
#
# RESULTING COMMITS: 1 commit with default title "Change file1 to 'b'",
#                      but pops up editor and allows you to change it
# RESULTING STATE:   file1 is 'b'
TTT
  • 22,611
  • 8
  • 63
  • 69
  • I did not try `fixup` with 2 or more files, only with one. So when it's more than one file (one made in commit `aaaa` and another in commit `bbbb`) it will keep both? On the other hand, for one file it will only keep content of `fixup` commit because it's "melding in"? – Filip Jan 11 '23 at 03:31
  • @Filip in my example, if you change the word "file" to "line" it is the same result, **if** the lines aren't next to each other. If they are next to each other, or modifying the same line, then you will get the same end result whether you pick both, or pick and fixup. If you drop one and pick (or reword) the second, then you will get a conflict. – TTT Jan 11 '23 at 03:41
  • @Filip I will add more examples to the answer to cover a single file too. – TTT Jan 11 '23 at 03:42
  • Sorry, I did not mention about which case I am talking about. I am talking about second case. Case when you have `pick` and `fixup` together. – Filip Jan 11 '23 at 03:44
  • Examples of individual files would also be very helpful, thanks. – Filip Jan 11 '23 at 03:46
  • @Filip I updated for a single file. It might help to realize that "pick" followed by "fixup" makes it look like you made all the changes in both commits in a single commit, had you thought to do it that way before you committed the first time. Also, "pick" and "fixup" is the same as making a commit, and then amending that commit with a change that you wanted to be part of that commit. – TTT Jan 11 '23 at 04:00
  • 1
    This is the answer I was looking for. Thank you for your help. I'll leave the question open for potential additional comments, and then most likely accept yours. Once again, thank you once again. It's completely clear to me now. – Filip Jan 11 '23 at 05:16
0

Yes, a huge difference. If you drop the first commit, it... drops the first commit. Whatever changes were in that commit will be gone, the thing that you were fixing won't be there to fix, the second commit probably won't apply, and if it does apply it probably won't be valid.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • Yes, if I `drop` a commit all changes will gone, but that also happens when I use `fixup` because `fixup` commit will just "meld into" previous commit and get over all content. – Filip Jan 11 '23 at 02:52
  • 1
    @Filip No, the changes made it the dropped commit will be gone. If you `drop 577dab2 add navbar` there will be no navbar. it's as if that commit never happened. – Schwern Jan 11 '23 at 02:53
  • @Schwern It will be because it is "inside" commit `432fda1 fix navbar bug`. If it's not inside this commit it won't even be after "fixing up" because `fixup` "meld into" previous commit, so the content of `432fda1 fix navbar bug` will get over content of `577dab2 add navbar`. – Filip Jan 11 '23 at 02:57
  • @Filip Normally yes, 432fda1 would contain a snapshot of the current state of the code. However, during an interactive rebase you are rewriting history moving, changing, rearranging, and deleting commits. `432fda1 fix navbar bug` is *only* the changes made in 432fda1. `577dab2 add navbar` is *only* the changes committed in 577dab2. If you drop 577dab2 you are rewriting history to never have added a navbar. There is no navbar for 432fda1 to fix: conflict. – Schwern Jan 11 '23 at 02:59