Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Git saying file is 'deleted by us' when I haven't touched it?

Tags:

git

I adjusted 2 files and went to merge them to Github. However, when I run a push I get an error:

Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#       deleted by us:   somefolder/somefile.ext

However, I have not touched this file. When looking at my local directory I see the file is still very much intact.

Would anyone know why this is occurring and what I can do about it?

like image 841
MeltingDog Avatar asked May 17 '17 04:05

MeltingDog


2 Answers

See "Who is “us” according to git?"

When you merge, us refers to the branch you're merging into, as opposed to them, the branch to be merged.

So the destination branch should have this file deleted.

See "git merge “deleted by us”" for resolution: you can add it back or remove it.

As torek details, a "us" in this situation (where you did not delete the file yourself) means a rebase (like a git pull --rebase).

See "git rebase, keeping track of 'local' and 'remote'"

  • local ("us") references the partially rebased commits: "ours" (the upstream branch)
  • remote ("them") refers to the incoming changes: "theirs" - the current branch before the rebase, ie your branch where you did not delete that file.
like image 164
VonC Avatar answered Sep 28 '22 02:09

VonC


You said:

I adjusted 2 files and went to merge them to Github.

This is not how Git works. You run git commit to make commits locally (in your repository). You run git fetch to obtain commits from another Git (such as one running on GitHub), which adds more commits locally in your repository. Then you can merge or rebase commits locally. Everything happens locally. This really starts to matter in a moment, as we'll see.

It seems that somewhere along the way, you ran git merge or git rebase, or had some proxy command (such as git pull) run git merge or git rebase for you, because:

... when I run a push I get an error [starting with Unmerged paths]

This means you have not yet finished the merge or rebase. The merge-or-rebase gave you an error, telling you that one of your commit series removed a file, and the other of your commits series modified the file.

The question then becomes:

  • Did you run git merge, or did you run git rebase? This matters because the deleted by us claim is from a perspective that reverses when you are rebasing.

  • And of course, what do you want to do to resolve this situation?

There's a sort of general problem with this whole "ours/theirs" notation, because of the fact that everything you do is local. Even if you got some commits made by someone else, they're still your commits, in your repository. You just did not make them yourself.

I strongly suspect that you ran git rebase, perhaps through git pull (which runs git fetch first, and then runs either git merge or git rebase according to whatever instructions you give git pull).

Why merge or rebase is required

Let's look for a moment at how merge and rebase work in the presence of new commits brought in from someone else. We start with what you have in your repository, which is a series of commits, the last one or two or however many of them being ones you made. For simplicity, let's assume you made them on your master branch:

          H--I   <-- master
         /
...--F--G

Here H and I are the commits you made. The reason we drew them up on a separate line is that your Git remembers the commit that some other Git had as its master, which we're showing here as commit G:

          H--I   <-- master
         /
...--F--G   <-- origin/master

Now you run git fetch. This may obtain some new commit(s) from the other Git involved here. Let's call these commits J and K, and draw them in:

          H--I   <-- master
         /
...--F--G--J--K   <-- origin/master

You now have two options for combining "your" new work (commit H) with "their" new work (commit I). These options are git merge and git rebase. Using merge is conceptually a bit simpler, so we will start with merge, but both wind up using what I call Git's internal merge machinery.

How merge works

Git begins by identifying the merge base commit. This is the last commit that you, on your master, have in common with "them", on your origin/master. That is, we start at the two tip (rightmost) commits and work backwards—leftwards—until the two graph lines meet up. That's at commit G.

Next, Git effectively runs:

git diff G I   # figure out what "we" did on master
git diff G K   # figure out what "they" did on origin/master

Git then tries to combine the two sets of changes. Suppose "we" changed file README and "they" also changed the same file. But we added one new line near the top, and they removed one line near the bottom. It's easy to combine these changes: just keep the new added line near the top, and remove the line near the bottom, from the original version of README in commit G. That gives us the new final version, which Git will put in the new merge commit it will make.

If that were the only change, Git would go ahead and make the new commit, with the new version of README. The commit would be a new commit, on master, and would look like this:

          H--I--L   <-- master
         /     /
...--F--G--J--K   <-- origin/master

But suppose that besides both "we" (commits H--I) and "they" (commits J--K) modifying README, we also touch one line in somefile.ext, and they remove somefile.ext entirely. This is a modify/delete conflict: Git does not know whether to take the file from G and keep our change, or remove the file from G entirely. Git stops, does not make merge commit L, and instead says that there is a merge conflict, with somefile.ext as an "unmerged path".

If we ask about the status, Git will say:

    deleted by them: somefile.ext

and this makes sense since "our" changes are commits H--I. Commits J--K are ours too in that they are in our repository (but we didn't make them, we just got them from someone else).

In any case, Git leaves somefile.ext in our work-tree so that we can open the file in the editor and modify it if needed. Then we can git add or git rm the file, whatever is appropriate. Then we run git commit to finish the merge, with us figuring out what to do about the modify/delete conflict. This will make the final merge commit L and we will be ready (and OK) to push.

Rebase is a bit more complicated but has the same problem

Now let's look at how git rebase works.

Git still identifies the merge base commit as before, but instead of trying to merge what we did (commits H--I) directly with what they did, Git tries to copy our changes to new commits. (Git must do this copying because no commit can ever be changed.)

Git essentially does:

git checkout <hash-of-K>

followed by:

git cherry-pick <hash-of-H>

Let's say that in H, we modified README. We're already sitting on commit K, in which "they" modified a line near the end. In commit H, we modified a line near the top. We tell Git: Copy the other commit's modification from H and make a new commit that does what H did. Git can do this because that's just modifying an existing file in a nice safe place to do it. Git commits the result as a copy of H, which we can call H', and we now have:

          H--I   [somewhat in limbo]
         /
...--F--G--J--K   <-- origin/master
               \
                H'  <-- HEAD

Next, Git tries to cherry-pick commit I. This is the one where we modify file somefile.ext. But we're on commit H', which is based on commit K, which does not have somefile.ext. It's just gone! So Git stumbles over the change and stops with a merge conflict.

Who modified file somefile.ext? That's commit I, the other ("them") commit we are cherry-picking. Who removed somefile.ext? Well, that was probably commit K (it was J or K for sure), but as far as Git is concerned, that's us. We removed somefile.ext. So now Git says:

    deleted by us: somefile.ext

Just as before, Git stops and makes us clean up the mess. Git doesn't know if somefile.ext should be modified or removed entirely, so it leaves it in the work-tree. We must make the decision, based on our commits J--K and our commits H--I. We edit the file and git add it, or git rm it, to finish the merge work that git cherry-pick could not do on its own. This time, though, instead of git commit, we should run git rebase --continue.

Git will now try to finish the rebase, making new copy I' with whatever other changes we got from I. But: note that if the only change we got from I was to this somefile.ext, and we resolved the problem by still-removing somefile.ext, there are now no changes to commit. Running git rebase --continue will complain, and suggest that maybe we need to git rebase --skip to drop original commit I entirely.

Whichever is correct, we should do that (--continue and copy the rest of I to I', or skip and toss out I entirely), giving:

          H--I   [abandoned]
         /
...--F--G--J--K   <-- origin/master
               \
                H'--I'  <-- master

or the same without I'.

In any case, we're now ready to git push again, the same as after merging.

like image 21
torek Avatar answered Sep 28 '22 01:09

torek