合并两个分支A和B,其中A包含从B中删除的文件 [英] Merging two branches A and B where A contains files that were deleted from B

查看:105
本文介绍了合并两个分支A和B,其中A包含从B中删除的文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好的,所以基本的想法是,有一些文件我想确保恢复或取消删除。



我不确定发生了什么,但是我有git分支A和B,而我是99%,B是从A分支出来的。创建B后,我从B中删除了一些文件,然后做了很多更改。我现在想要与A合并,但是我担心会发生什么情况是,当我合并分支时,从B中删除的文件将被永久删除。



有没有人之前处理过这种情况?我会尝试的第一件事是从A创建一个分支C,然后尝试将C与B合并,但是我的猜测是,这并不能解决问题。

解决方案


我不完全确定发生了什么,但是我有git分支A和B,而我99%是B分支出来的。


合并过程并不在意你现在的位置,只在你实际所在的位置。更具体地说,它需要三项:


  • 您当前的提交( HEAD ),在任何分支上,如果有的话。我们称之为我们的提交和/或本地提交。当我们需要它时(快),Git有 - 我们的


  • ,从参数到 git merge :例如, git merge foo 将解析名称 foo 找到它的特定提交ID。 Git的大多数部分都给它一个名字,称它为他们的提交(尽管有一部分称之为远程提交,由于它与Git的提取和推送遥控无关,所以特别令人困惑)。让我们把它称为其他提交,但是当我们需要它时(即将发布),Git使用 - 他们的

    <即使这两个提交都是由你完成的,这个 - 我们的 - 他们的我认为)称它们为本地和其他。

  • > 2 >这是 分支上的最新 3 提交,也就是两个分支分叉的点。


这是第三件事 - 合并基础 - 你有点模糊。你可以简单地指示Git使用命令 git merge-base 来告诉你哪个提交。


创建B之后,我从B中删除了一些文件,然后进行了很多更改。我现在想要与A合并,但我担心会发生什么情况是,当我合并分支时,从B中删除的文件将被永久删除。


他们可能会 - 除了Git中没有永久的东西。或者我们可以说所有东西都是永久性的,这意味着什么都不是永久性的,它变得非常具有哲学性,但基本上就像是说如果一切都是100%至关重要的,那么没有什么是真正重要的,或者至少没有什么比什么都重要。 : - )



以下是一些需要考虑的事项,以及一些提供这些事情的工具:


  • Git通过将合并基数与两个(本地和其他)提交进行比较来执行合并。这实际上告诉它,自从通用基础提交以来发生的分支的每个侧面发生了什么。



    合并是通过获取每一方都做出了每一项改变。因此,如果文件 misc.txt 被删除,那么Git会在每次不能执行该操作时声明冲突。

    基本到本地更改集,并在基本到其他更改集中添加一行。我们如何结合删除整个文件和添加此行? Git的答案是:我不这样做,我只是在该文件上声明合并冲突。

    另一方面,假设文件 samp .dat 在基本到本地中删除,并且在基本到另一部分中没有更改。我们如何结合这些?答案很明确:删除文件。



    还有很多其他的冲突情况,但上面显示的是您特别担心的情况,在这里。


  • 你想将A合并成B,还是把B合并成A?例如,上面我们刚刚说过,如果基本到本地删除 samp.dat ,会发生什么情况? / code>,而base-to-other使其未经修改:Git删除合并结果中的 samp.dat 。如果base-to-other删除文件,并且base-to-local将其保留为未修改,则Git 也会删除该文件,因为更改仍然是 一边说删除一边说什么都不做。 并不重要,只是正好是。 (或者,如果 都声称删除文件,那么也没有问题。)

    实际上,合并结果结果总是相同的:一组变更表示做X,另一套表示做Y;如果Git可以将它们结合起来,那么结果就是这种组合,否则结果就是冲突,无论Git能够做什么来记录这两组变更。但在所有情况下,结果都是对称的:combine(X,Y)≡combine(Y,X)。 (如果它没有出现在所有字体中,则在两个组合之间有三行=符号,等同于符号。)



    <那么,唯一的区别就是Git会在你开始进程的任何分支上进行新的合并提交 - 当然,无论你在哪个分支上,你都需要命名其他 em>分支(或其提示提交)在 git merge 命令中。它是相同的工作树(合并结果),而不是提交本身。
  • 最后,假设你有一些提交 - 也许合并提交,或者可能只是普通的普通提交 - 即删除文件 misc.txt 。现在,记住Git是一个版本控制系统并且永远保存所有内容,假设我们告诉Git从commit中提取 misc.txt 在此提交之前,并进行新的提交。新提交具有删除 misc.txt 加上版本<$ c的所有内容作为其工作树内容$ c> misc.txt 从早先的提交中复活。



    如果我指示您查看新提交而不是旧提交,你会关心 misc.txt 复活吗? (有些情况下,你可能不在乎,你可能会关心一些的情况,以及一些你可能会关心很多的情况,所以这是一个思考一段时间或者回来的问题但是,只要所有东西都是永久性的,它就会得到永久性 的哲学问题。 $ c>并且新的永久性复活它,那么甚至永久甚至是是什么意思? 5 )


  • 因为合并本身就是一个新的提交,所以您可以在任何时候重复合并或至少任何自动的Git执行部分。旧的提交是永久的,您可以简单地从旧提交中的某个匿名分支上获取,并合并其他旧提交,以重复Git的工作。如果旧的提交因合并冲突而停止,那么新的提交将停止,并具有相同的冲突(无论如何,只要您使用相同的合并策略和选项)。 在冲突的情况下,您不能直接重复您选择的任何手形解决方案......但您不必分辨率记录在合并提交中。如果你弄错了,那么你可以重做合并,获取任何 正确的最终分辨率,然后重新手工解决剩下的冲突。这包括删除和/或复活文件。




