如何让git在不同分支之间共享同一文件?

时间:2019-10-25 10:25:55

标签: git

我有两个分支,一个是master,另一个是dev。有一个名为config的文件,希望在分支之间共享。也就是说,如果我在master中进行了更改,然后切换到dev,则所做的更改将保留下来,并且可以在dev中看到更改后的文件。此外,我希望可以将其推送到远程存储库,因此无法将其添加到.gitignore文件中。

我该怎么做?

1 个答案:

答案 0 :(得分:0)

除非被迫这样做,否则您不能这样做。

在您了解Git根本不是关于分支之前,上面的答案没有任何意义。 Git就是关于 commits

每个提交都有自己的笨拙哈希ID,例如08da6496b61341ec45eac36afcc8f94242763468。此ID表示 this 提交,没有其他提交。试试:

git rev-parse HEAD

在您自己的Git存储库中,以查看当前提交的哈希ID。现在,该哈希ID意味着那个提交,永远更多,并且再也没有其他提交。 1

但是提交哈希ID大而丑陋,不会有人会记住它们。我们(人类)需要一种能够为我们记住它们的设备。也许我们可以使用我们的计算机!他们擅长记住这样的长字符串。因此,我们要做的就是让Git记住这些 我们的哈希ID。

Git将在几个不同的地方为我们记住哈希ID。例如, tag 名称包含一个提交哈希ID。 2 但实际上, branch 名称,例如master只保留一个提交哈希ID。

幸运的是,每个 commit 都包含哈希ID!尽管我们(人类)也倾向于将分支名称(有时在Git存储库中使用某些其他名称)视为“ Git”,但这正是Git形成我们通常认为的分支的方式。分支机构”。有关更多信息,请参见What exactly do we mean by "branch"?

每个提交都存储我们所有文件的完整快照。提交不存储更改!它完整​​地存储了整个文件。 3 因此,如果提交A(其中A代表一些哈希ID)存储了文件foo/bar/file.txt的内容,则提交A拥有文件的完整副本,没有差异,只有副本。如果其他一些提交B存储了文件foo/bar/file.txt的内容,则提交B也具有完整的副本。

如果完全匹配,则会自动共享这些完整副本和完整副本 每次提交后,所有提交都是完全只读的,并且始终冻结。< sup> 4 还包括保存的文件内容。因此,如果提交A与提交B一样存储相同内容(甚至仅针对一个文件),则该文件的基础文件存储也将共享。但是您不能更改该文件的副本。如果确实要更改文件,则必须进行新的提交C,该提交将获得其自己的新的唯一哈希ID。这个新的提交C可以有一个文件foo/bar/file.txt,但是,由于我们说过您更改了该文件的内容(更改为与其他任何现有提交都不匹配的文件),因此该文件的内容是新的,可以提交{{1 }}。只要C继续具有相同的内容,就可以与以后的提交DEF再次共享它。


1 从技术上讲,哈希ID只是提交对象全部内容的加密校验和。从理论上讲,某些不同的对象可能具有相同的校验和。但是,如果您已经有一个带有哈希 H 的对象,则您的Git将不允许任何具有相同哈希的新的但不同的对象进入您的存储库。因此,一旦“获取”对象ID,就表示那个对象。

另请参阅How does the newly found SHA-1 collision affect Git?

2 一个轻量标签直接保存哈希ID,而一个带注释的标签通过一个带注释的标签对象 ,它还保存其他数据。该标签对象获得其自己的唯一哈希ID,标签 name 然后保存标签对象的ID。 Git将使用该名称查找标签对象,然后读取标签对象以查找存储的哈希ID。

标签可以将Git的四种内部对象类型的任何作为其标签目标。如果一个带注释的标签指向另一个带注释的标签,则Git会一直跟随该标签,直到到达其他对象类型为止,因此最终,每个标签都指向某个非带注释的标签对象,但可能会经过许多间接操作。如果最终对象是树或blob对象,则标记毕竟不会指向提交。

3 在提交级别,Git存储一个 tree 对象。然后,树对象引用 blob 对象以及更多 tree 对象。树对象包含文件名成分和对Blob哈希ID的引用,而Blob对象则包含文件的内容。

这些对象最初存储为松散对象,它们始终完整且完整无缺。后来,Git最终将对象压缩为 pack文件。当存储在打包文件中时,可以将对象delta compressed与其他打包对象相对。因此,在某些时候,“文件”(实际上是对象)必须通过增量编码来存储,这与存储差异相同。但这会在之后发生,并且在Git处理文件的级别上是不可见的。请注意,树和提交对象本身也已经或者至少可以被增量压缩!

4 这是必需的,也是自然的,因为Git存储对象的方式。每个对象都有其内容(不管是什么内容),然后对由脚注1中提到的相同校验和过程计算出的哈希ID进行哈希处理。如果将对象从数据库中取出,则只需修改一点,然后将其写回<在数据库中,新对象具有不同的新校验和。如果新数据已经在数据库中,则新对象毕竟不是新对象:Git只是重新使用现有对象。否则,Git将该对象写入数据库,然后获取哈希ID。

