1

I commited but realized there was a mistake in the message. I ran

git commit --amend -o -m "blah blah"

I then deleted the file (on purpose) and then did

git add .
git commit -m "blah blah"
git push origin master

and got the error

! [rejected] master -> master (non-fast-forward)

I tried to fix it with git pull origin master and got

CONFLICT (modify/delete): MyFile.java deleted in HEAD and modified in ad0c109

what is going on and how do I fix it? I just want there to be a history that I deleted the file. I intend to create a new one with the same name.

Update: I ran git push -f origin master and it worked. Now git status gives

On branch master All conflicts fixed but you are still merging. (use "git commit" to conclude merge)

I ran git commit and got

E325: ATTENTION Found a swap file by the name "C:/Users/Dev3/Documents/NetBeansProjects/OctRecProj/src/OctRecProj/.git/.COMMIT_EDITMSG.swp" owned by: Dev3 dated: Sun May 22 03:27:31 2016 file name: ~Dev3/Documents/NetBeansProjects/OctRecProj/src/OctRecProj/.git/COMMIT_EDITMSG modified: YES user name: Dev3 host name: Desktop-D process ID: 2844 While opening file "C:/Users/Dev3/Documents/NetBeansProjects/OctRecProj/ src/OctRecProj/.git/COMMIT_EDITMSG" dated: Thu Oct 13 03:22:34 2016 NEWER than swap file!

(1) Another program may be editing the same file. If this is the case, be careful not to end up with two different instances of the same file when making changes. Quit, or continue with caution. (2) An edit session for this file crashed. If this is the case, use ":recover" or "vim -r C:/Users/Dev3/Documents/NetBe ansProjects/OctRecProj/src/OctRecProj/.git/COMMIT_EDITMSG" to recover the changes (see ":help recovery"). If you did this already, delete the swap file "C:/Users/Dev3/Documents/NetBe ansProjects/OctRecProj/src/OctRecProj/.git/.COMMIT_EDITMSG.swp" -- More --

