我可以将一个本地 git 存储库与两个远程存储库一起使用吗?

5pa*_*iff 3 git github

刚接触 git,想确保我明白我在做什么。

目前,我有一个本地存储库推送到公共远程存储库。我想将某些文件和目录从公共远程存储库中分离出来并添加到私有远程存储库中。

我的计划是将这些文件和目录添加到.gitignore,创建一个名为 的新分支privateRepo,然后将其链接到私有远程存储库。然后切换到分支,privateRepo添加/提交/推送本地内容。

git checkout -b privateRepo
git remote add privateRepo <url>
git switch privateRepo
git add <files & dir>
git commit -m "message"
git push
Run Code Online (Sandbox Code Playgroud)

之后,我可以使用git switch main返回主分支并推送而不影响privateRepo.

它是否正确?

如果我想从privateRepo本地存储库中提取数据,这会带来任何问题吗?

tor*_*rek 5

可以按照你的建议去做。我建议反对,因为:

\n
    \n
  • 它不像我想象的那样工作;和
  • \n
  • 很容易意外地将错误的提交发送到错误的“其他 Git 存储库”,从而永远发布您的私人文件。
  • \n
\n

为了理解这一点,我们需要了解存储库是什么以及它为您做什么的基础知识。存储库主要是两个数据库的集合:

\n
    \n
  • 一个数据库保存提交和其他支持的 Git 对象。这就是“对象数据库”。它是一个简单的键值存储,其中键是哈希 ID \xe2\x80\x94commit 哈希 ID,以及其他支持对象哈希 ID\xe2\x80\x94,值是对象(提交和提交使用的内容)永久存储文件)。

    \n
  • \n
  • 另一个数据库保存名称。对象数据库\xe2\x80\x94中的键和哈希ID\xe2\x80\x94对于人类来说太大且难看。所以我们不这样做:我们使用名字。Git 保留第二个数据库,以便它可以从名称转换为哈希 ID,以查找提交(和其他内部对象)。例如,人类可以处理名称,例如master分支名称或分支名称。main

    \n
  • \n
\n

当您使用多个 Git 存储库并使用git fetch或交叉连接它们时git push(请注意,这git pull是一个方便的命令,意味着run git fetch,然后运行第二个 Git 命令,所以它实际上是git fetch伪装的),您正在做的是在存储库之间传输提交。这里你有几个选择:要么发送整个提交,要么不发送。您要么收到整个提交,要么什么都没有。1 如果您发送或接收提交,您还将发送或接收您或他们没有的所有前驱(祖先)提交。2

\n

这样做的结果是,如果您不小心将一个提交发送到错误的(即公共)存储库,您可能会发送所有这些提交。现在他们拥有您提交的每个私人文件的每个版本。您可以尝试将其收回\xe2\x80\x94GitHub,这有点困难,因为您必须联系 GitHub 支持\xe2\x80\x94,但在这些提交可见的窗口期间,任何人和每个人都可以从公共存储库复制它们。

\n

相反,如果您将文件拆分为“公共可用性文件”\xe2\x80\x94,将其放入一个存储库中,然后与 GitHub 共享该存储库作为公共存储库,\xe2\x80\x94 和“私有文件”进入一个不同的存储库,然后将该存储库仅作为私有存储库与 GitHub 共享,整个事情更易于管理。

\n

您可以使用 Git 的子模块协调这两个存储库评论中提到的 \xc3\x94rel。子模块有自己令人头疼的问题和缺点,以至于人们有时称它们为sob模块,但它们确实实现了适当的公共/私有分割。

\n
\n

1 Git 正在开发一种称为“部分克隆”的新工具,其中这种通用的“全有或全无”原则被小心地拆开,就像叠叠乐塔一样。但拉错了部分,整个东西就会崩溃。这不是 \xe2\x80\x94 至少目前 \xe2\x80\x94 意味着你正在谈论的那种事情,我不建议使用它,除非你打算在 Git 本身上添加它。(理论上它可以用于此类目的。)

\n

2浅克隆也可能会小心地违反这条规则。浅层克隆比部分克隆代码更成熟,但它们仍然不能做您想要的事情。

\n
\n

关于存储库的更多说明

\n

许多人认为 Git 是关于文件的。事实并非如此:这实际上与提交有关。然而,提交确实保存了文件。或者他们会认为 Git 是关于分支的。这不是:这是关于提交。然而,分支名称确实可以帮助您(和 Git)找到提交。因此分支名称和文件很重要。但这实际上都是关于提交的。

\n

