0

I am using Git Extensions and my situation looks like this: enter image description here

As you see, my target commit is not the last one. And it's already pushed and pulled by other users of the branch. I read some articles about using different commands through the git bash, for doing some work, but here I found an easier way - I right-click the target commit > Manipulate commit > Fixup commit and another window pops out. Then I am able to change the commit message (the green part), and apparently to push the changes, but I haven't done anything yet as, from what I read, this is gonna cause problems in future for the other users of the branch.

What could these "problems" be and how can I fix the commit + avoiding any problems in future?

Community
  • 1
  • 1
Syspect
  • 921
  • 7
  • 22
  • 50

3 Answers3

3

Note: I don't use these GUIs and sometimes they do something a bit unexpected. But in this case I expect it only does what I expect (as it were :-) ).

Fundamentally, git never actually changes anything in a commit. If you do an "amend" operation, even if you don't first back up one or two commits in a branch, what git does underneath is to make a new commit, leaving the old one in the repo.

For instance, your commit chain looks like this. All I have done is rewrite the vertical graphical version in a horizontal form (more suitable for text).

... - M - B - C - P   <-- master, origin/master
    /
...

where M is the "Merge branch 'deploy" commit, B is the first "blah, blah" commit, C is the one you want to change, and P is the pushed commit (the second "blah, blah" one).

Now, if you "detach" HEAD so that it points directly to commit C, you can run the command-line command git commit --amend, which seems like it's changing C. Git brings up an editor with the original commit message and you can change the text and write and exit the editor, and git makes a new commit. But what it does is not "change C". Instead, it makes a copy of C, let's call it D, with the same files but different commit-text. (If you modify some file(s) and git add them before doing git commit --amend, the changes you make to the files get associated with the new commit D; again, commit C remains entirely unchanged.) This gives you a commit chain that looks like this:

... - M - B - C - P   <-- master, origin/master
    /       \
...           D       <-- HEAD

It is now possible to take commit P and copy it as well. (You still can't change it—this is a feature of a git commit. Git commits are frozen in time forever: their SHA-1 "true name" is a cryptographic checksum of your name as the commit author, the time-stamp at which you make the commit, the commit text, the commit's parent commits, and all the files and directories inside the commit; and changing a single bit anywhere in the commit, changes the SHA-1, and hence the "true name" of the commit. The result is a new, different commit.) Let's call the copy of P (with parent changed to D) E:

... - M - B - C - P   <-- master, origin/master
    /       \
...           D - E   <-- HEAD

It's possible your graphical tool will do this for you automatically, if you say "please change commit C": it will make a copy of C, with whatever changes you like, which becomes D; and then it will make copies of things on top of C—in this case, that means a copy of P—as well. (Or more likely it doesn't bother copying "things on top of C" at all, forcing you to do it if you want that.)

You can now move your local branch label master to point to commit E instead of commit P. In fact, git will sometimes even help you do that, with some commands. (I have no idea what your GUI will do, I find GUIs mysterious and often annoying. :-) ) But, if you do that, you will have a "divergent" master branch, because the remote repo, the one at origin, has your commits C and P (because you successfully "pushed" them earlier). And it has them on what it considers to be the master branch.


Side note: if you can log in to the machine origin, you can find the git repo over there and look at its idea of the various branch labels. Over there, you will see master pointing to commit P. That's why your repo has the "remote branch" label, origin/master, pointing to P: essentially, whenever your machine contacts the remote, it asks: "hey! what labels do you have?" The remote answers—it might say "I've got master and develop and they point to commits P and Q"—and then your git copies these over to your repo, but changes the names: master becomes origin/master, develop becomes origin/develop, and so on. That's how you know what he knows. Or, more precisely, you know what he knew, the last time you talked with him. That could have been seconds ago and things might be totally different by now!


