Git merges removing files

ぐ巨炮叔叔 提交于 2021-02-08 03:11:28

问题


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.

  1. Why does this happen?
  2. 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...

enter image description here


回答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 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). [Edit: confirmed]

Now, using the two commit IDs produced above, find the merge-base. For simplicity I'll assume the first command produced 1234567... Per comment, this is 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 diffs. (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 in feature).

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!