Celeritas
  • 12,953
  • 32
  • 95
  • 174
  • what does git status give you after you have tried to pull? – Gernot Oct 13 '16 at 10:03
  • @Gernot `You have unmerged paths.` and refers to the file I deleted. – Celeritas Oct 13 '16 at 10:09
  • The `.swp` file messages suggest you're using vim and still have a vim running on those files (perhaps in another window). – torek Oct 13 '16 at 10:33
  • Possible duplicate of [How to resolve merge conflicts in Git?](http://stackoverflow.com/questions/161813/how-to-resolve-merge-conflicts-in-git) – 1615903 Oct 13 '16 at 11:46

4 Answers4

5

Let's make a few notes, then draw some commit graphs, since git merge's actions depend critically on both the commit graph and the trees attached to the three "interesting" commits.

  1. git commit --amend does not modify a commit (because this is impossible). Instead, it makes a new commit and shoves the old one aside to make room for the new one, instead of doing the usual "add new one on the end".

  2. git commit -o (or --only) omits the current index (staging area), making a temporary index instead, starting from the previous commit. That's a reasonable enough thing to do when amending, although if you have not staged anything, this has no effect. So this "amended" (really, copied) commit will have the same tree as the original commit, even if you had staged something. (This only matters for note 3.)

  3. Finally, the subsequent git commit (after removing a file and staging the removal) will make a new commit with the file removed, plus anything else you already had staged (as noted in note 2).

Now, we don't know whether the commit you're amending here is a merge commit itself, but let's just assume it isn't. (If it is, the picture gets more complicated, but the result is the same.) We also don't know for sure, but can reasonably assume, that the commit you're amending was already pushed—since that's precisely what will cause this problem. So, we can draw the graphs like this.

First, we draw the "before" situation: before you amended anything. That graph is pretty straightforward:

...--o--*--A   <-- master, origin/master

That is, your branch master points to commit A (the one we're about to "amend"), and origin/master—your copy of master as seen in the other Git repository, over on the machine origin—also points to commit A. Commit * is the commit just before A; this one will be important later. There are additional earlier commits as well.

Now let's draw the graph after amending, but before making any new commits:

          A    <-- origin/master
         /
...--o--*
         \
          A'   <-- master

Commit A' is the amended commit, with slightly different message. It has the same tree as commit A—the same source code—but a different message, and therefore a different SHA-1 ID.

Let's add on the commit that deletes the file, and call this D for Delete:

          A      <-- origin/master
         /
...--o--*
         \
          A'-D   <-- master

You now try to git push origin master and get an error. The error is because, on origin, their master points to commit A, or perhaps even some commit(s) after A that you do not have.1 Let's just proceed with the theory that origin is not a super-active repository gaining ten new commits per second or whatever, though, and hope that it's just A. :-)

What their Git, over on origin, is complaining about is that if they let you do this git push, they will "lose" commit A. Your Git is asking their Git to set their master to point to your commit D (after, of course, handing over your A' and your D). If they do that, they will no longer have a name pointing to commit A: commit A will be forgotten.

Of course, this is precisely what you did to your own repository when you amended A. You told your Git: "forget about A, shove it off to one side where the only name for it is origin/master, and give me an A' copy with a different message."

OK, so far so good, except that the push failed. So you ran git pull origin master.


1It's not possible to tell precisely what they had then, although you can see, now, what you have now, which will match up with what they had right when you ran git pull. Of course your git pull was at least several whole seconds ago, and who knows how much has changed in that time! Seriously, there's a synchronization issue because whatever you're doing in your Git, someone else may be doing other things in other Gits right now. How much of a problem this is for you depends on just how active origin is.


git pull is not the opposite of git push

In fact, there is no exact opposite for git push. The closest thing is git fetch, which tells your Git to call up their Git, same as with a push, but instead of sending them your commits and asking them to move their branches, you get their commits, and you have your Git move your "remote-tracking branches": your origin/master.

What git pull does is first run git fetch, and then run git merge.

git fetch

The first part of git pull origin master is that Git runs git fetch origin master.

This fetch step called up their Git and brought over their new commits. We're assuming there were none: that their master still points to their A, which is also your A. So your Git updated your origin/master to keep pointing to your A (not much of an update: nothing changed):

          A      <-- origin/master
         /
...--o--*
         \
          A'-D   <-- master

(If they did in fact have some commits—let's call them B and C—then your Git obtained these commits and updated your origin/master:

          A--B--C   <-- origin/master
         /
...--o--*
         \
          A'-D   <-- master

but probably there were no such commits. Let's proceed without them.)

git merge

The second part of git pull origin master is to run git merge, with some extra arguments that are too complicated to reproduce exactly, but more or less amount to origin/master.2 Let's just pretend it really is git merge origin/master, since that's a little easier to work with.

What git merge does is to first find the merge base, which is the first commit that is the same between your current branch—that's your master—and the commit you tell it to merge in. The commit you (or git pull, really) are telling your Git to merge in is the tip of origin/master, i.e., commit A.

So now we go back to that graph we've been drawing and find the merge base. That's commit *. We also find the tip of origin/master, i.e., commit A, and the tip of our current branch: commit D.

Next, we make (or Git makes) two git diff listings:

git diff <id-of-*> <id-of-A>   # "what they changed"
git diff <id-of-*> <id-of-D>   # "what we changed"

Presumably, between commit * and commit A, "they" (whoever they were, maybe us) changed the file you deleted in D.

Meanwhile, between commit * and commit D, "we" deleted the file. Note that Git doesn't even look at commit A' here: all it does is compare * and D. It doesn't matter that we changed the file in the same way between * and A', it only matters that we deleted the file in the overall change.

This is the source of the merge conflict. Between "what they did" and "what we did", Git does not know whether to keep the change to the file, or keep the deletion of the file.

If you wish to make the merge—you probably don't—you can resolve this by running git rm <file> and then git commit to commit the merge. The result will be a new merge commit:

          A    <-- origin/master
         / \
...--o--*   \__
         \     \
          A'-D--M   <-- master

The tree for the new merge will match that for commit D, but now you will have both the original commit A and the amended copy (with same tree but different commit message) in your history.

You will be able to push this to origin, because now if you send them commit M and ask them to set their master to point to M, they won't lose commit A.


2The difference mainly shows up in the default merge commit message, which says, approximately, "merge the branch named master from the remote named origin" rather than "merge our own remote-tracking branch origin/master". Also, if you have an ancient version of Git, predating 1.8.4, there are a few additional technical gotchas that prevent origin/master from working right here. Hopefully you're not stuck with Git version 1.7.x.


What to do about this mess

First, you can abort your in-progress merge:

git merge --abort

Now you're back to the original:

          A      <-- origin/master
         /
...--o--*
         \
          A'-D   <-- master

in your repository.

The big problem here is that they, whoever they are, over on origin, have commit A. You may be able to use a force-push to get them to ditch it:

git push --force origin master

This tells their Git "go ahead and lose commit A, just set your master to D. The drawback is that if they've acquired some commit(s) B and/or C now, you will also lose those commits. Also, if anyone else has acquired a copy of commit A, those other users have to do something to recover from your resetting origin to abandon A in favor of A'.

You can use --force-with-lease to tell them, in effect, "I believe your master points to commit A; if so, make your master point to D instead." (This requires that both your Git and their Git be new enough to have the --force-with-lease option.)

Or, you can abandon your idea of modifying the commit message. Just give in and transplant your commit D so that it comes after A, instead of after A'. Give up your modified A' entirely, giving you this graph:

          A      <-- origin/master
         / \
...--o--*   D'   <-- master
         \
          A'-D   [abandoned]

You can now push this because your new copy D' of your original D simply adds on to their existing commit A. If, after doing this, we forget about the abandoned commits entirely and draw this more nicely, we get:

...--o--*--A--D   <-- master, origin

which is a nice straight history, and is what you were hoping for.

Summary

Your three options, after using git merge --abort to escape the in-progress merge, are:

  1. force push;
  2. force-with-lease;
  3. give up on the amend: cherry-pick D into D', or repeat your deletion to create D', after resetting your own master to point the same place as origin/master.

(If origin is private enough, option 1 is safe and easy. [Also, pushing while you're in the in-progress merge is largely harmless since push only pushes completed commits.])

torek
  • 330,127
  • 43
  • 437
  • 552
  • 1
    I did `git push -f origin master` but it still complains "you are still merging" (see update on main question for full details) – Celeritas Oct 13 '16 at 10:29
  • You haven't yet done the `git merge --abort` to terminate the conflicted merge. (I'll put the merge abort into the summary too) – torek Oct 13 '16 at 10:31
  • 1
    I have the same complaint after each problem like this: why is it so difficult? Isn't the point of git to make version control easier. I just wanted to add something to a message. – Celeritas Oct 13 '16 at 10:41
  • Can there be multiple `commits` in one `push`? I usually do a `push` right after each `commit` so maybe this is the problem. Could you do *work* `commit -m "msg 1"` *work* `-commit -m "msg 2"` *work* `commit -m "msg 2"; push` and it would save all 3 messages? – Celeritas Oct 13 '16 at 10:43
  • 1
    Yes! The `push` takes (from your side) a commit ID and (for their side) a branch name (or other reference). Your Git then sends whatever objects—commits and the files that go with them—that you have, that they don't, and last, asks them to set their name to your just-delivered commit ID. If you `git push origin master` your Git uses your `master`'s commit ID and the name `master` (though this glosses over `push.default` and a lot of other configurable settings). If you're 3 commits ahead, that pushes all 3 in one go, and then sets their `master` to the tip-most. – torek Oct 13 '16 at 10:45
  • 1
    As for why it's so difficult: Git sometimes seems like a kit car that you have to build from individual spark plugs, bolts, cylinder heads, pistons, rings, steering linkages, tires, fenders, and so on. :-) It lets you control *everything*, even if that's not what you actually *want*.... – torek Oct 13 '16 at 10:47
1

I assume that this is what happened:

  1. You changed MyFile.java and committed the change as commit A.
  2. You pushed your change, so the remote repository contains commit A.
  3. You amended the commit and thus created commit B (which is in fact a new commit with the same changes as A had).
  4. You deleted the file and committed that change creating commit C.

To check if this is the situation you are in, you could do git log and git log origin/{branchname} (assuming that your remote repo is called origin and you replace {branchname} with the name of the branch you push to). The remote branch should contain the branch with the old message, while the local branch contains the new message.

Now locally you have commits B and C while the remote repository contains the commit A. When you do git pull, git tries to fetch the commit A from the remote repository. This commit contains a change to the file MyFile.java. When git tries to apply that change to your local repository, it recognizes that that file has been deleted in the meantime.

A little more explanation: git recognizes commits based on their hashes. The hash is calculated based on many information, amongst them are the diff done by that commit, the previous commit, the message and also the time of the commit. Thus if you amend the commit, the hash changes. In the future git does recognize the commit before and after the change as different commits.

You have multiple ways to handle that issue. As you commented in the meantime that the repo is not shared but only you are working with it, option 2 is most probably the preferred one.

  1. Solve the merge commit by deciding to delete the file. This however will lead to both commits A and B existing in your history, which contain the same changes but different messages.
  2. Force push your changes with git push -f. This will remove commit A from the remote repository and push your commits B and C. This will lead to problems, if anyone else has already fetched your changes from the remote repository, as they will get the same problems you got before.
  3. Do not amend the commit and keep it in the old state. To get that state back, you can first store the commit hash of the commit deleting MyFile.java anywhere to use it later on. Than do a git reset --hard A (attention: if you have any open changes left, this will remove those, so stash them before) and afterwards git cherry-pick C where C is the hash of the commit deleting the file. If you have done more commits than that one, proceed for them accordingly (remembering their hashes and picking them later on).
lucash
  • 1,794
  • 19
  • 23
  • What should I have done differently to avoid this situation aka what did I do wrong? Was it because I didn't `git push` after amending? The new message did appear on github... – Celeritas Oct 13 '16 at 10:18
  • You could have avoided it only by not pushing before amending the commit. If you had pulled before deleting the file you probably wouldn't have gotten any merge conflict, however the problem of the duplicate commit would still exist. – lucash Oct 13 '16 at 10:23
  • It still gives an error when I do `git status` please see update in main question – Celeritas Oct 13 '16 at 10:27
0

Did you push after change the commit message?
After git ammend, push that change. Then run the commands to delete the file.
P.S - I didnt get this issue when I followed the steps you have mentioned

M.Sharma
  • 214
  • 1
  • 5
  • No I did not push after the amend, I didn't know I needed to. – Celeritas Oct 13 '16 at 09:55
  • You don't need to, I tried the same steps as you, but didn't get any error, but worth trying push first. – M.Sharma Oct 13 '16 at 09:58
  • I always get random merge conflict errors and I don't know why – Celeritas Oct 13 '16 at 10:00
  • Most of the conflicts can be avoided if, once you are about to commit your change in a shared repo, stash your code, take a pull, apply stash, resolve conflicts if any, commit and push. – M.Sharma Oct 13 '16 at 10:04
  • The repo isn't even shared, it's just me – Celeritas Oct 13 '16 at 10:08
  • to resolve this issue, try - git reset --soft origin/master. This should bring you local in sync with remote with your local changes applied on top. If you see conflicts, try resolving and commit/push – M.Sharma Oct 13 '16 at 10:12
0

If you didn't push the branch before amending, it means that someone else did some changes on the remote master branch and it's not related the amend of the commit.

When you did a pull, git fetched the remote modifications of the branch and then tried to merge it in your local branch, this failed cause the file you deleted was modified in the other commits, so I guess you need to synchronize with the other person to see why they changed this file.

You need to resolve this conflict manually.

Another option is to rebase your local branch master on the remote branch, you'll still have the conflict but you'll avoid having a merge commit in your history.

Matt
  • 2,749
  • 1
  • 18
  • 28
  • 1
    No one else did any changes. I'm the only one working on the project. Also I saw my amended message on github so I don't think it needed a `git push` after. – Celeritas Oct 13 '16 at 10:10