简体   繁体   English

在 git 与 meld 合并期间,如果只保存 MERGED,为什么我可以修改 LOCAL 和 REMOTE?

[英]during a git merge with meld, why can I modify LOCAL and REMOTE if only MERGED will be saved?

according to one of the answers to this question https://stackoverflow.com/a/18011273/5238559 , LOCAL, BASE and REMOTE files will not be altered in the merge process, but only the resulting MERGED file.根据此问题https://stackoverflow.com/a/18011273/5238559的答案之一,在合并过程中不会更改 LOCAL、BASE 和 REMOTE 文件,而只会更改生成的 MERGED 文件。

during a merge in meld, I would modify the middle panel (BASE) by moving over code from left (LOCAL) and right (REMOTE).在合并期间,我将通过从左侧(本地)和右侧(远程)移动代码来修改中间面板(BASE)。 I understood that BASE will be a sort of "preview" for what the finally merged file will look like, but it wont be saved directly, which seems like a logical safety step.我知道 BASE 将是最终合并文件外观的一种“预览”,但不会直接保存,这似乎是一个合乎逻辑的安全步骤。

however, I can also move code from BASE to LOCAL or REMOTE, and, when I close meld, I'll be asked to save the changes to all three files.但是,我也可以将代码从 BASE 移动到 LOCAL 或 REMOTE,当我关闭 meld 时,系统会要求我保存对所有三个文件的更改。 why can I do this if only BASE (ie MERGED) is relevant to the merge process?如果只有 BASE(即 MERGED)与合并过程相关,为什么我可以这样做? what happens with the modifications in LOCAL and REMOTE? LOCAL 和 REMOTE 中的修改会发生什么?

TL;DR of the TL;DR TL;DR 的 TL;DR

Git doesn't use your working tree files except when you (or something) run(s) git add . Git 不使用您的工作树文件,除非您(或其他)运行git add Note that git mergetool runs git add on only one of the files that meld works with.请注意, meld git mergetool运行git add一个可以使用 meld 的文件。 So you can write as many extra files as you like.因此,您可以编写任意数量的额外文件。 Git doesn't care. Git 不在乎。 It only cares about that one particular file when meld is done. meld完成后,它只关心一个特定的文件

TL;DR TL;博士

Presumably you're running this merge tool meld via git mergetool .大概您正在通过meld git mergetool运行此合并工具。 The way git mergetool works is ridiculously simple, once you understand how merge itself works, and that's why you can modify all these files: because they are all just files . git mergetool的工作方式非常简单,一旦您了解合并本身的工作原理,这就是您可以修改所有这些文件的原因:因为它们都只是文件

For all this to make sense, you need to know how git merge works.为了使这一切有意义,您需要了解git merge的工作原理。 This gets us into the distinctions between:这使我们能够区分:

  • commits, which are how Git actually stores stuff;提交,这是 Git 实际存储内容的方式;
  • Git's index , which has three names; Git 的index ,有三个名字; it is involved in making commits, and it takes on an expanded role during merging;它参与提交,并在合并期间发挥更大的作用; and
  • your working tree or work-tree (both names refer to the same thing), which holds files that you, and programs like meld or vim or whatever, can actually see and edit.您的工作树工作树(两个名称指的是同一事物),其中包含您以及诸如vim meld其他程序之类的文件,它们实际上可以查看和编辑。

The third one of these—your work-tree—is the only place that holds files that you can see.其中第三个——你的工作树——是唯一保存你可以看到的文件的地方。 But—and this is very important—your work-tree is not in Git at all .但是——这非常重要——你的工作树根本不在 Git 中 It's just a place that Git sticks files into, so that you can see them and work on / with them.这只是 Git 将文件粘贴到的地方,以便您可以看到它们并使用它们进行处理。 Later, git add will copy one of these files back into Git's index.稍后, git add会将其中一个文件复制回 Git 的索引中。 If you use git mergetool to run a merge tool, the git mergetool code runs git add for you.如果您使用git mergetool合并工具运行合并工具,则git mergetool合并工具代码为您运行git add