因为提交存储在对象数据库中,所以它们是用那些又大又难看的哈希 ID进行编号的。每个唯一的对象都有一个唯一的编号。特别是,提交编号在整个宇宙中的每个 Git 存储库中都必须是唯一的,这意味着数字必须很大(目前有 2 160 个可能的数字,并且在不久的将来可能会变成 2 256,因为事实证明是 2 160太小)。这就是为什么它们如此大、丑陋且看起来随机,尽管实际上它们完全是非随机的:它们是加密哈希函数(当前为 SHA-1;SHA-256 是计划的未来)的输出。

\n

不过,每次提交都会存储件事:

\n
    \n
  • 每次提交都会存储每个文件的完整快照,以您(或任何人)进行提交时文件的形式为准。它们以特殊的、只读的、仅限 Git 的、压缩和去重复的格式存储(作为数据库中的对象),而不是普通的计算机文件。

    \n
  • \n
  • 每个提交都会存储一些元数据,或者有关提交本身的信息:例如,是谁、何时以及为什么(日志消息)。该元数据与对象数据库中的所有内容一样是只读的。(只读质量来自 Git 的哈希技巧,并且对于允许文件重复删除也是必要的。)

    \n
  • \n
\n

在任何给定提交的元数据中,Git 存储先前提交列表的原始哈希 ID。通常这个列表只有一个元素长。我们将此单个先前提交称为该提交的提交。这些父 ID 形成向后查找的链:

\n
... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n

这里H代表链中最后一次提交的哈希 ID。CommitH内部包含所有文件的完整快照以及一些元数据。中的元数据H显示您(或任何人)进行了提交。他们保存您的日志消息等数据。而且,他们表明提交H有一个父级,无论哈希 IDG代表什么。

\n

CommitG当然也有快照和元数据。使用H,Git 可以查找G、提取两个快照并进行比较。相同的部分并没有改变(Git 很容易看到这一点,因为通过哈希进行了重复数据删除)。不管有什么不同,这里 Git 都需要运行git diff来找出发生了什么变化。如果您要求,Git 就会执行此操作,并且当您查看 commit 时,您将看到从到的变化GHH

\n

查看提交后H,像现在这样的命令git log会向后移动一跳以提交G。提交G具有元数据,包括早期提交的哈希 ID F。CommitF有一个快照,因此 Git 可以比较F-vs-G以查看发生了什么变化,从而向您显示G更改,即使G 快照。然后git log可以向后移动一跳到F,然后重复。仅当 Git 返回到第一次提交(其中 \xe2\x80\x94 是第一个提交 \xe2\x80\x94没有父级)时,或者当您厌倦了读取git log输出并使其退出时,该过程才会结束。

\n

使用提交:工作树和索引/暂存区

\n

但这里有一个大问题。如果快照中的内容(永久保存的文件)是只读的\xe2\x80\x94,更糟糕的是,它的格式只有 Git 本身才能读取\xe2\x80\x94我们用过它吗?

\n

这个问题的答案很简单。在非裸存储库(也就是说,大多数)中,Git 添加一个工作区域,称为工作树工作树。要使用提交,Git 只需将提交中的所有文件复制到您的工作树中。现在您可以查看您的文件并完成您的工作。

\n

重要的是要认识到这些文件不在 Git 中。这些是普通的计算机文件,所有普通计算机程序都可以读取和写入并且通常可以使用它们。它们可能来自Git。但此时,它们已经不在Git中了。它们只是文件。

\n

当您与他们一起工作时,他们可能会偏离 Git中的内容。它们的内容发生变化。在某些时候,您可能希望获取所有更新的文件并将它们永久存储在新的提交中。在其他非 Git 版本控制系统中,这非常简单:例如,您运行,hg commitMercurial 会找出您更改的内容并进行新的提交。在 Git 中,这并不那么容易。

\n

相反,Git 为每个文件添加了一个隐藏的额外“副本”。我在这里用引号说“复制”,因为每个文件的这个额外“副本”是预先 Git 化的:它以 Git 内部使用的压缩、去重复格式存储。由于所有这些文件最初都来自某个提交,因此它们都是重复的,因此它们都不占用任何空间。3

\n

当你告诉 Git现在进行新的提交时,Git 只查看这些隐藏的额外副本。因此,在进行提交之前,必须运行git add. 所做git add的是:

\n
    \n
  • 通读某个文件的工作树副本;
  • \n
  • 压缩并通常进行 Git 化,得出内部对象哈希 ID;
  • \n
  • 如果这是某个现有文件的重复项,请扔掉现在建立的临时内容并使用重复项;
  • \n
  • 否则,为将来的提交做好准备并存储它。
  • \n
\n

无论哪种方式,该git add步骤都会获取更新的文件并使其准备好进入下一次提交。这将替换那里的副本,准备进入下一次提交。或者,如果该文件是全新的\xe2\x80\x94,如果之前没有该名称的文件\xe2\x80\x94,则git add无需替换,而是添加一个新文件,现在有一个副本,准备用于进入下一个提交。

\n

在所有情况下,在之前 git add,Git 都已准备好进入新提交的所有文件。 之后 git add,Git 已准备好进入新提交的所有文件。所以 Git总是准备好所有文件。所做git add的是将这些准备好的“副本”中的一个、两个或多个替换为更新的文件(如果需要,则使用新副本,或者如果可能的话,重新使用旧的“副本”)并添加任何新文件。

\n

这个额外的准备就绪但尚未提交的东西所在的区域在 Git 中有三个名称。这可能是因为主名称index没有意义。另一个主要名称,暂存区,指的是如何使用索引。第三个名称,缓存,大部分已失效,但仍然显示在诸如 之类的标志中git rm --cached。我倾向于使用名称“索引”,但“暂存区域”可能已成为最常见的名称,这绝对是您使用它的方式:通过将文件排列在“暂存区域”上/中来“暂存”文件,准备好被“拍照”到他们将永远生活的承诺中。

\n

该暂存区域或索引位于当前提交和工作树之间。Git 的索引很像提交,最初是提交建立的,后来成为新的提交,但实际提交和 Git 索引之间的关键区别在于,您可以替换文件,添加文件到 Git 的索引,以及从Git 的索引中删除文件。您不能对提交执行此操作:提交一旦完成,就一成不变。

\n

当您最终运行时,Git 会立即git commit打包索引中的文件。这些成为新提交的快照。所以你必须更新索引。人们经常受到诱惑,但这样做有一些缺陷,我建议用户避免它(见下文)。git commit -a

\n

无论如何,我发现将索引视为位于提交和工作树之间是有帮助的,如下所示:

\n
  HEAD         index      work-tree\n---------    ---------    ---------\nREADME.md    README.md    README.md\nmain.py      main.py      main.py\n                          new.txt\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,我们检查了一些包含两个文件的提交,因此HEAD\xe2\x80\x94 当前提交\xe2\x80\x94 有这两个文件,Git 的索引有这两个文件,我们的工作树有这两个文件两个文件。与 HEAD 和索引副本不同,我们实际上可以看到并使用工作树副本,但是这三个副本都存在。

\n

然后我们在工作树中创建了一个新文件, 。new.txt它不存在于 中HEAD,也不存在于 Git 的索引中。现在我们来看一个有趣的特殊案例。

\n
\n

3它们占用一些空间来存放名称、哈希 ID 和 Git 内部使用的一堆缓存数据。所需的空间量取决于名称长度和索引格式(有多种索引格式编号),但通常很小,每个文件大约 100 字节。

\n
\n

已跟踪、未跟踪和忽略的文件

\n

我们在工作树中创建的新文件不在 Git 中。我们也看不到其他两个文件,但这两个文件确实在Git 的提交中拥有副本并且在索引中准备了一个建议的新提交。但不在索引中:它甚至不在建议的下一次提交中。HEADnew.txt

\n

此时,new.txt就是 Git 所说的未跟踪文件。未跟踪的文件被定义为当前不在 Git 索引中的任何文件

\n

例如,假设我们现在运行:

\n
git rm --cached main.py\n
Run Code Online (Sandbox Code Playgroud)\n

产生这个:

\n
  HEAD         index      work-tree\n---------    ---------    ---------\nREADME.md    README.md    README.md\nmain.py                   main.py\n                          new.txt\n
Run Code Online (Sandbox Code Playgroud)\n

我们没有更改提交(我们无法更改其内容),但 nowmain.py不在Git 的索引中。我们的工作树中的main.py已从跟踪变为未跟踪

\n

如果我们git add main.py new.txt现在运行,Git 将:

\n
    \n
  • 读取 的内容,压缩它们,发现它是重复的并在索引中main.py重新使用旧的;main.py
  • \n
  • 读取 的内容new.txt,压缩它们,可能会发现它是新的,并制作一个新的 Git 化副本准备提交,并将其放入索引中;
  • \n
\n

现在我们将拥有:

\n
  HEAD         index      work-tree\n---------    ---------    ---------\nREADME.md    README.md    README.md\nmain.py      main.py      main.py\n             new.txt      new.txt\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们没有未跟踪的文件。

\n

文件的跟踪性是可变的。 我们所要做的就是创建或删除索引条目和/或工作树文件。两者中的文件都是跟踪文件;那些仅存在于工作树中的文件是未跟踪的文件。这就是\xe2\x80\x94 的全部内容

