我有两个分支,一个是master
,另一个是dev
。有一个名为config
的文件,希望在分支之间共享。也就是说,如果我在master
中进行了更改,然后切换到dev
,则所做的更改将保留下来,并且可以在dev
中看到更改后的文件。此外,我希望可以将其推送到远程存储库,因此无法将其添加到.gitignore
文件中。
我该怎么做?
答案 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
继续具有相同的内容,就可以与以后的提交D
,E
和F
再次共享它。
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 --auto
和A
具有与B
相同的文件内容,它们实际上会 share 文件的基础副本。用Git的术语来说,它们共享 blob 对象。但是commit foo/bar/file.txt
引入了C
的新版本,因此commit foo/bar/file.txt
有一个新的blob对象。提交C
,D
和E
,然后将该内容重新用于其 F
文件,因为您已提交{{1} },foo/bar/file.txt
和D
,而无需更改文件。
现在,关于提交的一件有趣的事情是,除了存储文件(文件的 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
现在让我们创建一个新的分支F
。 A--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 :进行该特定提交的人,并带有姓名,电子邮件地址和时间戳。如果两者匹配,则这是原始提交的原始副本。
我们从快照G
和dev
中存在的名为config
的文件开始:
G
具有相同的内容。因此,该文件 被共享(仅存储一次),并且H
和 H <-- master (HEAD)
/
A--B--C--D--E--F
\
G <-- dev
都使用一个共享的blob对象来存储它。但是,然后您修改文件H
和G
的工作树副本,以进行新的提交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不能自动做到这一点。