有一些用于释放(“收集垃圾”)未使用对象的过程,如果它们出现的话(实际上在Git中相对常见-许多命令会创建临时对象,然后将其丢弃)。您无需做任何事情就可以发生这种情况:Git本身会在适当的时候为您运行foo/bar/file.txt。但是散列ID的位数足以使实践中永远不会发生意外冲突;只有故意的,强制的碰撞才能发生。同样,请参见How does the newly found SHA-1 collision affect Git?


但是以上内容都是关于提交的!那分支呢?

我们刚刚说过,如果提交git gc --autoA具有与B相同的文件内容,它们实际上会 share 文件的基础副本。用Git的术语来说,它们共享 blob 对象。但是commit foo/bar/file.txt引入了C的新版本,因此commit foo/bar/file.txt有一个新的blob对象。提交CDE,然后将该内容重新用于 F文件,因为您已提交{{1} },foo/bar/file.txtD,而无需更改文件。

现在,关于提交的一件有趣的事情是,除了存储文件(文件的 all 的快照)外,每个提交还存储了一些元数据。在元数据中,您将找到自己的名称和电子邮件地址,分别存储为“作者”和“提交者”。您将找到两个日期和时间戳,指示您何时进行提交。 5 Git存储您的日志消息:主题和正文E的文本,以提醒您自己和其他人为什么。而且,也许最重要的是,您会找到 parent 提交或某些父提交的哈希ID。通常只有一个父母。

请记住,该父对象是提交的原始哈希ID -丑陋的字母和数字字符串。这就是Git记住我们的哈希ID 的原因,因此我们不必这样做。

只要Git中的某个东西持有一个丑陋的哈希ID,我们就说该东西指向。因此,如果提交包含其前任(父)提交的哈希ID,则该comimt指向其父级。这意味着提交F指向提交git log,提交B指向提交A,依此类推:

C

每个提交中都嵌入了这些向后的提交箭头,形成了将提交链接在一起的链。由于任何提交都无法更改 ,因此我们可以像这样绘制链:

B

但是分支名称从何而来?答案是:就在这里。该链中的 last 提交是提交A <-B <-C ... <-F (无论其真实,大而丑陋的哈希ID到底是什么,我们在这里仅将其称为A--B--C--D--E--F )。为了记住 this 哈希ID,Git为我们提供了一个分支名称。假设这是分支F名称 F将保存提交master的实际哈希ID:

master

现在让我们创建一个新的分支FA--B--C--D--E--F <-- master 保留哪个哈希ID?好吧,让它也保持dev!现在我们有了:

dev

我们选择这两个分支之一为“ on”:

F

,然后我们做一些工作并进行新的提交,该提交将获得自己的新的,唯一的,大而丑陋的哈希ID。我们将其称为哈希ID A--B--C--D--E--F <-- master, dev 。提交git checkout dev 将指向现有的提交G,因为这是我们为了进行 make G而检出的提交,我们得到:

F

分支名称指向何处?好吧,G仍然指向提交A--B--C--D--E--F \ G ,但是由于我们只是做出新的提交master,因此Git 会自动更新分支< em> name 指向新的提交F,给出:

G

我们在与G一起使用的分支名称旁边绘制A--B--C--D--E--F <-- master \ G <-- dev (HEAD) 一词,以便我们和Git知道哪个分支名称在我们进行更新时得到更新新提交。

如果我们在(HEAD)上进行更多新的提交,它们将指向在git checkout上的现有提交。现有提交dev指向提交dev。提交G在哪个分支上? Git的答案很不寻常:提交F同时在两个分支上

但是,如果我们F拥有:

F

然后重新提交git checkout master?好吧,就像上次一样。新提交A--B--C--D--E--F <-- master (HEAD) \ G <-- dev parent 是我们刚才检查的提交,即提交H,我们的 name 指向到新提交:

H

提交F H <-- master (HEAD) / A--B--C--D--E--F \ G <-- dev 两个分支上。提交A仅在F上,提交H仅在master上。


5 这两个时间戳的原因是您可以复制一个提交,同时(大概)可以更改它。新的提交有一个新的 committer 行:您现在进行了新的提交。但是它保留了原来的 author 行:谁写了原始提交,无论何时写,都会在 new 提交中捕获。因此,每个提交都有一个 author:编写原始提交的人,并带有姓名,电子邮件地址和时间戳。每个提交也都有一个 committer :进行该特定提交的人,并带有姓名,电子邮件地址和时间戳。如果两者匹配,则这是原始提交的原始副本。


现在我们可以看到为什么您的原始问题在Git中大多被回答为“否”

我们从快照Gdev中存在的名为config的文件开始:

G

具有相同的内容。因此,该文件 被共享(仅存储一次),并且H H <-- master (HEAD) / A--B--C--D--E--F \ G <-- dev 都使用一个共享的blob对象来存储它。但是,然后您修改文件HG的工作树副本,以进行新的提交git add

git commit

提交I具有 H--I <-- master (HEAD) / A--B--C--D--E--F \ G <-- dev 的新副本:新的blob。您希望分支I自动 提交:

config

其中提交dev与提交 H--I <-- master (HEAD) / A--B--C--D--E--F \ G--J <-- dev 相同,只是文件J已更新。

您可以通过执行G然后在config上进行新提交git checkout dev来手动完成 ,该提交的快照中具有相同的更新J的副本。但是Git不能自动做到这一点。