总之,让我们看看这些工具合并:




  • git diff 。 Git将把合并基础提交的更改结合到两个提示提交中。您可以在合并之前,之中和/或之后查看这些更改。有一个很好的快捷预合并,停止工作后合并(因为当前分支名称现在指向新的合并提交):

      git diff A ... b 
    git diff B ... A

    (注意三个点)。第一个命令比较合并基础提交 B ,第二个命令比较合并基础提交 A 。添加 - name-status (和可选的 - diff-filter = D 只选择状态为'D 'eleted)查看哪些文件在哪一边被删除,并查看这些文件是否被修改( - diff-filter = M ,对于'M' ) 另一方面;这会告诉你是否 git merge 会删除它们。

  • -no-commit 到 git merge ,它会在合并后停止,即使没有冲突可以解决:

     自动合并进行得很顺利;在提交请求之前停止

    现在您可以对树进行手动更改。这些当然不是自动的,所以以后的重新合并不会这样做。这是一个好主意吗?这取决于你想要从你的永久提交中获得什么!


  • 在解析过程中的某个点(从 - no-commit 或归因于合并冲突),您可以从合并的任一侧提取特定文件。



    通常,在合并冲突期间,仔细检查一切后。例如,我们可能会认为合并应该从我们的本地提交中获取我们的文件版本,完全忽略其他提交的更改:

      git checkout --ours  -  file.ext 

    或者我们可以决定其他提交是正确的:

      git checkout  - 他们 -  file.ext 

    只有在文件发生冲突时才能使用。如果Git自己解决了合并,我们可能需要一种不同的方式来命名文件。我们可以简单地指定具有我们想要的版本的提交:

      git checkout a012345  -  file.ext 

    为例。但是输入SHA-1哈希是丑陋的并且容易出错。幸运的是,我们可以使用两个名字: HEAD 是本地提交(我们的), MERGE_HEAD 是另一个提交(他们),所以:

      git checkout HEAD  -  file.ext 
    git checkout MERGE_HEAD - restored1.py restored2.tex

    - 是通常不需要,通常是一个好主意:例如,如果你需要的文件是奇怪的 - 他们的,这是一个很好的习惯,可以保护你)。




最后,您当然可以进行合并,让它进行提交,然后提取您想要恢复的任何文件 - 可能来自其他分支的提示:

$ g $ check g - restore.me restore.me.too

并进行另一次提交,或者使用 git commit --amend 用合并提交来替换合并提交,该合并提交具有恢复的文件。 6




1 可能为了更好地检测文件重命名,护理或至少应该能够。这完全是另一个讨论。

2 从技术上讲,可能有多个 合并基础。如果是这样, git merge-base --all 将会找到所有这些;如果是这样,需要做更多的工作来预测会发生什么。对于大多数合并,只有一个。



3 Git没有(也不一定)关心提交的时间戳,而是取决于图形的形状。因此,最近期并不完全正确,但它确实捕捉到如果两个分支上都有很多提交 - 这几乎总是正确的 - 我们希望那个最接近本地和其他提交,而且我们追溯到第一个的后面,即使这些提交的时间戳是奇怪的,那些较早的提交也是以图表历史记录的方式出现的。

事实上,有些东西会被丢弃,但只有一次它们是未被引用的
,这是一个单独讨论的主题。



5 事实上,这个问题至少对于Git来说是严格正确的答案。该答案与Git哈希的身份相关联,这与您如何使用数字签名在特定提交中签字相关。任何事物的实际持久性都是它的哈希ID:只要哈希ID保持有效,提交依然存在,保持永久永久...除非,即你设法破坏SHA-1哈希。



