我如何 gitignore 不同回购协议的不同文件,包括拉取请求?

How can I gitignore different files for different repos including pull requests?

      remote
   ______|______
  |             |
localA       localB
-file1       -file3
-file2       -file4
-file3       -file5

我想让我的远程仓库存储文件 1-5。我希望 localA 回购只有 push/pull 个文件 1-3。我希望 localB 回购只有 push/pull 个文件 3-5。所以目标是让一些文件在本地仓库和一些不同步的文件之间同步,但远程仓库应该存储所有这些文件。我的 .gitignore 文件可以很好地提交,但最终会拉下所有我不想要的文件。我也尝试过使用 .git/info/exclude 文件,但也没有成功。如果需要,我可以从头开始,但我更愿意配置现有设置。

编辑上下文: 这是一个奇怪的测试环境,用户在其中使用个人帐户在一个区域进行更改,但必须在另一个区域的共享帐户上进行测试。我希望在他们的个人帐户下跟踪他们的更改,而不是共享帐户。我希望在共享帐户下跟踪测试结果,而不是他们的个人帐户。

你可能会得到你想要的,但首先你必须改变你想要的。

Git 不存储 文件; Git 存储 提交 。一次提交就像一个文件存档——事实上,每个提交都包含 每个 文件的完整存档——还有一些额外的 add-on 特性。推送和获取操作(pull 表示首先 运行 fetch,然后 运行 第二个 Git 命令 并且它是fetch 步骤,而不是第二个命令,与此处的 push 相当)通过 t运行sferring 整个 commits 工作。因此,假设您的每个用户都在为他们拥有的 每个 文件创建一个 read-only 存档,并且每次都发送这些文件——因为 他们在做什么。

名称.gitignore(或.git/info/exclude,其工作方式相同)非常具有误导性:它不会导致文件被忽略。 read-only 存档充满了它包含的任何文件,并且 none 这些文件可以 忽略 因为它们已经在 read-only 存档中。当您提取该存档(使用 git checkout)时,您将获得它的所有文件。这些文件现在位于您的工作区(您可以在其中查看和使用它们,甚至可以更新它们)和 Git 的存储区(Git 称为 暂存区) 或索引,准备好进行 下一次 提交(一个新的 read-only 存档)。除非你明确地删除其中一些文件,否则它们都将在下一个提交中。

B运行ches,在 Git 中,意思也比您想象的要少。 Git 与 b运行ches 无关,而是 commits。 B运行ch 名称只是查找 某些特定提交的一种方式。我们需要这个,因为单个提交的实际名称非常难看 random-looking 哈希 ID,用 hexadecimal 表示,没有人能记住或正确输入。 b运行ch 名称只是我们让计算机保存 latest 提交的哈希 ID 的方式。最新的提交保存了其前一个提交的哈希 ID,因此从最近的提交,Git 可以回顾一跳。前一次提交依次包含 前一次提交的哈希 ID,以便 Git 可以再回头一跳,依此类推。

Ok so let's say I have 2 branches, one for localA and one for localB.

同样,b运行ches 不适用于 files。 B运行ches 用于查找提交。每个提交都包含 每个 文件的完整快照(存档)——或者更准确地说,它包含的每个文件。你可以有一个提交——一个档案——只包含一个文件。但这不会很有帮助,至少对于普通的日常 Git 工具来说是这样。 (您可以编写自己的工具来处理这些问题,但这需要大量工作。)

Am I able to merge both branches in the remote repo without overwriting file3?

