二进制文件如何在 git 上工作
How do binary files work on git
我正在使用 git 管理这个 LaTeX 项目,其中我有几个分支,我使用 master
作为一个分支,我在其中获得所有更改(在项目它将是最终版本)。有时,当我在一个分支下编译我的项目时,获取 pdf,然后当我将该分支与 master
合并时,我遇到合并冲突(在 master
的 pdf 版本和 branch
之间的 pdf 版本)。有时,两个版本会无缝合并。我在做什么导致了一种和另一种情况?如何确保两个版本合并而不冲突?
通常认为任何可以从源代码构建的东西 不 处于修订控制之下的良好做法。也就是说,它应该列在 .gitigore
文件中。
这有几个原因;
- 它生成大量额外数据(可以轻松复制)存储在存储库中。
- 正如您所发现的,您可能会在二进制文件上遇到合并冲突。二进制文件通常不能以有意义的方式合并。您可以,但是选择其中一个来替换另一个。请参阅
ours
或 theirs
合并策略。
- 如果源代码也被合并,您之后无论如何都必须创建一个新的二进制文件。否则二进制与源不一致。
对于 LaTeX 存储库,我的 .gitignore
至少包含:
*.aux
*.bbl
*.blg
*.fdb_latexmk
*.fls
*.idx
*.ilg
*.ind
*.lof
*.log
*.lot
*.out
*.toc
(我正在使用 latexmk
构建 LaTeX 文档。)
与一样,二进制文件根本不会合并。但是,关于 git merge
有一点您应该了解:它并不总是合并文件。事实上,它 从未 真正合并 文件 ,除非作为副作用。它有时(不总是)合并 提交 。当它这样做时,其中一些有时需要它来合并文件。
正如其他人到目前为止在评论中所说的那样,"compiled" 文件(处理您 想要使用版本管理的文件的程序的输出-control system——这些的现代术语似乎是 build artifact,虽然 artifact has a more general definition) 通常不应该在 Git.[=80= 中提交]
什么 git 合并 <em>b运行ch</em>
当你运行git merge
时,你:
- 正坐在一些提交上,通常是 b运行ch 的尖端(通过
git checkout <em>b运行ch-name</em>
):这个提交是由 HEAD
命名的(尝试 git rev-parse HEAD
查看哈希 ID,git symbolic-ref HEAD
查看 Git从 HEAD
); 找到你当前的 b运行ch name
- 提供另一个 b运行ch 的名称,或解析为另一个提交的任何其他标识符(尝试
git rev-parse <em>b运行ch-name</em>
看看这是如何工作的)。
合并命令然后运行是一个合并策略(默认情况下-s recursive
)。有一些特殊的策略可以做不同的事情,但默认策略通过 commit graph 获取你的两个提交哈希和 grubs,也称为 DAG对于有向无环图,找到合并基。您可以使用 git log --graph
或 git log --all --decorate --oneline --graph
查看此图,其中 "A DOG" 是一个有用的助记符,以记住 All Decorate Oneline Graph 选项。粗略地说,合并基础是 "where the two lines in the graph, starting from your HEAD and other commits, first come together again."
我们可以自己画这个图,在 Whosebug 上看起来更好看(实际上有很多方法可以画):
C--D--E <-- branch1
/
...--B
\
F--G--H <-- branch2
其中每个大写字母代表一次提交。在这里,两个 b运行ches 的两个 tips 是提交 E
和 H
,它们的合并基础是提交 B
.
To merge(作为动词)提交 E
和 H
,Git 本质上是 运行s git diff B E
(查看自基本提交以来 branch1
发生了什么变化)然后是第二个 git diff B H
(查看 branch2
发生了什么变化)。如果这两行中 不同的 文件发生了变化,合并结果很简单:我们只取两行中变化的文件,以及基础 B
中所有未变化的文件], 然后把它们堆在一起。
如果 E
和 H
两者 都对 一个特定的 文件进行了更改,但是 然后 git merge
必须将这些更改合并(合并)到那个文件。如果文件是二进制文件,Git 将——至少在默认情况下——立即放弃并声明冲突。您的 PDF 文件就是这种情况:如果 both E
and H
与 B
, Git 将声明合并冲突并让您解决文件。
无论如何,一旦所有冲突都得到解决,git merge
通常会进行新的合并提交。这是a合并:合并作为名词。合并提交是有两个父项的提交,我们可以将其绘制为:
C--D--E
/ \
...--B I
\ /
F--G--H
请注意,这次我省略了 b运行ch 名称。新提交 I
是相同的(就提交的文件而言),无论我们移动到哪个 b运行ch name 指向它。但是,移动的 b运行ch 名称是我们 运行 git merge
时使用的名称。因此,如果我们在 branch1
,结果是:
C--D--E
/ \
...--B I <-- branch1
\ /
F--G--H <-- branch2
但如果我们在 branch2
,结果是:
C--D--E <-- branch1
/ \
...--B I <-- branch2
\ /
F--G--H
换句话说,新的提交是以通常的方式进行的:无论 b运行ch 我们现在,那个 b运行 ch name 已更改,因此它指向新的提交。新的提交本身——提交 I
,在我们的例子中——指向前一个提交,对于合并提交,also 也指向另一个提交。
作为一个微妙但重要的一点,新提交的 第一个 父级是当时的 HEAD
提交。因此,虽然 merge I
的 contents 不依赖于我们所在的 b运行ch,但 first parent 做。如果我们使用 git log --first-parent
,稍后,在查看提交历史时,我们将仅遵循 first 父级。由于那是我们所在的 b运行ch,这意味着我们将根据需要返回 E
或 H
。
当git merge
不合并时
上面的图故意只涵盖四种可能情况中的一种。
假设不是:
C <-- branch1
/
...--B
\
D <-- branch2
之类的,我们有:
C <-- branch1 (HEAD)
/
...--B <-- branch2
现在 merge base 提交 B
是 branch2
的 tip 提交。我们在 branch1
——这就是为什么它被标记为 (HEAD)
——但是 branch2
没有任何东西可以合并。在这种情况下,git merge
说 "already up to date" 什么都不做。
或者,假设我们有这个:
C <-- branch2
/
...--B <-- branch1 (HEAD)
在这种情况下,branch1
和 branch2
的合并基础再次提交 B
,但是 branch2
领先于 branch 1
。 Git 可以,默认情况下 将 ,跳过合并并执行它所谓的 快进 。它将更改名称 branch1
以便它直接指向提交 C
,并检查提交 C
,给出:
C <-- branch2, branch1 (HEAD)
/
...--B
当您与其他人共享 "upstream" 存储库(例如 GitHub 上的存储库)时,这种 "fast forward merge"(根本不是合并)经常发生工作并推动那里。如果你们中的一个人做了一些工作并推送,而另一个人没有做出新的提交并进行了获取和合并,Git 看到从上游获得的新提交是 "fast-forward-able" 并改为这样做进行真正的合并。
你可以用 git merge --no-ff
打败它。一些工作流程需要这样做。
还有最后一种可能的情况,但这种情况非常罕见:可能根本没有 合并基础。如果您合并两个单独的存储库,或使用 git checkout --orphan
启动一个新的独立提交子图,就会发生这种情况。这里我们可以把整个图画成:
A--B--...--G--H <-- branch1 (HEAD)
I--J--...--O--P <-- branch2
如果您要求 Git 合并提交 H
和 P
,结果取决于您的 Git 版本。 Git 的旧版本尝试使用 Git's semi-secret empty tree 作为基础树来合并这两个图,这可能会或可能不会工作,具体取决于 H
和 P
的内容。然而,自 Git 版本 2.9.0 以来,Git 已开始默认拒绝这些,需要 --allow-unrelated-histories
。 (如果您提供该标志,合并将像以前一样进行,使用空树作为基础。)
我正在使用 git 管理这个 LaTeX 项目,其中我有几个分支,我使用 master
作为一个分支,我在其中获得所有更改(在项目它将是最终版本)。有时,当我在一个分支下编译我的项目时,获取 pdf,然后当我将该分支与 master
合并时,我遇到合并冲突(在 master
的 pdf 版本和 branch
之间的 pdf 版本)。有时,两个版本会无缝合并。我在做什么导致了一种和另一种情况?如何确保两个版本合并而不冲突?
通常认为任何可以从源代码构建的东西 不 处于修订控制之下的良好做法。也就是说,它应该列在 .gitigore
文件中。
这有几个原因;
- 它生成大量额外数据(可以轻松复制)存储在存储库中。
- 正如您所发现的,您可能会在二进制文件上遇到合并冲突。二进制文件通常不能以有意义的方式合并。您可以,但是选择其中一个来替换另一个。请参阅
ours
或theirs
合并策略。 - 如果源代码也被合并,您之后无论如何都必须创建一个新的二进制文件。否则二进制与源不一致。
对于 LaTeX 存储库,我的 .gitignore
至少包含:
*.aux
*.bbl
*.blg
*.fdb_latexmk
*.fls
*.idx
*.ilg
*.ind
*.lof
*.log
*.lot
*.out
*.toc
(我正在使用 latexmk
构建 LaTeX 文档。)
与git merge
有一点您应该了解:它并不总是合并文件。事实上,它 从未 真正合并 文件 ,除非作为副作用。它有时(不总是)合并 提交 。当它这样做时,其中一些有时需要它来合并文件。
正如其他人到目前为止在评论中所说的那样,"compiled" 文件(处理您 想要使用版本管理的文件的程序的输出-control system——这些的现代术语似乎是 build artifact,虽然 artifact has a more general definition) 通常不应该在 Git.[=80= 中提交]
什么 git 合并 <em>b运行ch</em>
当你运行git merge
时,你:
- 正坐在一些提交上,通常是 b运行ch 的尖端(通过
git checkout <em>b运行ch-name</em>
):这个提交是由HEAD
命名的(尝试git rev-parse HEAD
查看哈希 ID,git symbolic-ref HEAD
查看 Git从HEAD
); 找到你当前的 b运行ch name
- 提供另一个 b运行ch 的名称,或解析为另一个提交的任何其他标识符(尝试
git rev-parse <em>b运行ch-name</em>
看看这是如何工作的)。
合并命令然后运行是一个合并策略(默认情况下-s recursive
)。有一些特殊的策略可以做不同的事情,但默认策略通过 commit graph 获取你的两个提交哈希和 grubs,也称为 DAG对于有向无环图,找到合并基。您可以使用 git log --graph
或 git log --all --decorate --oneline --graph
查看此图,其中 "A DOG" 是一个有用的助记符,以记住 All Decorate Oneline Graph 选项。粗略地说,合并基础是 "where the two lines in the graph, starting from your HEAD and other commits, first come together again."
我们可以自己画这个图,在 Whosebug 上看起来更好看(实际上有很多方法可以画):
C--D--E <-- branch1
/
...--B
\
F--G--H <-- branch2
其中每个大写字母代表一次提交。在这里,两个 b运行ches 的两个 tips 是提交 E
和 H
,它们的合并基础是提交 B
.
To merge(作为动词)提交 E
和 H
,Git 本质上是 运行s git diff B E
(查看自基本提交以来 branch1
发生了什么变化)然后是第二个 git diff B H
(查看 branch2
发生了什么变化)。如果这两行中 不同的 文件发生了变化,合并结果很简单:我们只取两行中变化的文件,以及基础 B
中所有未变化的文件], 然后把它们堆在一起。
如果 E
和 H
两者 都对 一个特定的 文件进行了更改,但是 然后 git merge
必须将这些更改合并(合并)到那个文件。如果文件是二进制文件,Git 将——至少在默认情况下——立即放弃并声明冲突。您的 PDF 文件就是这种情况:如果 both E
and H
与 B
, Git 将声明合并冲突并让您解决文件。
无论如何,一旦所有冲突都得到解决,git merge
通常会进行新的合并提交。这是a合并:合并作为名词。合并提交是有两个父项的提交,我们可以将其绘制为:
C--D--E
/ \
...--B I
\ /
F--G--H
请注意,这次我省略了 b运行ch 名称。新提交 I
是相同的(就提交的文件而言),无论我们移动到哪个 b运行ch name 指向它。但是,移动的 b运行ch 名称是我们 运行 git merge
时使用的名称。因此,如果我们在 branch1
,结果是:
C--D--E
/ \
...--B I <-- branch1
\ /
F--G--H <-- branch2
但如果我们在 branch2
,结果是:
C--D--E <-- branch1
/ \
...--B I <-- branch2
\ /
F--G--H
换句话说,新的提交是以通常的方式进行的:无论 b运行ch 我们现在,那个 b运行 ch name 已更改,因此它指向新的提交。新的提交本身——提交 I
,在我们的例子中——指向前一个提交,对于合并提交,also 也指向另一个提交。
作为一个微妙但重要的一点,新提交的 第一个 父级是当时的 HEAD
提交。因此,虽然 merge I
的 contents 不依赖于我们所在的 b运行ch,但 first parent 做。如果我们使用 git log --first-parent
,稍后,在查看提交历史时,我们将仅遵循 first 父级。由于那是我们所在的 b运行ch,这意味着我们将根据需要返回 E
或 H
。
当git merge
不合并时
上面的图故意只涵盖四种可能情况中的一种。
假设不是:
C <-- branch1
/
...--B
\
D <-- branch2
之类的,我们有:
C <-- branch1 (HEAD)
/
...--B <-- branch2
现在 merge base 提交 B
是 branch2
的 tip 提交。我们在 branch1
——这就是为什么它被标记为 (HEAD)
——但是 branch2
没有任何东西可以合并。在这种情况下,git merge
说 "already up to date" 什么都不做。
或者,假设我们有这个:
C <-- branch2
/
...--B <-- branch1 (HEAD)
在这种情况下,branch1
和 branch2
的合并基础再次提交 B
,但是 branch2
领先于 branch 1
。 Git 可以,默认情况下 将 ,跳过合并并执行它所谓的 快进 。它将更改名称 branch1
以便它直接指向提交 C
,并检查提交 C
,给出:
C <-- branch2, branch1 (HEAD)
/
...--B
当您与其他人共享 "upstream" 存储库(例如 GitHub 上的存储库)时,这种 "fast forward merge"(根本不是合并)经常发生工作并推动那里。如果你们中的一个人做了一些工作并推送,而另一个人没有做出新的提交并进行了获取和合并,Git 看到从上游获得的新提交是 "fast-forward-able" 并改为这样做进行真正的合并。
你可以用 git merge --no-ff
打败它。一些工作流程需要这样做。
还有最后一种可能的情况,但这种情况非常罕见:可能根本没有 合并基础。如果您合并两个单独的存储库,或使用 git checkout --orphan
启动一个新的独立提交子图,就会发生这种情况。这里我们可以把整个图画成:
A--B--...--G--H <-- branch1 (HEAD)
I--J--...--O--P <-- branch2
如果您要求 Git 合并提交 H
和 P
,结果取决于您的 Git 版本。 Git 的旧版本尝试使用 Git's semi-secret empty tree 作为基础树来合并这两个图,这可能会或可能不会工作,具体取决于 H
和 P
的内容。然而,自 Git 版本 2.9.0 以来,Git 已开始默认拒绝这些,需要 --allow-unrelated-histories
。 (如果您提供该标志,合并将像以前一样进行,使用空树作为基础。)