我正在使用 git 管理这个 LaTeX 项目,其中我有几个分支,我将其用作master获取所有更改的分支(在项目结束时它将是最终版本)。有时,当我在分支下编译项目时,获取 pdf,然后当我将该分支与 合并时,master会出现合并冲突(在masterpdf 版本和branchpdf 版本之间)。有时,两个版本会无缝合并。我做了什么导致了一种又另一种情况?如何确保两个版本合并而不发生冲突?
人们普遍认为良好的做法是,任何可以从源代码构建的东西都不会受到修订控制。也就是说,它应该列在一个.gitigore文件中。
有几个原因;
ours或theirs合并策略。对于 LaTeX 存储库,我的.gitignore至少包含:
*.aux
*.bbl
*.blg
*.fdb_latexmk
*.fls
*.idx
*.ilg
*.ind
*.lof
*.log
*.lot
*.out
*.toc
Run Code Online (Sandbox Code Playgroud)
(我用于latexmk构建 LaTeX 文档。)
正如crashmstr 在评论中所说,二进制文件根本不会合并。但是,您应该了解一些事情git merge:它并不总是合并文件。事实上,它不会真正合并文件,除非有副作用。它有时(并非总是)合并提交。当它这样做时,其中一些有时需要它来合并文件。
正如其他人到目前为止在评论中所说的那样,“编译”文件(处理您确实想要使用版本控制系统管理的文件的程序的输出\ xe2\x80\x94这些文件的现代术语似乎是构建工件(尽管工件有更一般的定义)通常不应该在 Git 中提交。
\n\ngit merge branch当你跑步时git merge,你:
git checkout branch-nameHEADgit rev-parse HEADgit symbolic-ref HEADHEADgit rev-parse branch-name然后,合并命令运行合并策略(-s recursive默认情况下为 )。有一些特殊的策略可以执行不同的操作,但默认策略会通过提交图(也称为有向无环图的DAG )获取两个提交哈希和 grub,以找到合并基础。您可以使用git log --graph或查看此图表git log --all --decorate --oneline --graph,其中“A DOG”是一个有用的助记符,可以记住“所有装饰单线图表”选项。粗略地说,合并基础是“图中的两条线,从你的 HEAD 和其他提交开始,首先再次聚集在一起。”
我们可以自己用 StackOverflow 上看起来更好的方式来绘制这个图(实际上有很多绘制方法):
\n\n C--D--E <-- branch1\n /\n...--B\n \\\n F--G--H <-- branch2\nRun Code Online (Sandbox Code Playgroud)\n\n其中每个大写字母代表一次提交。这里,两个分支的两个提示E是 commits和H,它们的合并基础是 commit B。
为了合并(作为动词)提交E和H,Git 本质上运行git diff B E(以查看branch1自基本提交以来发生了什么变化),然后运行第二个git diff B H(以查看 中发生了什么变化branch2)。如果这两行中的不同文件发生了更改,则合并结果很简单:我们只需取出两行中更改的文件以及 base 中所有未更改的文件B,然后将它们堆在一起。
但是,如果E两者H 都对一个特定文件进行了更改,则必须合并(合并) 对该文件git merge的这些更改。如果文件是二进制的,Git 至少默认情况下会\xe2\x80\x94\xe2\x80\x94 立即放弃并声明冲突。您的 PDF 文件就是这种情况:如果 和、 vs两者都不同,Git 将声明合并冲突并让您解决该文件。 E HB
无论如何,一旦解决了所有冲突,git merge通常会进行新的合并提交。这是合并:作为名词合并。合并提交是具有两个父级的提交,我们可以将其绘制为:
C--D--E\n / \\\n...--B I\n \\ /\n F--G--H\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,这次我省略了分支名称。新的提交I是相同的(就提交的文件而言),无论我们移动到哪个分支名称来指向它。不过,移动的分支名称是我们运行时所在的分支名称git merge。因此,如果我们在 上branch1,结果是:
C--D--E\n / \\\n...--B I <-- branch1\n \\ /\n F--G--H <-- branch2\nRun Code Online (Sandbox Code Playgroud)\n\n但如果我们在branch2,结果是:
C--D--E <-- branch1\n / \\\n...--B I <-- branch2\n \\ /\n F--G--H\nRun Code Online (Sandbox Code Playgroud)\n\n换句话说,新的提交以通常的方式进行:无论我们现在在哪个分支,该分支名称都会更改,以便它指向新的提交。新提交本身\xe2\x80\x94commit I,在我们的例子中\xe2\x80\x94指向上一个提交,对于合并提交,也指向另一个提交。
作为一个微妙但重要的一点,新提交的第一个父级是HEAD当时的提交。因此,虽然合并的内容I不取决于我们所在的分支,但第一个父级却取决于。如果我们稍后使用, ,我们在查看提交历史记录时git log --first-parent将仅遵循第一个父级。因为那是我们所在的分支,这意味着我们将返回到E或H根据需要
git merge不合并上图故意只涵盖四种可能情况中的一种。
\n\n假设代替:
\n\n C <-- branch1\n /\n...--B\n \\\n D <-- branch2\nRun Code Online (Sandbox Code Playgroud)\n\n或类似的,我们有:
\n\n C <-- branch1 (HEAD)\n /\n...--B <-- branch2\nRun Code Online (Sandbox Code Playgroud)\n\n现在合并基础提交B 是的尖端提交branch2。我们位于branch1\xe2\x80\x94,这就是为什么它被标记为(HEAD)\xe2\x80\x94,但没有任何内容可以branch2合并。在这种情况下,git merge显示“已经是最新的”并且不执行任何操作。
或者,假设我们有这个:
\n\n C <-- branch2\n /\n...--B <-- branch1 (HEAD)\nRun Code Online (Sandbox Code Playgroud)\n\nbranch1在这种情况下,和 的合并基础同样branch2是 commit B,但branch2位于之前 branch 1。Git 可以(默认情况下)跳过合并并执行所谓的快进操作。它将更改名称,branch1以便它直接指向 commit C,并检查 commit C,给出:
C <-- branch2, branch1 (HEAD)\n /\n...--B\nRun Code Online (Sandbox Code Playgroud)\n\n当您与也在其中工作和推送的其他人共享“上游”存储库(例如 GitHub 上的存储库)时,这种“快进合并”(根本不是合并)经常发生。如果你们中的一个人做了一些工作并进行了推送,而另一个人没有进行任何新的提交并进行了获取和合并,Git 会发现从上游获得的新提交是“可快进的”,并且会执行此操作而不是执行此操作真正的合并。
\n\n你可以用 来击败它git merge --no-ff。有些工作流程需要这样做。
还有最后一种可能的情况,但这种情况非常罕见:可能根本没有合并基础。如果您组合两个单独的存储库,或者用于git checkout --orphan启动新的独立提交子图,就会发生这种情况。这里我们可以将整个图绘制为:
A--B--...--G--H <-- branch1 (HEAD)\n\nI--J--...--O--P <-- branch2\nRun Code Online (Sandbox Code Playgroud)\n\n如果您要求 Git 合并提交H和P,结果取决于您的 Git 版本。旧版本的 Git 尝试使用Git 的半秘密空树H作为基础树来合并这两个图,这可能会也可能不会起作用,具体取决于和的内容P。然而,从 Git 版本 2.9.0 开始,Git 开始默认拒绝这些,要求--allow-unrelated-histories. (如果您提供该标志,则合并会像以前一样继续,使用空树作为基础。)