The mergetool script runs git add on the merged file (by name) so whatever is in that file is what gets git add ed. mergetool 脚本在合并文件(按名称)上运行git add ,因此该文件的任何内容都是git add ed。 Any remaining files are just junk as far as Git is concerned: they are simply untracked files.就 Git 而言,任何剩余的文件都只是垃圾:它们只是未跟踪的文件。 I believe mergetool should clean up the junk files (but should does not mean always will and opinions may differ on the should part too; there's a "keep backup" option here, which I have never used).我相信 mergetool 应该清理垃圾文件(但应该不意味着总是会,并且意见也可能在应该部分有所不同;这里有一个“保留备份”选项,我从未使用过)。

Long

You may be able to skip some sections below, depending on how familiar you are with Git.您可以跳过以下部分,具体取决于您对 Git 的熟悉程度。 I will try to keep them short (by leaving a lot out) but they are still going to be long.我会尽量让它们保持简短(通过省略很多),但它们仍然会很长。

More background on commits有关提交的更多背景信息

Each Git commit is given a unique number.每个 Git 提交都有一个唯一编号。 These numbers are not simple counting numbers—we don't have commit #1 followed by #2, then #3, and so on.这些数字不是简单的计数数字——我们没有提交 #1,然后是 #2,然后是 #3,依此类推。 Instead, the numbers are random-looking, big, ugly hash IDs computed by a cryptographic hash function.相反,这些数字是由密码 hash function 计算的随机的、大而丑的hash ID These numbers are unique across all Git repositories everywhere (which is how Git manages the distributed nature of commits), but all we need to know here is that commits are numbered.这些数字在所有 Git 存储库中都是唯一的(这是 Git 管理提交的分布式性质的方式),但我们需要知道的只是提交是有编号的。

Each commit holds two things.每个提交包含两件事。 All parts of the commit are read-only, so these things are unchangeable, and are valid forever—or at least as long as the commit itself continues to exist:提交的所有部分都是只读的,所以这些东西是不可更改的,并且永远有效——或者至少只要提交本身继续存在:

  • Each commit has a full snapshot of every file, stored in a special archival format that only Git can read.每个提交都有每个文件的完整快照,以只有 Git 可以读取的特殊存档格式存储。 (This format is compressed, often highly so, and de-duplicates file contents. It can store files that your OS may be unable to use effectively, or even check out in some cases; in those cases, merging will be difficult or impossible.) The files that are in the commit are determined by what is in Git's index, as described in the next section, at the time someone runs git commit . (这种格式是经过压缩的,通常是高度压缩的,并且对文件内容进行重复数据删除。它可以存储您的操作系统可能无法有效使用的文件,甚至在某些情况下检出;在这些情况下,合并将很困难或不可能。 ) 提交中的文件由 Git 索引中的内容确定,如下一节所述,当时有人运行git commit

  • Each commit also has some metadata , or information about the commit itself.每个提交也有一些元数据,或者关于提交本身的信息。 This includes the name and email address of an author, and another for a committer.这包括作者的姓名和 email 地址,以及提交者的另一个地址。 Each of those has a date-and-time-stamp.每一个都有一个日期和时间戳。 There is space for a log message, to be written by whoever makes the commit, to describe why they made this commit.有空间放置日志消息,由做出提交的人编写,以描述他们做出此提交的原因 And, so that Git can string commits together backwards, each commit records the hash ID(s) of its parent commit(s).并且,为了使 Git 可以将提交串在一起,每个提交记录其父提交的 hash ID。

A merge commit is simply a commit that has at least two parent hash IDs in it.合并提交只是一个提交,其中至少有两个父 hash ID。 The git merge command often makes such a commit at the end: the first parent is the same parent that any ordinary non-merge commit would have, and the second parent is the hash ID of the commit that you just merged (eg, the tip commit of a branch you merged by branch-name). git merge命令通常在最后进行这样的提交:第一个父级与任何普通的非合并提交具有相同的父级,第二个父级是您刚刚合并的提交的 hash ID(例如,提示您按分支名称合并的分支的提交)。 The snapshot part of a merge is the same as any commit: it's just a full copy of every file as recorded in Git's index at the time the merge is completed.合并的快照部分与任何提交相同:它只是合并完成时记录在 Git 索引中的每个文件的完整副本。

Git's index, and how it expands during merges Git 的索引,以及它在合并期间如何扩展

Git's index has three names: Git calls it the index (as I am doing here), the staging area (for normal commits at least), and—rarely these days, mostly in flags like --cached —the cache . Git 的索引有三个名称:Git 将其称为索引(就像我在这里所做的那样)、暂存区(至少对于正常提交)以及——现在很少见,主要在--cached之类的标志中——缓存 For normal, non-merge commits, I like to describe the index as holding your proposed next commit .对于正常的非合并提交,我喜欢将索引描述为保存您提议的下一次提交

What's in the index is—normally—a list of tuples: name, mode, and hash ID:索引中的内容通常是一个元组列表:名称、模式和 hash ID:

  • The name is a file name, complete with forward slashes like top/sub/file.ext .该名称是一个文件名,带有正斜杠,如top/sub/file.ext At this level, Git doesn't "think about" directories holding files: it just has files with long names that contain slashes.在这个级别上,Git 不会“考虑”保存文件的目录:它只是具有包含斜杠的长名称的文件。 Even on Windows, these slashes go forwards, even though Git has to put such a file into a file named file.ext inside a folder named top containing a subfolder sub , which Windows would prefer to express as top\sub\file.ext . Even on Windows, these slashes go forwards, even though Git has to put such a file into a file named file.ext inside a folder named top containing a subfolder sub , which Windows would prefer to express as top\sub\file.ext . The index insists on forward slashes internally.该指数在内部坚持使用正斜杠。 (This normally doesn't show up to users, it's just a way to understand the problem Git has that prevents it from storing an empty folder. Such a thing simply can't exist in Git's index: the index only holds files .) (这通常不会向用户显示,这只是一种理解 Git 阻止它存储空文件夹的问题的一种方式。这样的事情根本不能存在于 Git 的索引中:索引只包含文件。)

  • The mode, for an ordinary file, really just remembers whether it's +x or -x : an executable file, or a non-executable file.对于普通文件,模式实际上只记住它是+x还是-x :可执行文件或不可执行文件。 For hysterical reasons this is stored as either 100755 or 100644 respectively.出于歇斯底里的原因,这分别存储为100755100644

  • The hash ID has to do with how Git stores file content internally, as a blob object . hash ID 与 Git 如何在内部存储文件内容有关,作为blob object These things are compressed and read-only, and if the object is stored as a packed object, it may be even-more-compressed using delta encoding .这些东西是压缩的和只读的,如果 object 存储为打包的 object,它可能会使用delta encoding进行更多压缩。

Again, that's in the normal, non-merge case.同样,这是在正常的非合并情况下。 These entries have a stage number (because the index is the "staging area") that is always zero.这些条目的阶段号(因为索引是“暂存区”)始终为零。 This is what makes them normal.这就是使它们正常的原因。

When git merge starts, it expands the index .git merge开始时,它会扩展索引 It replaces all the stage-zero entries, which represent the current commit –the index needs to match the current commit at the start of the merge operation—with stage 2 entries.它将代表当前提交的所有阶段零条目(索引需要在合并操作开始时与当前提交匹配)替换为阶段 2条目。 This also opens up spaces for stage 1 and stage 3 entries.这也为第 1阶段和第 3 阶段的条目打开了空间。 We'll come back to this below.我们将在下面回到这一点。

Your working tree你的工作树

Both committed files—which are stored via blob hash IDs—and the index, which literally stores these same kinds of blob hash IDs, store the internal format versions of Git files, in which contents are compressed and de-duplicated, and maybe even delta-encoded. Both committed files—which are stored via blob hash IDs—and the index, which literally stores these same kinds of blob hash IDs, store the internal format versions of Git files, in which contents are compressed and de-duplicated, and maybe even delta -编码。 This format is suitable for archiving (because it's compressed and de-duplicated) but not for getting any actual work done.这种格式适用于存档(因为它是压缩和去重复的),但不适用于完成任何实际工作。 So Git has to extract such a file, from a commit or from Git's index, expanding out any compression.所以 Git 必须从提交或 Git 的索引中提取这样的文件,扩展任何压缩。

The result of extracting an archived blob object goes into an ordinary file.提取存档 blob object 的结果进入普通文件。 These files need to live somewhere, and that somewhere is your working tree.这些文件需要存在于某个地方,而那个地方就是你的工作树。 So git checkout or git switch works by copying files out, from a commit into Git's index—this part is fast and cheap as the index holds the files in the same format as the commit—and then to your working tree.因此, git checkoutgit switch通过将文件从提交复制到 Git 的索引来工作 - 这部分快速且便宜,因为索引以与提交相同的格式保存文件 - 然后到您的工作树。

The copying out to your working tree is slowish, but Git gets to cheat.复制到工作树的速度很慢,但 Git 会作弊。 Because the index keeps track of what's in your working tree, Git can usually tell very quickly if the working tree file is untouched from the last checkout.因为索引会跟踪您的工作树中的内容,所以 Git 通常可以非常快速地判断工作树文件是否在上次结帐时未被触及。 It can also tell, just by checking hash IDs, whether the file in the new commit you're checking out now is the same as the file in the old commit you had checked out before.它还可以通过检查 hash ID 来判断您现在签出的新提交中的文件是否与您之前签出的旧提交中的文件相同。 If all goes well—and usually it does—Git can just leave the file alone , so it does.如果一切顺利(通常情况下),Git 可以不理会文件,它确实如此。

In principle, then, a git checkout of a different commit has to remove every old file (from Git's index and your working tree) and then fill in every new file from the new commit.那么,原则上,不同提交的git checkout出必须删除每个旧文件(从 Git 的索引和您的工作树中),然后从新提交中填写每个新文件。 Git just skips a lot of this work, which means a multi-megabyte or gigabyte checkout can take very little time (sometimes just a few milliseconds but this depends strongly on OS, caches, and other details, and also on the switch from commit X to commit Y not needing to change a lot of working tree files). Git 只是跳过了很多这项工作,这意味着数兆字节或千兆字节的检出可能需要很短的时间(有时只需几毫秒,但这在很大程度上取决于操作系统、缓存和其他细节,以及从提交 X 的切换提交 Y 不需要更改很多工作树文件)。

Other than this, though, your working tree is just a regular old set of files and directories / folders (whichever term you prefer).但是,除此之外,您的工作树只是一组常规的旧文件和目录/文件夹(无论您喜欢哪个术语)。 Everything that works on your computer, works here.在您的计算机上运行的一切都在这里运行。 Aside from writing into it when you tell it—eg, with git checkout —Git just lets you play with it to your heart's content.除了在你告诉它时写入它——例如,使用git checkout ——Git 只是让你随心所欲地玩它。 Then you can run git status , which only looks at it , or git add , which copies from it into Git's index.然后你可以运行git status ,它只查看它,或者git add ,它从它复制到 Git 的索引中。 Until you do either of these, though, Git is completely hands-off.但是,除非您执行其中任何一项,否则 Git 将完全不干涉。

In short, your working tree is yours , to do with as you will.简而言之,您的工作树是的,您可以随意使用。 You can create files here that Git never needs to know about.您可以在此处创建 Git 永远不需要知道的文件。 As long as (a) you don't git add them and (b) they never come out of some existing commit, they never get into Git's index, and Git never knows about them.只要(a)您没有git add它们,并且(b)它们永远不会出现在某些现有提交中,它们永远不会进入Git 的索引,并且 Git 永远不会知道它们。 The git status command will whine about them, and you will need to list such files in .gitignore to make Git shut the bleep up , but other than that, they're quite irrelevant. git status命令会抱怨它们,您需要在.gitignore中列出此类文件以使 Git关闭哔哔声,但除此之外,它们完全无关紧要。

Internals of the three-way merge三路合并的内部结构

When we run git merge , we quite typically are doing a three-way merge, which can have conflicts.当我们运行git merge时,我们通常会进行三向合并,这可能会产生冲突。 To understand what's happening, let's look at a sample commit graph , ie, a set of commits as found in some Git repository.要了解发生了什么,让我们看一个示例提交图,即在一些 Git 存储库中找到的一组提交。 Because the hash IDs of real commits are incomprehensible, we'll use single uppercase letters to stand in for them, like this:因为实际提交的 hash ID 难以理解,我们将使用单个大写字母代替它们,如下所示:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

I've added two branch names, branch1 —which we currently have checked out, ie, we're using commit J to fill Git's index and our working tree—and branch2 , which selects commit L .我添加了两个分支名称, branch1 ——我们目前已经签出,即,我们使用提交J来填充 Git 的索引和我们的工作树——和branch2 ,它选择提交L The (HEAD) notation shows that we have branch1 checked out. (HEAD)表示法表明我们已branch1 All six listed commits are ordinary single-parent commits, so that as viewed from commit J —ie, git log if we were to run it right now—we see, as history, commit J first, then commit I , then commit H , then commit G , and so on.所有六个列出的提交都是普通的单亲提交,所以从提交J来看——即,如果我们现在运行它, git log ——我们看到,作为历史,首先提交J ,然后提交I ,然后提交H ,然后提交G ,依此类推。 As viewed from commit L —if we run git log branch2 —we see commit L first, then K , then H , then G , and so on as before.从提交L来看——如果我们运行git log branch2我们首先看到提交L ,然后是K ,然后是H ,然后是G ,依此类推。

These two commit histories meet up , when we go backwards like this, at commit H .这两个提交历史相遇,当我们 go 像这样向后,在提交H So commit H is the merge base in this three-way merge.所以 commit H是这个三向合并中的合并基础

The goal of a merge is to combine work.合并的目标是合并工作。 We'd like to have Git figure out, on its own, what we changed since commit H .我们想让 Git 自己弄清楚自提交H以来我们所做的更改 These are "our changes".这些是“我们的改变”。 We'd like to have Git figure out what they changed since commit H .我们想让 Git 弄清楚自提交H以来他们发生了什么变化。 These are "their changes".这些是“他们的变化”。 Git can in fact do this, using git diff : Git 实际上可以做到这一点,使用git diff

git diff --find-renames <hash-of-H> <hash-of-J>

This will produce a list of each file we changed, and what lines need to be deleted and added to each of those files to turn the copies of those files that exist in commit H into the copies of those same files that exist in J .这将生成我们更改的每个文件的列表,以及需要删除哪些行并将其添加到每个文件中,以将提交H中存在的那些文件的副本转换为存在于J中的相同文件的副本。

Similarly:相似地:

git diff --find-renames <hash-of-H> <hash-of-L>

will produce a list of files they changed, and what lines need to be modified in those files.将生成他们更改的文件列表,以及这些文件中需要修改的行。

If Git simply (simply?) combines these two lists and applies both sets of changes to the files taken from commit H , Git will arrive at a set of files that keeps our changes ( H -to- J ) and adds their changes ( H -to- L ).如果 Git 简单地(简单地?)组合这两个列表并将两组更改应用于从提交H获取的文件,Git 将到达一组文件,保留我们的更改( H -to- J )并添加他们的更改( H - 到 - L )。 In many cases, some file we changed will have no changes on their side, and vice versa.在许多情况下,我们更改的某些文件不会对其进行更改,反之亦然。 These will be easy for Git.这些对于 Git 来说很容易。 In some cases, some files will have changes on both sides .在某些情况下,某些文件的两边都会发生变化。 If those changes touch different lines, Git may be able to combine those changes on its own.如果这些更改涉及不同的线路,Git 可能能够自行组合这些更改。

These are the rules that Git uses, anyway.无论如何,这些是 Git 使用的规则。 It just:这只是:

  • Extracts (into Git's index) every file in H : these go into the slot-1 entries.H中的每个文件提取(到 Git 的索引中):这些 go 到 slot-1 条目中。
  • Extracts (into Git's index) every file in J : these go into the slot-2 entries.J中的每个文件提取(到 Git 的索引中):这些 go 到 slot-2 条目中。 Of course they were already there in slot 0, so no extracting is needed;当然它们已经在 slot 0 中了,所以不需要提取; Git can just move the slot-0 entries to slot-2. Git 可以将 slot-0 条目移动到 slot-2。 (When using git cherry-pick -n or similar, Git really does need to just move slot entries, because these cases don't require that the index match anything. But that's a special case that git merge does not normally allow.) (当使用git cherry-pick -n或类似方法时,Git 确实只需要移动插槽条目,因为这些情况不需要索引匹配任何内容。但这是git merge通常不允许的特殊情况。)
  • Extracts (into Git's index) every file in L : these go into the slot-3 entries.L中的每个文件提取(到 Git 的索引中):这些 go 到 slot-3 条目中。

The index now has three copies of each file, from merge base commit ( BASE ), --ours commit ( LOCAL ), and theirs ( REMOTE ).该索引现在具有每个文件的三个副本,来自合并基本提交 ( BASE )、 --ours提交 ( LOCAL ) 和他们的 ( REMOTE )。 Each of these is really just a hash ID, for an internal Git blob object (well, plus the name and mode, with the staging number representing the slot).其中每一个实际上只是一个 hash ID,用于内部 Git blob object(好吧,加上名称和模式,以及代表插槽的暂存号)。 1 1

Because of the de-duplication trick, if nobody made any changes to the file, all three staging slots will hold the same hash ID (and mode) and Git can just collapse all three index entries back down to a single slot-zero entry.由于重复数据删除技巧,如果没有人对文件进行任何更改,所有三个暂存槽都将保存相同的 hash ID(和模式),并且 Git 可以将所有三个索引条目折叠回单个零槽条目。 If we changed the file, but they didn't , the base and their slot will have the same hash ID (and mode) and ours will differ and Git will just take our version of the file, moving slot 2 to slot zero and erasing slots 1 and 3. If they changed the file and we didn't, the base and our slot will have the same hash ID and theirs will differ and Git will just take their version of the file, moving slot 3 to slot zero, etc.如果我们更改了文件,但他们没有更改,则基础及其插槽将具有相同的 hash ID(和模式),而我们的 ID 将不同,Git 将只采用我们的文件版本,将插槽 2 移动到插槽 0 并擦除插槽 1 和 3。如果他们更改了文件而我们没有更改,则基础和我们的插槽将具有相同的 hash ID 并且它们的 ID 会有所不同,并且 Git 只会采用他们的文件版本,将插槽 3 移动到插槽 0 等.

This means we only ever have to work hard for files where both sides made changes (well, or for high-level / tree conflicts, which I'll skip over here).这意味着我们只需要努力处理双方都进行了更改的文件(好吧,或者对于高级/树冲突,我将在此处跳过)。 In this case, the various merge strategies that Git has today work by:在这种情况下,Git 今天采用的各种合并策略通过以下方式起作用:

  • invoking the merge driver, if there is one: this program must do the job;调用合并驱动程序,如果有的话:这个程序必须完成这项工作; or或者
  • invoking the built-in low-level merge driver, otherwise.否则调用内置的低级合并驱动程序。

The built-in low-level merge driver works on a line-by-line basis, using git diff on the individual files.内置的低级合并驱动程序逐行工作,对单个文件使用git diff 2 For each diff-hunk you'd see in git diff output, it looks to see if the other side has touched the same lines , or lines that "touch" another change (eg, if "our" diff adds a line at the end and "their" diff also adds a line at the end, Git has no idea which order to use when adding both sets of lines). 2对于您在git diff output 中看到的每个 diff-hunk,它会查看另一侧是否触及相同的行,或者“触及”另一个变化的行(例如,如果“我们的”差异在end 和“他们的”差异也在末尾添加一行,Git 不知道添加两组行时使用哪个顺序)。 3 It writes, to our working tree copy of the file in question, Git's best guess at the correct merge. 3它会将 Git 对正确合并的最佳猜测写入相关文件的工作树副本 If this all goes well—if Git is able to combine the two sets of changes without conflicts—Git then does an internal git add on the file.如果一切顺利——如果 Git 能够组合这两组更改而不会发生冲突——Git 然后会在文件上git add If not, Git leaves the conflicts in the working tree copy of the file , complete with conflict markers, and doesn't do an internal git add on the file.如果没有,Git将冲突留在文件的工作树副本中,并带有冲突标记,并且不会在文件上执行内部git add

When the low level driver encounters something that is considered a conflict, if there is an extended-argument -X ours or -X theirs in effect, it will just take our change (from 1-vs-2) or their change (1-vs-3) according to the -X value, and not put in any conflict markers.当低级驱动程序遇到被认为是冲突的事情时,如果存在有效的扩展参数-X ours-X theirs ,它将只接受我们的更改(来自 1-vs-2)或他们的更改(1- vs-3) 根据-X值,并没有放入任何冲突标记。 So low-level conflicts can be resolved automatically in software using these flags.因此,可以使用这些标志在软件中自动解决低级冲突。 Note, though, that Git doesn't do anything smart here.但是请注意,Git 在这里没有做任何聪明的事情。 It just picks the 1-vs-2 file diff, or the 1-vs-3 file diff, on the basis of a line-by-line diff hunk.它只是根据逐行差异块选择 1-vs-2 文件差异或 1-vs-3 文件差异。 But this does let Git run an internal git add on its own.但这确实让 Git 运行内部git add

When Git does run an internal git add , this simply takes the working tree copy of the file and copies it into slot zero, erasing slots 1 through 3 for that file.当 Git确实运行内部git add时,这只是获取文件的工作树副本并将其复制到插槽 0,擦除该文件的插槽 1 到 3。 That marks the file as resolved.这将文件标记为已解决。 The index shrinks back to normal, for that one set of file entries.对于那一组文件条目,索引收缩回正常。 After all files have been processed, either there are some conflicts still showing in Git's index (because some file didn't get pre-collapsed and did not get git add -ed), or there aren't (all files got an easy index collapse, or got git add -ed after the low level driver did its thing).处理完所有文件后,Git 的索引中仍然显示一些冲突(因为某些文件没有预先折叠并且没有得到git add -ed),或者没有(所有文件都有一个简单的索引崩溃,或者得到git add -ed 在低级驱动程序完成它的事情之后)。


1 The design here was supposed to allow more than one slot-1 entry when doing recursive merge, but that never went anywhere. 1这里的设计应该在进行递归合并时允许多个 slot-1 条目,但这从未发生过。 It's not clear if it could go anywhere as there are some very tricky corner cases with files that don't exist in one or two of the three commits, and they get trickier if you allow this kind of thing.目前尚不清楚它是否可以在任何地方使用 go,因为在三个提交中的一个或两个中存在一些非常棘手的文件不存在的极端情况,如果您允许这种事情,它们会变得更加棘手。

2 There is, in the existing merge-recursive algorithm, a bunch of redundant work in both the high and low level code. 2在现有的合并递归算法中,无论是高层代码还是底层代码都存在大量冗余工作。 The ongoing work to put in a new improved merge is eliminating a lot of this and will speed up a lot of the more difficult merges.正在进行的改进合并的工作正在消除很多这种情况,并将加速许多更困难的合并。 This doesn't change the goal of the merge code, nor the high level description I'm giving here, but does shuffle the point at which some bits of work are done and results saved, or not saved, so that they can be done once instead of repeatedly.这不会改变合并代码的目标,也不会改变我在这里给出的高级描述,但会改变完成某些工作并保存或不保存结果的点,以便它们可以完成一次而不是重复。

3 A low level union merge , which Git doesn't support directly—but which you can get with git merge-file , used as a low-level merge driver that you write—assumes that line order is irrelevant, and can handle this without calling it a conflict. 3低级联合合并,Git 不直接支持——但你可以使用git merge-file获得,用作你编写的低级合并驱动程序——假设行顺序无关紧要,并且可以在没有称之为冲突。


The upshot of all of this这一切的结果

The description of what merge does with Git's index is pretty long, but if you've followed the logic all the way through, you will see that:合并对 Git 索引的作用的描述很长,但如果你一直遵循逻辑,你会看到:

  • Any file that couldn't have a conflict is now at stage zero.任何不能有冲突的文件现在都处于零阶段。
  • Any file that could have had a conflict, but the driver (from .gitattributes ) or default built-in low-level file merger was able to resolve on its own—perhaps using -X ours or -X theirs —is also at stage zero.任何可能有冲突的文件,但驱动程序(来自.gitattributes )或默认的内置低级文件合并能够自行解决——可能使用-X ours-X theirs ——也处于阶段零.
  • Hence, only files that had unresolvable low-level conflicts, or had some high-level / tree-level conflict (which I'm omitting here for space reasons), have nonzero index stage entries.因此,只有具有无法解决的低级冲突或具有某些高级/树级冲突(出于空间原因我在此省略)的文件具有非零索引阶段条目。

So merge conflicts remain if and only if there are any nonzero stage numbers in Git's index.因此,当且仅当 Git 的索引中有任何非零阶段编号时,合并冲突仍然存在。 In this case, git merge stops, leaving behind a bunch of internal files—such as .git/MERGE_HEAD and .git/MERGE_MSG —to record that there's an ongoing merge.在这种情况下, git merge停止,留下一堆内部文件——例如.git/MERGE_HEAD.git/MERGE_MSG记录正在进行的合并。 Meanwhile the index itself has some nonzero slot numbers, which record that there is a conflict.同时索引本身有一些非零的槽号,记录有冲突。

If the conflict was a low-level conflict, and we used Git's built in low-level merge driver on some file, the working tree copy of that file has conflict markers in it.如果冲突是低级冲突,并且我们在某个文件上使用了 Git 内置的低级合并驱动程序,则该文件的工作树副本中有冲突标记。 These markers are derived from running the three original input files through the same code that git merge-file has available (so you could reconstruct the merge conflicts that way, but there's an easier way with git checkout -m or git restore -m at this point).这些标记来自于通过git merge-file可用的相同代码运行三个原始输入文件(因此您可以通过这种方式重建合并冲突,但使用git checkout -mgit restore -m F933E有更简单的方法观点)。 Regardless of what's in the working tree copy of the file, the three input files exist in the index.无论文件的工作树副本中有什么,三个输入文件都存在于索引中。

If we now run git mergetool , this code rummages through the index (using git ls-files --stage or equivalent) to find the conflicted files.如果我们现在运行git mergetool ,此代码会翻阅索引(使用git ls-files --stage或等效文件)以查找冲突文件。 It then uses git checkout-index to extract the three files that were the inputs to the low-level merge driver.然后,它使用git checkout-index提取作为低级合并驱动程序输入的三个文件。 These get funky .gittemporary style names, which git mergetool renames to file _BASE , file _LOCAL , and file _REMOTE respectively (well, the exact naming pattern is tricky and this is just an approximation).这些获得时髦.gittemporary样式名称, git mergetool分别重命名为file _BASEfile _LOCALfile _REMOTE (嗯,确切的命名模式很棘手,这只是一个近似值)。 For internal purposes, it copies the file to file _BACKUP .出于内部目的,它将file复制到file _BACKUP Then it runs your selected merge tool on these files (excluding the backup one).然后它在这些文件(不包括备份文件)上运行您选择的合并工具。

Your merge tool now works with working tree files.您的合并工具现在适用于工作树文件。 None of these files are in Git.这些文件都不在 Git 中。 You do whatever you like to them, using your merge tool.您可以使用合并工具对他们做任何您喜欢的事情。 Whatever is in file , git mergetool assumes that's the result that you produced through use of the merge tool.无论file中的内容是什么, git mergetool合并工具都假定这是您通过使用合并工具产生的结果。

Here, there's one more special trick:在这里,还有一个特别的技巧:

  • Some merge tools have "trusted" exit codes and some don't.一些合并工具具有“受信任的”退出代码,而有些则没有。

  • If your merge tool is marked "trusted" and exits with a status that says the merge is done, use the result , Git will git add that.如果您的合并工具被标记为“受信任”并退出并显示合并完成的状态,请使用结果,Git 将git add This erases the three slots and marks the file resolved.这将擦除三个插槽并标记文件已解析。

  • If your merge took is not trusted, Git will compare the _BACKUP file with the tool's output.如果您的合并不可信,Git会将_BACKUP文件与工具的 output 进行比较。 If the file is unchanged, git mergetool asks you if you think the merge worked.如果文件未更改, git mergetool合并工具会询问您是否认为合并有效。 Only if you say yes does it git add the result.只有当您说是时,它git add结果。

When git merge stops in the middle, your job is to clean up the mess, by writing into Git's index, at slot zero, the correct merge result .git merge在中间停止时,您的工作是清理混乱,通过将正确的合并结果写入 Git 的索引,在插槽 0 处。 You can do this any way you like.你可以用任何你喜欢的方式来做这件事。 My preferred method is generally just to open file in vim , after Git writes it with merge.conflictStyle set to diff3 .我的首选方法通常只是在 Git 写入文件后 vim 中打开filevim merge.conflictStyle设置为diff3 I find most conflicts easy to resolve this way.我发现大多数冲突都可以通过这种方式轻松解决。 In a few cases, I really do want to get the three versions, and for those cases, git mergetool is a way to do it—but having played with git mergetool , I haven't found it a particularly good way to do it.在某些情况下,我确实想要获得这三个版本,对于这些情况, git mergetool一种方法——但是在玩过git mergetool之后,我还没有发现它是一个特别好的方法。 This is one of those user-preference deals, though.不过,这是用户偏好交易之一。

Anyway, once you have all the conflicts resolved, and have run git add to update Git's index, you should run:无论如何,一旦你解决了所有的冲突,并运行git add to update Git's index,你应该运行:

git merge --continue

to tell Git to finish the merge.告诉 Git 完成合并。 Git does not care how you resolved the conflicts. Git 不在乎您如何解决冲突。 Git just cares that you put the right file into the index at staging slot zero, clearing out the other three staging slots. Git 只关心您将正确的文件放入暂存槽零处的索引中,清除其他三个暂存槽。

In the bad old days you had to run:在过去的糟糕日子里,您必须运行:

git commit

to finish the merge, and if you'd gotten confused (eg, got interrupted, had cd 'ed to some other repository, then had a meeting or something, and were now somewhere other than what you were thinking when you ran git commit ) you could make an ordinary commit instead of finishing your merge.完成合并,并且如果您感到困惑(例如,被打断,已经cd 'ed 到其他存储库,然后开会或其他什么,并且现在与您运行git commit时的想法不同)您可以进行普通提交而不是完成合并。 The --continue checks that there is in fact a merge to finish, then runs git commit to finish it. --continue检查实际上是否有要完成的合并,然后运行git commit来完成它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM