我有两个独立的 git 存储库;一个用于保存我的数据分析项目的代码,另一个用于保存运行代码的输出。所以,它看起来像这样:
.
|-- project_output
| |-- .git
| |-- output_sample1
| |-- output_sample2
| `-- output_sample3
`-- project_code
|-- .git
|-- code
| `-- all_my_scripts.sh
`-- output -> ../project_output
输出数据由非常大的基于文本的文件组成,我将其保存在project_output
中。该项目本身在GitHub上是开源的,并在project_code
上进行了跟踪。我使用git
来跟踪两者的变化。
当我想向project_code
添加新功能,或者调试或更改旧功能时,我会创建一个分支:
project_code$ git checkout -b fix-some-bug
project_code$ # make some changes, run the new code
project_code$ # save output in output -> ../project_output
现在,我可以在project_output
中查看对输出的更改:
project_output$ git status
project_output$ git diff
如果我想保留新的输出,我会提交:
project_output$ git add -u; git add .
project_output$ git commit -m "Update results from project_code/fix-some-bug branch"
但是,在这样的project_output
中跟踪不同project_code
分支的输出变得乏味且困难。我认为如果有一个系统可以在project_output
中镜像project_code
中创建和更改分支,那会容易得多。例如:
project_code$ git checkout -b fix-some-bug # project_output/fix-some-bug is created
project_code$ git checkout master # project_output switches to master branch as well
project_code$ git merge fix-some-bug # project_output merges fix-some-bug to master as well
我想这听起来很像我想要拥有单个存储库的好处,同时保持防止敏感数据接触我的公共存储库的安全性。
总而言之,您希望有一个仅包含代码的公共存储库,以及另一个从公共存储库获取代码但随后添加数据的私有存储库。 事实证明,这真的很容易做到(尽管如果您不小心,也很容易意外发布您的私人数据)。
Git 非常像《星际迷航》博格集体:它喜欢采用另一个存储库的技术独特性——即新提交——并将其添加到自己的存储库中。 事实上,这正是git fetch
所做的。
要使用git fetch
,你告诉你的 Git 调用其他一些 Git,通常是通过互联网电话的某个 URL。 然后,您的 Git 会获得其所有引用的列表 - 主要是分支和标签名称,但也包括其他内容。 (更准确地说,你的 Git 会得到他们的 Git 愿意向你展示的任何内容,但默认情况下,他们会向你展示一切。 这些引用名称指向特定的提交。1然后,您的 Git 会要求您提供您尚未拥有的任何提交,以及完成它们所需的任何其他对象。
由于git fetch
的方向是"从他们到我们",因此所有转移都是这样工作的。 (最接近git fetch
相反的是git push
,我们指示我们的 Git 调用另一个 Git 并向他们发送我们的技术独特性。显然,您不希望从私有存储库执行此操作。 一旦我们的 Git 拥有所有对象,它就可以停在那里,或者设置名称来记住对象。
如果我们告诉我们的 Git 保存名字,我们得到的名字是我们的名字,而不是他们的名字。 但是,当我们使用分支名称作为起点来复制提交以查找提交时,我们通常会让 Git 通过远程跟踪分支名称将它们保存在我们自己的存储库中。 例如,如果他们的master
提交了我们没有的deadbee
,我们将他们的deadbee
复制到我们的存储库中,然后让我们的origin/master
记住这个哈希 IDdeadbee
。2(如果deadbee
的父提交是ac0ffee
,我们也让我们的 Git 接受他们的ac0ffee
,除非我们已经有了它,依此类推。
如果我们让我们的 Git 选取名为标签的提交(和/或脚注 1 中的标签对象),我们会让 Git 将这些标签名称存储在我们的标签名称中,而不是"远程标签"中,所以如果他们添加了一个名为v2.3
的标签,我们为自己设置了一个新标签,也名为v2.3
。 默认情况下,花哨的重命名仅适用于分支。 但这在你自己的控制之下:它是你的存储库,所以你控制一切。
在任何情况下,您都可以指示您的 Git 根本不设置自己的名称。 如果你这样做,你依赖于git fetch
从昏暗时间中所做的一些事情,那就是它总是保存它在.git/FETCH_HEAD
中获得的每一个名字。 正常的git fetch
会覆盖以前的FETCH_HEAD
,因此您必须从该文件中提取提交 ID,并在再次运行git fetch
之前执行一些操作来记住它们。
但是,与此同时,无论您是否为他们的提交设置了自己的名称,您都有他们的所有提交(好吧,您指示git fetch
复制的所有提交)。 你的 Git 像 Borg 一样,将他们的技术独特性添加到你自己的身上。
因此,您所要做的就是将公共存储库设置为私有存储库中的命名远程存储库,然后运行git fetch
:
~/repos/private$ git remote add public https://github.com/...
或:
~/repos/private$ git remote add public file://~me/repos/public
或任何您喜欢的网址。 之后,运行:
~/repos/private$ git fetch public
会让你的 Git 调用另一个 Git(也许在你自己的机器上!3) 使用保存的 URL,并将在"他们的"(您的其他)仓库中找到的任何新的唯一提交下载到您的私有仓库中。 它将命名"他们的"分支public/master
等等,即将他们的分支从X重命名为public/X
,因为我们与git add
一起使用的名称,以创建这个"远程",是public
。
请注意不要将您的私有提交推送到您的公共仓库。 Git 和 Borg 一样,非常乐意添加新的东西,但会为了删除东西而与你战斗至死。 好吧,也许不是死亡,确切地说。:-)但是一旦数据像这样逃逸,任何人都可以克隆它,即使你设法迅速将其从公共存储库中清除,它也可能已被复制并广泛分发。
1标记名称可以指向四种对象类型中的任何一种。 通常,它们会指向带注释的标记对象,然后标记对象指向提交,但有时标记名称无论如何都只是直接指向提交。 分支名称只能指向提交。
2这就是fetch
和push
的不同之处:当我们从master
发送提交时,我们通常会要求他们设置master
。 他们没有为我们准备的"推送跟踪分支"。 但是,如果我们使用拉取请求,我们会以更迂回的方式执行此操作"请设置您的主人",方法是将我们的主节点发送到他们可以识别为"请看这个,然后决定是否喜欢它"的名称,而不是更自动的"请自动接受这个,只要它容易适合
"。换句话说,拉取请求相当于远程跟踪分支:一个"安全的地方",用于存储您尚不完全信任的内容,以便您可以在合并这些新对象之前查看并决定它们。 因为他们的名字往往很糟糕——拉取请求通常是有编号的,而且"PR#1234"和"我希望你把它合并到功能/加利福尼亚熊"之间没有明显的联系,有些人的做法不同。 他们推送到自己的公共仓库,然后通过电子邮件宣布:">我的功能/甲虫中有新的东西给你,你为什么不从我的公共仓库git fetch
。 这具有完全相同的目的:您可以通过某个名称、某个 URL 提供提交cafedad
。 然后由其他人使用相同的 URL 或不同的 URL 来检索您的提交,要么是因为他们已经在某个奇怪名称(如pull/1234/head
)下的 URL 上,要么是位于名为feature/beetle
的 URL。
3当将文件从您自己机器上的一个仓库复制到您自己机器上的另一个仓库时(即使用路径 URL 或file://
URL),您的 Git 最终可能会在获取和推送对话中同时扮演"您的 Git"和"他们的 Git"的角色。 然而,从高层次的角度来看,效果与有两个单独的 Git 相同,在两个独立的存储库中工作,通过相对狭窄的通道交换数据,就像通过互联网连接一样。