git merge操作是关于合并工作。考虑像这样的正常日常合并:

  • 我们采用三个(而不是两个)提交,即每个文件的三个 read-only 存档。

  • 这三个档案之一是 merge base: 提交是 shared 因此 两个b运行ches.

  • 剩下的两个提交是两个 b运行ches 中每一个的最新提交。其中之一是“我们的”b运行ch——我们在开始之前用 git checkoutgit switch 选出的那个。另一个是“他们的”b运行ch,我们将其命名为 git merge 命令。

  • 我们现在Git将合并库中的所有存储文件与所有进行比较将文件存储在我们的 b运行ch 提示提交中。无论 changes Git 在这里观察到什么——添加新文件、删除旧文件、更改现有文件——都是我们的 changes.

  • 接下来,我们Git将合并库中的所有存储文件与所有进行比较他们 b运行ch 提示提交中存储的文件。 改变 Git 这里观察到的是他们的改变。

  • 最后,我们 Git 将这两组更改结合起来:如果我们添加了一个 totally-new 文件,Git 将获取我们的文件;如果他们添加了一个,Git 会拿走他们的文件;如果我们修改了 fileA,Git 会保留对该文件的更改;如果他们还修改了 fileA,Git 也会添加该更改。 Git 然后从 merge base 提交中提取所有文件,并将 combined 更改应用于这些文件。

    如果合并更改时出现问题,Git 将在此过程中停止。请注意,如果我们只更改 fileA 而他们只更改 fileB,那么将这些更改组合起来永远不会有任何问题。当我们和他们接触一个文件中的相同行时,或者如果我们修改fileA,我们运行就会合并问题和他们例如,完全删除 fileA

    如果 Git 能够独自完成所有 change-combining,Git 将合并的更改应用于合并基础——这会保留我们更改的内容并添加他们更改的内容— 并创建一个新的 merge commit,这在一个方面是特殊的(我稍后会谈到这一点)。如果没有,Git 会在这个过程中停止,这就变成了你的工作——“你”是 运行 git merge 的人——通过提供 正确的合并结果。无论您在这里提供什么,Git 都会 相信 它是正确的,因此最好做到这一点。然后,您告诉 Git 您的合并解决方案,然后 Git 继续进行相同的合并提交,如果它能够自己进行合并的话,它会进行相同的提交。

因此,您的问题的答案是临时的“是”:

  • 假设组 X 和 Y 以相同的提交开始,因此它们都有相同的存档文件。在此存档中,存在名为 fileAfileBfile3 的文件。

  • Group X 创建了一个新的 b运行ch,修改了 fileA,并且——使用他们的 b运行ch(见下文)——制作了一个新的存档其中除 fileA 之外的所有文件都相同。

  • 分别(在他们的拥有新的b运行ch),Y组修改fileB并制作一个新的存档,其中除了 fileB 之外的所有文件都是相同的。

  • 你现在通过选择他们的一个提交作为新的“最佳组合结果”来推动 main-line b运行ch,然后你 运行 git mergeother commit / b运行ch 上获得新的“最佳组合结果”。由于这三个档案中的 file3 相同 ,因此无需对 file3 进行任何更改。 Git 从合并基础存档复制 file3 以放入新存档。

这是 临时 是的原因是 每个小组都必须注意保留而不是修改他们不应该更改的任何文件。如果他们弄乱了其中一个文件,您的合并将不会那么顺利。但是请注意,每个存档都是 read-only 并且永远存在 所以很容易注意到——嘿!——他们更改了 不应该拥有的文件! 并让他们将这些文件放回 new 存档中,然后添加到他们的 b运行ch 中。请参阅下面的示例。

一个例子

让我们从一个完全空的 Git 存储库开始,它有 no 提交和 no b运行 全部:

mkdir repo && cd repo && git init
[Git prints messages about initializing a new repository]

现在您创建初始的 working-tree 文件集:可能是空的 A、空的 B 和三个编号的文件。然后你 git add 所有这些:这告诉 Git 将它们复制到 Git 的 index 又名 暂存区 ,Git 从中进行新的提交。请注意,这些文件中的 none 将被“忽略”(同样,稍后会详细介绍)因为我们希望 every文件在每个提交中。

您现在在您的存储库中有一个提交。这一次提交是初始提交。一个正常的新提交会 link 回到上一个提交,但由于没有上一个提交,这个确实不能。它有一个丑陋的大哈希 ID:一个唯一的数字,在整个宇宙中的每个 Git 存储库中都是唯一的。1 而不是猜测它的数字,甚至缩写它像 a123456 之类的,让我们把这个提交称为 A:

A

现在假设您忘记了一些东西,或者发现了一些您没有想到的东西,需要输入 file3。没问题!您在工作树中调整 file3 的副本——这是一个普通的日常文件; archivescommits 中,你不直接处理它们——然后是 运行 git add file3git add 将更新的 file3 复制到 Git 的暂存区/索引中,准备提交。你现在又运行git commit了。

这会进行第二次提交——第二次每个文件的完整存档。因为 Git 很聪明,所以这个存档实际上与第一个存档共享存储空间。即使 file2 有 100 兆字节大,您的存储库也只会增长几个字节来保存对 file3 的更新。档案彼此独立,但又共享;这就是 Git 实现的神奇之处(它知道如何做到这一点 因为 每个存档都是完整的 read-only,因此很容易共享)。

这个新的提交,我们称之为 B 而不是试图猜测其唯一的哈希 ID,它向后指向旧的提交:

A <-B

Git 知道 B 最新的 主 b运行ch 提交——我们称之为 main;你可以给它取任何你喜欢的名字; Git 仍然默认为 master 尽管 GitHub 现在默认为 main——是 name main 指向提交B,像这样:

