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?
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'"
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
).
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With