\n

为什么.gitignore并不意味着人们认为的意思

\n

Git 索引中的文件永远不会被忽略

\n

中的一个条目.gitignore列出了文件名,部分地告诉 Git,如果该文件未被跟踪,请不要抱怨它。 该命令非常有用,因为它会告诉我们索引中有哪些内容以及索引中没有哪些内容,例如git status我们忘记的内容。git add这包括工作树中但不在索引中的任何文件。

\n

但是有些东西\xe2\x80\x94比如Python 2.x\xe2\x80\x94会生成很多永远不应该提交的工作树文件。如果git status一直抱怨你的所有*.pyc文件,git status可能会变得无法使用:“哦,我忘记了这一件事”的有用金块git add将被埋在无用​​的“这里有 5000 个*.pyc你可以添加的文件”消息中。

\n

为了防止这种情况,我们在文件中列出了*.pyc glob 模式.gitignore。这使得人们git status对这些文件闭嘴。

\n

它还有另一种效果。我们可以运行git add .git add --all或类似的命令来集体“添加所有内容”。当我们使用它时,Git 将跳过也在 中列出的任何现有的未跟踪文件.gitignore。但 Git 只跳过未跟踪的文件,而不跳过跟踪的文件。跟踪的文件\xe2\x80\x94(Git 索引\xe2\x80\x94 中的文件)已更新。(这实际上通常是人们想要的。)

\n

所以,.gitignore是错误的名字。该文件的名称应类似于.git-do-not-complain-about-these-files-when-they-are-untracked-and-also-if-they-are-untracked-and-I-use-an-en-masse-git-add-command-do-not-add-them-to-the-index-after-all. 但这个名字很可笑,几个稍微短一点的版本也是如此。所以就叫它.gitignore

\n

这里的关键是它不会忽略索引中的文件。对于要提交的文件,它必须位于Git 的索引中。如果它在提交中并且我们签出了该提交,则该文件现在位于 Git\ 的索引中。因此,一旦文件被提交,它往往会溜回索引,即使我们时不时地将其取出:每当我们检查包含该文件的提交时它都会回到索引中。然后它的上市.gitignore就没有任何影响。

\n

为什么(以及何时)避免git commit -a

\n

所做git commit -a的就是使用快捷方式。它:

\n
    \n
  • git add 为你奔跑,然后
  • \n
  • 执行该git commit步骤。
  • \n
\n

您还可以执行以下操作:

\n
git commit --only main.py\n
Run Code Online (Sandbox Code Playgroud)\n

和:

\n
git commit --include main.py\n
Run Code Online (Sandbox Code Playgroud)\n

这些都操作索引,然后进行提交。然而,它们非常棘手:它们让 Git在提交期间创建一两个额外的临时索引文件。这些额外的索引文件可能会扰乱预提交挂钩。他们是否、何时以及多少程度地搞乱这些钩子取决于几个因素,包括钩子编写者编写钩子的仔细程度,他们是否意识到这个 Git 技巧,以及您--only是否--include使用git commit. 4

\n

也就是说,git commit -a通常效果很好(见脚注 4)。但大致相当于git add -u先运行,再运行git commit。该-u选项git add将仅更新已知文件,而不添加新文件。当我们用来git add .集体添加我们更新的所有内容时,这包括添加新文件。-a无法添加新文件git commit 选项。

\n

除此之外,git commit -a就是纯粹的懒惰。有时懒惰是一种美德,所以这并不意味着永远不要使用它。但是git status,后面跟着仔细的git add操作\xe2\x80\x94,甚至可能是操作\xe2\x80\x94 ,后面跟着git add -p另一个操作(或者\xe2\x80\x94,这些做完全相同的事情),将帮助您安排仅提交您的更改想。通过阅读差异,您可以在另一个窗口中撰写良好的提交消息,或者至少做笔记,然后可以将其粘贴到提交中。这可以让你做出良好、谨慎的承诺。为此,您通常必须避免. 所以这至少是一个坏习惯,即使懒惰有时是一种美德。git statusgit diff --cachedgit diff --stagedgit commit -a

\n
\n

4.内部git commit -a使用模式。--include这比--only表单的破坏性更小,因为--include只需要两个索引文件,而不是三个,并且提交期间使用的索引文件将在提交成功后成为新索引。额外的文件仅用于回滚情况。该--only表单需要三个:一个用于提交,一个用于回滚,一个用于成功;所有三个都有\xe2\x80\x94至少可能\xe2\x80\x94不同的内容。

\n