6 请注意 - 修改(并且就此而言, rebase ,特别是 -i 似乎违反了永远保存所有内容规则。但事实上,他们不会:他们不会替换任何东西,他们只是添加新的提交,并从 git log 。隐藏的内容最终会变得完全没有引用,因此会像脚注4中那样失效,但你有足够的时间 - 通常至少一个月 - 才能让它们恢复。而且,仍然可以在 git log --all 永远过期时查看的任何提交 - 仅仅是被 - 修改或rebase,即一旦离开,最终会过期。


Ok, so the basic idea is that there are some files that I want to ensure get reinstated or "undeleted".

I am not exactly sure what happened, but I have git branches A and B and I am 99% that B was branched off of A. After I created B, I deleted some files from B and then made a lot of changes. I now want to merge with A, but I am afraid that what will happen is that the deleted files from B will be permanently dropped when I merge the branches.

Has anyone handled this type of situation before? The first thing I will try is to create a branch C from A and then try merging C with B, but my guess is that doesn't solve the problem.

解决方案

I am not exactly sure what happened, but I have git branches A and B and I am 99% that B was branched off of A.

The merge process does not care how you got to where you are now,1 only where you actually are. More specifically, it needs three items:

  • Your current commit (HEAD), on whatever branch it's on, if any. Let's call this "our" commit and/or the "local" commit. When we need it (soonish), Git has --ours.

  • Your "other" commit, from the argument to git merge: e.g., git merge foo will resolve the name foo to find its specific commit-ID. Most parts of Git that give it a name, call this "their" commit (although one part calls it the "remote" commit, which is particularly confusing since it has nothing to do with Git's fetch and push remotes). Let's just call this the "other" commit, but when we need it (soonish) Git uses --theirs.

    This --ours and --theirs stuff holds even if both commits are made by you (another reason to call them "local" and "other", I think).

  • The merge base.2 This is the most recent3 commit that is on both branches, i.e., the point from which the two branches diverge.

It's this third thing—the merge base—that you are a bit vague about. You can simply instruct Git to tell you which commit that is, using the command git merge-base.

After I created B, I deleted some files from B and then made a lot of changes. I now want to merge with A, but I am afraid that what will happen is that the deleted files from B will be permanently dropped when I merge the branches.

They probably will—except that nothing is permanent in Git. Or we could say that everything is permanent which means nothing is permanent, which is getting awfully philosophical, but is basically like saying that if everything is 100% critical, then nothing really matters, or at least, nothing matters more than anything else. :-)

Here are some things to consider, and then some tools to consider for providing these things:

  • Git performs a merge by comparing the merge base to the two (local and other) commits. This tells it, in effect, what has happened on each "side" of the branching that happened since the common base commit.

    The merge is done by taking one copy of each change made on each side. Git declares a conflict whenever it can't do that.

    So, suppose file misc.txt is deleted in the base-to-local change-set, and has a line added in the base-to-other change-set. How do we combine "delete entire file" with "add this line"? Git's answer is: "I don't, I just declare a merge conflict on that file."

    On the other hand, suppose file samp.dat is deleted in base-to-local and has no change made in base-to-other. How do we combine these? The answer is clear: delete the file.

    There are numerous other conflict cases, but the above shows the ones you are particularly worried about, here.

  • Do you want to merge A into B, or B into A? What, precisely, is the difference?

    For instance, above, we just said what happens if base-to-local deletes samp.dat while base-to-other leaves it unmodified: Git deletes samp.dat in the merge result. If base-to-other deletes the file, and base-to-local leaves it unmodified, Git also deletes the file, as the change is still "one side says delete, one side says do nothing". It doesn't matter which side says it, just that it's exactly one side. (Or, if both sides say to delete the file, there's no problem there either.)

    In fact, the merge result is always the same: one set of changes says do X, the other says do Y; if Git can combine them, the result is that combination, otherwise the result is "conflict", with whatever Git can do best to record both sets of changes. But in all cases the result is symmetric: combine(X, Y) ≡ combine(Y, X). (If it doesn't show up in all fonts there is a three-line "=" sign, "equivalent to" symbol, between the two "combine"s.)

    The only difference, then, is that Git will make the new merge commit on whatever branch you are on when you start the process—and of course, whatever branch you are on, you need to name the other branch (or its tip commit) in the git merge command. It's the work-tree (combined merge result) that is the same, not the commit itself.

  • Finally, suppose that you have some commit—maybe a merge commit, or maybe just a regular ordinary commit—that removes file misc.txt. Now, remembering that Git is a version control system and saves everything forever,4 suppose that we tell Git to extract misc.txt from the commit just before this commit, and make a new commit. The new commit has, as its work-tree contents, everything that was in the one that removed misc.txt plus the version of misc.txt resurrected from the earlier commit.

    If I direct you to look at the new commit instead of the old one, will you care how misc.txt got resurrected? (There are cases where you might care nothing, cases where you might care a little, and a few cases where you might care a lot, so this is a question to ponder for a while, or come back to eventually. But it gets at the philosophical question of what it means for anything to be permanent as long as everything is. The old commit permanently removed misc.txt and the new one permanently resurrected it, so what does "permanent" even mean?5)

  • Since the merge is itself a new commit, you can repeat the merge, or at least any automatic, Git-performed portion of it, any time later. The old commits are permanent and you can simply get on an anonymous branch from one of the old commits, and merge the other old commit, to repeat Git's work. If the old commit stopped with merge conflicts, the new one will stop with the same conflicts (as long as you use the same merge strategy and options, anyway).

  • In the case of a conflict, you can't repeat directly any hand resolution you chose ... but you don't have to, as the final resolution is recorded in the merge commit. If you got it wrong then, you can redo the merge, grab any final resolutions that are correct, and re-hand-resolve the remaining conflicts. This includes removing and/or resurrecting files.

Anyway, with those out of the way, let's look at these tools for merging:

  • git diff. Git is going to combine the changes from the merge base commit, to the two tip commits. You can look at these changes, before, during, and/or after the merge. There is a nice shortcut pre-merge, that stops working post-merge (because the current branch name now points to the new merge commit):

    git diff A...B
    git diff B...A
    

    (note the three dots). The first command compares the merge base to commit B, and the second compares the merge base to commit A. Add --name-status (and optionally --diff-filter=D selects only files whose status is 'D'eleted) to see which files are deleted on which side(s), and see if those files are modified (--diff-filter=M, for 'M'odified) on the other side; this will tell you if git merge will delete them.

  • Add --no-commit to git merge and it will stop after merging, even if there are no conflicts for you to resolve:

    Automatic merge went well; stopped before committing as requested
    

    You may now make by-hand changes to the tree. These are, of course, not automatic, so a later re-merge will not do them. Is this a good idea? That depends on what you want from your permanent commits!

  • At some point during resolving (either from --no-commit or due to a merge conflict), you can extract specific files from either side of the merge.

    Normally, one does this during a merge conflict, after inspecting everything carefully. We might, for instance, decide that the merge should take our version of the file from our local commit, completely ignoring their changes from the other commit:

    git checkout --ours -- file.ext
    

    Or we might decide that the other commit is the right one:

    git checkout --theirs -- file.ext
    

    These work only if the file is conflicted. If Git resolved the merge on its own, we may need a different way to name the file. We can simply specify the commit that has the version we want:

    git checkout a012345 -- file.ext
    

    for instance. But typing in SHA-1 hashes is ugly and error prone. Fortunately, there are two names we can use: HEAD is the local commit (ours) and MERGE_HEAD is the other commit (theirs), so:

    git checkout HEAD -- file.ext
    git checkout MERGE_HEAD -- restored1.py restored2.tex
    

    (the -- is not usually required, just usually a good idea: it is a good habit that protects you if the file you need is the oddly-named --theirs, for instance).

Finally, you can of course just do the merge, letting it make a commit, then extract any files you want restored—probably from the tip of the other branch:

git checkout A -- restore.me restore.me.too

and make another commit, or use git commit --amend to replace the merge commit with a new merge commit that has the restored files.6


1It probably should care, or at least, should be able to, in order to detect file renames better. That's another discussion entirely though.

2Technically, there could be more than one merge base. If so, git merge-base --all will find all of them; and if so, it takes more work to predict what will happen. For most merges, though, there is only one.

3Git does not (and must not) care about the time stamps on the commits, but rather on the shape of the graph. Hence "most recent" is not quite right, but it does capture the idea that if there are many commits on both branches—which is almost always true—we want the one "closest to" the local and other commits, and the further back we go behind the "first" one, the older those commits are in terms of graph-history, even if the time stamps on them are bizarre.

4In fact, some things do get discarded, but only once they are unreferenced, which is a topic for a separate discussion.

5There is, in fact, a strictly correct answer to this question, at least for Git. That answer ties into the identity of Git's hashes, which is connected to how you can use digital signatures to "sign off" on particular commits. The actual permanence of anything is its hash ID: as long as the hash ID remains valid, the commit still exists, keeping the "permanent" permanent ... unless, that is, you manage to break SHA-1 hashing.

6Note that --amend (and, for that matter, rebase, especially with -i) seem to violate the "save everything forever" rule. But in fact, they don't: they do not replace anything, they merely add new commits, and hide the old ones from git log. The hidden ones do eventually become entirely unreferenced, and hence expire as in footnote 4, but you have plenty of time—normally at least a month—to get them back. And, any commit still view-able in regular git log --all never expires—it's just the ones shoved aside by --amend or rebase, that, once out of view, eventually expire.

这篇关于合并两个分支A和B,其中A包含从B中删除的文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