Anyway, suppose you move your master to point to commit E. If you now ask to push these changes to origin, your git will tell his git: "hey, I have these new commits D and E and you should point your master to E!" His git will look at those and say: "if I do that, I could forget about commits C and P. Your commit E points back to D and then D points back to B, and C and P will become unreachable." His git will reject your push unless you specify the "force" option.

If you do specify "force", his git might still reject it, but probably he'll take it. Is this a "bad thing"? Well, maybe. You'll be OK, and the repo at origin will itself be OK. But what about Joe, who copied your commits C and P down from origin just 17 seconds ago? Next time he goes to origin he'll expect C and P to still be there, and they won't be. Joe will have to figure out that someone (you) "rewound" the branch, "removing" commits C and P.

Git is all set up for, and everyone expects to handle, new commits to get added. They don't usually expect old commits to "go away" like this. Joe can fix this—his repo will still have C and P; git doesn't actually delete commits (unless they become unreferenced and are garbage-collected). But he may have to do a lot of work to figure out what you did, and figure out that if he had new stuff that he had added on to P, he now needs to copy that to new commits that add on to E instead. (Specifically, he has to make sure he does not accidentally copy commits C and P, which are now subsumed by D and E.) So this could give Joe a headache. And if Joe has these, maybe Sally and Frank and Irene have those commits too, and maybe you will be giving dozens of people headaches.

Is it bad? Well, that depends on who has your old commits, how much of a headache you're going to give them by doing this, and whether you're allowed to give them headaches. :-) If nobody has them—if you're very quick, or the rate at which people pick up changes you push is measured in days instead of seconds—you'll be fine. If you give those who have your "bad" commits some warning, and they know what to do, you could be fine. If not, well, who knows?

torek
  • 448,244
  • 59
  • 642
  • 775
  • Note: recovering from a `git push -f` will be easier with Git 2.0: see http://stackoverflow.com/a/20423029/6309 – VonC Dec 06 '13 at 11:47
1

The problem with rewriting history which has already been pushed is that other users already have the "original" history, with which your rewritten history will not be compatible anymore. So if you change the next-to-last commit (and a change in commit message is enough to do that, as the message is part of the commit's hash calculation) and force-push it to the server, any work someone else already has done based on the old version of that commit or any newer ones will not apply anymore (as it no longer has a valid ancestor in the branch). As pointed out here: RECOVERING FROM UPSTREAM REBASE

Rebasing (or any other form of rewriting) a branch that others have based work on is a bad idea: anyone downstream of it is forced to manually fix their history.

If the commit you want to fix is very recent and the chance is low that anyone else has done much based on it, do the interactive rebase, git push --force to the server, and tell the other contributors to git pull --force if they can do without their local changes. For rebasing existing work based on the rewritten commit, see the accepted answer here: How do I recover/resynchronise after someone pushes a rebase or a reset to a published branch?.

This is not a one-stop solution, because rewriting published history is cumbersome by design. If it's not absolutely necessary, don't do it.

Alternatives: I don't know how severe your wrong commit message is (completely garbled, erroneous, incomplete?), but if you just need to clarify something, you can do

git notes add -m"<Comment or clarification>" [commit-id-if-not-HEAD]

Notes are kept seperately and can be changed/added/removed later, without affecting the history.

Community
  • 1
  • 1
ChrAfonso
  • 170
  • 4
0

Several ways of solving this, (imho) cleanest is to rebase your commit on top of origins master.

This can be done interactively as such:

git rebase -i origin/master
DusteD
  • 1,400
  • 10
  • 14
  • Sorry, I didn't understand completely (I'm a newbe user of git). You mean, that I should change the commit message, then push it and then rebase ? – Syspect Nov 26 '13 at 10:05
  • I'm sorry, maybe I misunderstood, I thought you were in the situation where your current commit was outdated because someone else pushed before you? You never change commits that may have been pulled by others, instead you make new commits to fix whatever was wrong with the first one. If you already changed your commit, reset your repository to origin/master and then make your changes anew. – DusteD Nov 26 '13 at 10:10