A <-B   <--main

如果您现在发现某个文件中有拼写错误,您可以通过再次提交来解决这个问题。我们将这个新提交称为 C。提交 C 将指向现有提交 B,并且 Git 会将 C 的哈希 ID 存储到 b运行ch 名称中:

A--B--C   <-- main

(注意:我在这里故意偷懒,不再将提交之间的箭头绘制为箭头,但它们仍然是 one-way 箭头。Git 工作 向后,从最近的提交(通过使用 b运行ch 名称找到)到较旧的提交。)

假设我们现在已准备好让您的测试人员进行测试。我将继续称它们为 X 组和 Y 组(以使单个字母与我的单个提交字母“远离”)。我们现在 两个新的 b运行ch names都指向提交 C,所以有 三个 提交的名称C 现在:

A--B--C   <-- main, groupX, groupY

1这个唯一性要求就是为什么哈希ID这么大这么丑。从技术上讲,ID 只需要在每个其他 Git 存储库中是唯一的,您的 Git 存储库将永远 交谈,但最好确保此 ID 是 完全独一无二。


测试人员开始测试

您的测试人员现在使用 git clone 复制充满提交的 整个存储库 。当他们这样做时,他们得到了 b运行ches 的 所有提交none。相反,他们的克隆有我们所说的 remote-tracking 名称。让我们从X组的角度来看一下。

A--B--C   <-- origin/main, origin/groupX, origin/groupY

因为他们想要添加 new 提交,所以他们立即需要一个自己的 b运行ch 名称。如果他们足够聪明,他们会在 git clone 时间告诉他们的 Git:请根据我的新 remote-tracking 名字为我取名 groupX origin/groupX。 (他们使用 git clone-b 选项来做到这一点。)这给了他们:

A--B--C   <-- groupX (HEAD), origin/main, origin/groupX, origin/groupY

如果他们不那么聪明,他们的 Git 可能会为他们创建错误的名称。假设他们从 GitHub:

获得默认值 main
A--B--C   <-- main (HEAD), origin/main, origin/groupX, origin/groupY

注意这里添加的 HEAD,在 parentheses 中:这表示它们的 当前 b运行ch 名称 。你也有一个,当你唯一的 b运行ch 名字是 main 时,我们就懒得画了。由于 main 是错误的名称,他们现在需要 Git 创建 他们的 groupX 名称,使用 git checkout groupX 或92=]。这使用他们的 origin/groupX 来创建他们的 groupX。他们的 groupX 将指向与他们的 origin/groupX 相同的提交,就像这样:

A--B--C   <-- groupX (HEAD), main, origin/main, origin/groupX, origin/groupY

请注意他们现在有两个 b运行ch 名称:maingroupX。特殊名称 HEAD 附加到 名称 groupXremote-tracking 名称都指向现有提交 C。两个 b运行ch 名称也指向现有提交 C。因此,提交 C最新 提交 both of their b运行ches,在这一点上。

所有提交都是 read-only. 它们实际上不能更改现有的三个提交中的任何一个。 他们所能做的就是添加新的提交——但这是他们应该做的。他们现在在他们的工作树中有从提交 C.

中的存档中提取的文件

他们可以 运行 他们的测试,并更新 fileAlocalA 或任何你给这个文件起的名字。此更新发生在他们的 工作树 中,其中包含 所有 从提交 C 中提取的文件。然后他们 运行 git add 在他们的一个更新文件上。 (他们可以 运行 在所有文件上使用 git add -Agit add .:Git 会注意到其他文件未更改,实际上不会 更改 这里有任何东西。)这准备 他们的 index/staging-area 通过更新 fileAlocalA 的暂存副本来进行新的提交或者不管你怎么称呼它。 (其他暂存副本保持不变,即使 git add 写入它们,因为它们没有 更改 这些文件。)

他们现在 运行 git commit。这使得 每个文件 的新存档作为新的提交,具有新的唯一哈希 ID。我们称它为D,并将其画在:

        D   <-- groupX (HEAD)
       /
A--B--C   <-- main, origin/main, origin/groupX, origin/groupY

请注意 他们的 名称 groupX 是如何更新为指向 他们的 新提交 D。他们的新提交指向现有提交 C.

同时,Y组也开始测试

Y 组执行相同的一系列操作,除了 name 他们将用来跟踪 他们的 提交( s) 是 groupY:

A--B--C   <-- groupY (HEAD), origin/main, origin/groupX, origin/groupY

