Reputation: 3436
In THIS question I asked what is the difference between git merge branchname
vs git merge branchname --squash
and how it's affecting history of project. Now the follow-up:
Is it possible to change this merge commit type (back and forth --squash
) after pushing this branch to remote?
Background story is that of course I merged with wrong flag and now is time to clean up after myself...
Upvotes: 0
Views: 50
Reputation: 488183
The short answer is no, except via force-push.
To understand why, consider the fact that the word merge, in Git, acts as a verb, to merge, and also as an adjective or noun: a merge commit (a commit of type "merge") or a merge (a merge commit).
When we use the verb form, to merge, we mean to combine two different sets of changes made with respect to some common base. This is the verb form of both git merge
(when doing a real, non-fast-forward, merge) and git merge --squash
. So at this step, the two behave in the same way. (So far, we're pretty good.)
Once git merge
or git merge --squash
have finished their "to merge" action, though, they take two different paths. A normal non-squash merge makes a merge commit, which is a commit that has two parent commit IDs. A --squash
merge makes—or rather, leaves it up to you to make, by running git commit
—a normal single-parent commit. Either way you get a commit, but the thing about a Git commit is that it is uniquely identified by its hash ID, and its hash ID is determined by every bit of information in the commit. Crucially for our case, those bits-of-information include the parent hash or hashes.
What this means is that when you, or Git, make that post-"merge-as-a-verb" commit, you freeze into the commit's ID the two parents (if a merge commit) or single parent (if a normal non-merge). As with every Git object, once the hash ID is computed from the content, you cannot change the pairing of hash-ID and content. If you change the content, you make a new object that gets a new (and different) ID.
You could therefore, before git push
-ing the commit you have made, make a new commit that uses the same tree but only one parent, or both parents—whichever one you didn't do the first time around—and hence have both "really, truly, a merge commit" and "not-really-a-merge squash commit" objects in your repository. You could then pick one of them and send it to some remote and ask the remote to set one of its own names, such as master
or develop
or whatever on the remote, to point to that one commit.
But now, you have already sent one of those two hash IDs to the remote and asked it to set its name to point to that one commit. Let's say, for concreteness, that you asked the remote to set the name M
. You can send the other hash ID (making the other commit first if you like) and ask that remote to set some other name, such as N
, to point to that other hash ID; that would generally be OK. But if you ask the remote to set M
to the other hash ID, the remote will first look at its current setting for M
. It will see that making M
point to the new commit instead of the old one will in fact lose the old one, because the new commit's hash ID does not have the old commit's hash ID in its ancestry (the new commit's hash ID is not the current hash ID of M
, nor is the new commit's parent or parents M
, nor is any of their parents M
, and so on).
Thus, the remote <remote> will reject git push <remote> <hash>:M
as a non-fast-forward. To get that other Git to set its M
to the new hash, discarding the old one, you must send it a force-push operation. That does not guarantee that the remote will obey, but it does tell the remote that you do want to discard some commit(s) from their M
.
The dangers here are:
You may discard the commit you pushed earlier (which is your intent) in favor of your new hash (which is better), but perhaps someone else using that remote already picked up your old merge, so that now it has gone viral all over the Internet. This creates problems for everyone.
You might discard more than just the merge you pushed. Suppose Bob added a commit atop that commit, and Carol added a commit atop Bob's. Your force-push will discard your old commit in favor of your new one, but it will also discards Bob and Carol's commits that build atop your old commit.
If you are sure that Bob and Carol do not exist and that no one else has your old (wrong-merge-style) commit, it's safe enough to force-push that commit away and replace it with the improved one. But if not, it's not safe after all. You can look into the --force-with-lease
option as an alternative, but if others picked up your bad commit, that bad commit can keep returning, virus-like, to haunt you.
Upvotes: 1