问题
It's the second time this happens, when I'm doing a merge I later realized some files that were in the branch being merged are no longer in the branch being merged to.
The latest example is we have a feature branch which I've been merging the changes in from the main dev branch, we lost a lot of files after a merge and they are not present in the feature branch now.
- Why does this happen?
- What's the best way to get these files? I'm thinking of manually adding them.
here is a description of the repo when the files were lost:
964495c is where the files were lost, it is a merge of the remote branch with a local branch. d1c457a is a merge from the main dev branch into the feature branch.
When I do a diff of these two branches I see the files I have lost as being removed, but they were never set to remove. This wasn't my merge so I'm not sure if it was user error, but I need to figure out the best way to include these files in my feature branch...
回答1:
An answer to part 1
I don't have enough information here to tell you what did happen ... but here is how to see what should happen (or have happened), on a git merge ...
. We can see from the image above that 964495c
is a merge and one if its parent commits is d1c457a
(the yellow dot and line). We cannot see its other parent commit—the other parent is down the other end of the purple line. (One line is, I imagine, branch dev
and the other is branch feature
.)
[Edit: per comment, the first-parent is 29a7b67
(on branch feature
), and second is d1c457a
(on branch dev
as expected).]
The first thing git merge
does is find the "merge base". So, in a shell command window, find the two parent-IDs:
$ git rev-parse 964495c^1
29a7b67...
$ git rev-parse 964495c^2
d1c457a...
(I'm partly guessing here, based on colors, that [Edit: confirmed]d1c457a
is the second-parent, i.e., the thing being merged-in, rather than the thing merged-into—not that it matters too much, the merge result should be the same either way).
Now, using the two commit IDs produced above, find the merge-base. For simplicity I'll assume the first command produced Per comment, this is 1234567...
8967ae7...
$ git merge-base 29a7b67 d1c457a
8967ae7...
You now have three commit IDs. Let's re-do the above with shell variables, just so that I don't have to guess at the commit-IDs any more since the symbolic names might help:
$ id_into=$(git rev-parse 964495c^1)
$ id_from=$(git rev-parse 964495c^2)
$ id_base=$(git merge-base $id_into $id_from)
Now shell variables $id_base
, $id_from
, and $id_into
hold the commit ID of the merge-base, the tip of the branch being merged-from,1 and the ID of the commit just before 964495c
itself (the original tip of branch feature
when the merge was being done).
Using these, we can see what git merge
is asked to merge, by doing two git diff
s. (The next bit assumes Unix/Linux/Mac shell redirection.)
$ git diff -M50% $id_base $id_into > /tmp/existing_changes
$ git diff -M50% $id_base $id_from > /tmp/new_changes
The first diff finds out "what has happened on this branch, i.e., feature
since the other branch dev
diverged from it". The second diff finds out "what has happened on the other branch dev
since it diverged from this branch, i.e., feature
".
The merge
command then attempts to combine these two. Wherever dev
has any change:
- if the change is to add a new file, add the new file (unless
feature
has also added it: then we probably have a conflict that requires manual resolution) - if the change is to rename a file, rename the file (again unless
feature
has it already) - if the change is to remove a file, remove the file
- if the change is to add or remove some lines from a file, do that here (unless
feature
has it already, in which case, ignore the change; or if we can't find those exact lines with the same contents, then we have a conflict—this conflict case includes the case where the file was removed infeature
).
I have bolded the case where git merge
should remove a file. If the merge-from branch (dev
) removed that file since the merge-base, git merge
will quietly remove the file from the merge result.
Now, with all that said, if whoever is doing the merge uses --no-commit
, or if there is a conflict, the merge stops after doing whatever it can, and whoever is doing the merge has a chance to make additional changes. In the case of merge conflicts, this is of course where the person doing the merge must resolve the conflicts. However, the person doing the merge can use --no-commit
to get here even without conflicts. In any case, at this point, the person doing the merge can remove files, add files, and modify files. When he or she (ok, let's use "they" :-) ) ... when they are done, they git commit
the result, and that made commit 964495c...
.
1At least, it's what was the tip of dev
at the time: dev
might have grown a bit since then, just as feature
has in fact grown since then.
An answer to part 2
There is not necessarily a single best way to get the missing files back, but it's easy to check out files from a particular commit. Here the obvious one to use is 29a7b67
(aka $id_from
above, the tip of feature
when whoever did the merge, did the merge):
$ git checkout 29a7b67 -- path/to/file
(List multiple files, or one or more directories, to get more files, or every file in the directory, out of that commit-ID.) This puts the files into the work-tree and the index, as of the state they were in at that commit-ID, so that the next git commit
will include them in that state.
(You can of course edit them and git add
the result to make changes if needed.)
来源:https://stackoverflow.com/questions/25555648/git-merges-removing-files