(我这次省略了 main,假设他们记得 git clone-b 选项。)最终他们以

A--B--C   <-- origin/main, origin/groupX, origin/groupY
       \
        E   <-- groupY (HEAD)

合并工作

请注意,此时有 三个存储库,每个存储库都有一些具有相同哈希 ID 的 共享 提交,并且每个都有一些独特的提交,具有 unique-to-it 哈希 ID。现在是 X 组和 Y 组使用 git push.

的时候了

这里有很多选项,但假设您让它们 git push 直接对一些共享的、可写的存储库进行操作。 X 组将发送他们的新提交 D 并要求共享可写存储库设置 its b运行ch name groupX 指向提交 D。 Y 组将对他们的提交 E 执行相同的操作,但要求共享可写存储库设置 its b运行ch 名称 groupY。并且,让我们暂时假设您可以直接访问这个共享的可写存储库,以便您现在可以登录并查看它。它现在有这个:

        D   <-- groupX
       /
A--B--C   <-- main (HEAD)
       \
        E   <-- groupY

现在你的工作是合并工作。你:

  • 移动你的 HEAD / main 指向 DE (哪个并不重要),通过使用 git merge,执行 so-called 快进 合并:

             D   <-- main (HEAD), groupX
            /
     A--B--C
            \
             E   <-- groupY
    
  • 运行 git merge groupY 执行合并;和

  • Git 自动合并,因为每个人都正确地完成了他们的工作,所以你得到:

             D   <-- groupX
            / \
     A--B--C   F   <-- main (HEAD)
            \ /
             E   <-- groupY
    

注意没有提交是如何改变的:我们添加了新的提交来存储新的档案。 b运行ch names 随着我们添加新的提交而向前推进。 Git 继续向后工作,从像 groupX 这样的 b运行ch 名称开始并向后工作。

这里比较特别的是,从名字main,我们发现commit F是一个merge commit,有两种方式倒退。这两种方式分别导致提交 DE。这意味着如果您的测试人员需要更新他们的文件,您所做的 next 合并将有不同的 merge base 提交。

现在由您的测试人员决定他们是否 (a) 选择新的提交 F 和 (b) 更新 their b运行ch合并提交的名称 F。这不是 必需的 只要他们做的一切都是正确的,但如果他们破坏了某些东西,它会让你的事情变得更容易。

当事情出错时

这种模式会一遍又一遍地重复:有人会更新 他们的 b运行ch,然后发送 他们的向您提交新的提交,您将选择是否合并这些提交。如果他们破坏了某些东西,您可以简单地拒绝合并该提交。例如,假设两个组都更新为 F,然后组 X 进行了错误提交 G:

        D   G   <-- groupX
       / \ /
A--B--C   F   <-- main (HEAD), groupY
       \ /
        E

你可以不带 G。您可以要求他们下一次提交 H parent F放弃 他们的 G,或者他们修复他们的 G 的提交 H 已更正 archive/snapshot。两种方法都可以。这是 abandoned-commit 的样子:

              G   [abandoned]
             /
        D   /__--H   <-- groupX
       / \ /
A--B--C   F   <-- main (HEAD), groupY
       \ /
        E

因为不再有 b运行ch name 找到提交 G,它从视图中消失了。最终,它完全脱离了存储库(un-find-able 提交在一段时间后有资格进行“垃圾收集”)。

好的,那么,.gitignore / .git/info/exclude 到底是什么?

阅读完所有内容后,您应该会问自己上述问题。答案是 工作树 往往会充满 永远不应存档 的“垃圾”或临时文件或输出文件。这有两个烦人的副作用:

  1. git status 告诉我们工作树中的内容 可以 放入下一个存档,但目前没有安排。这些是 Git 所谓的 未跟踪文件 git status 列出一千个未跟踪的文件,但 不应 添加这些文件,这很烦人。

  2. git add .或其他en-masse添加操作方便,但会添加未跟踪的文件。

如果我们可以告诉 Git 会怎么样:如果这个文件未被跟踪,(1) 不要抱怨它,(2) 不要 auto-add 它使用 en-masse add-all-changes 操作?这就是 .gitignore 的用途:抑制投诉并避免自动添加这些 never-to-be-archived 文件。

如果一个文件在 Git 的索引中,也就是暂存区——如果它来自某个存档的 out,它将在 out 中列出该文件 .gitignoreexclude 文件无效。现在 已跟踪 。只有在索引/staging-area和在你的工作树中的文件,untracked ,只有这些文件可以被“忽略d"这样。忽略是错误的术语,但正确的术语太长而不能用作文件名。