我如何 commit/push 我的构建文件夹到另一个 git 存储库而不是主存储库?

How do I commit/push my build folder into another git repository and not into the main repository?

所以,我最近制作了一个 React 应用程序,我已经 post 在 GitHub 上编辑了它。但是,我想 post 输出(运行 npm run build 之后的 build 文件夹)到 Glitch 应用程序。由于所有 Glitch 应用程序都有一个 git 存储库,我认为这是执行此操作的最佳方式。这是我想要的结构:

我见过有人使用子模块,但我不知道如何让我的主 git 存储库忽略 build 文件夹并让子模块只推送 build文件夹。

我也对一般如何设置子模块感到困惑,因此 example/explanation 也将不胜感激。

~阿尤什

在根目录下使用 .gitignore 文件,将你不想推送的文件添加到 GH

  1. 要忽略特定的 folder/files,是的,使用 .git忽略文件
  2. 将特定文件夹/“子”git 存储库推送到不同的存储库是通过在该特定文件夹上初始化新 git,通过 运行“git init”和“git 远程添加”。

示例:

git init
git add somefile
git commit -m "initial commit"
git remote add origin https://github.com/username/new_repo
git push -u origin master 

我不完全确定你想要这里的子模块,但是子模块可以让你做你正在描述的事情。不过,子模块很棘手。人们称它们为 sob-modules 是有原因的。

首先,如果您明确定义(参与者和动作)将会有很大帮助:

  • A repository 不推送任何内容。它只是提交的集合(加上一些名称;请参阅下面的最后一点)。

  • Git(软件套件)创建和操作存储库,包括其中的提交。

  • git push 命令推送 提交

  • A commit 是个小东西(从技术上讲,commit object,但人们使用术语非常松散,因此这里使用松散的“thingy”术语)具有以下特征:

    • 它有一个唯一的哈希 ID。
    • 它存储个文件。请注意,提交不存储 文件夹 ,仅存储 文件 。这些文件的路径名包含嵌入式斜杠(始终为正斜杠,即使您在 Windows 系统上使用反斜杠提取提交文件也是如此)。这最终在后面变得很重要,但是如果你愿意,你可以把它们想象成folders-full-of-files,只要你记住Git can't store an empty folder properly(因为它只存储文件 ).这些文件存储 作为 完整快照,尽管它们被压缩并且——重要的是——de-duplicated 跨存储库中的所有提交。因此,通常一些新提交的 re-uses 30,000 个文件来自以前的提交这一事实并不重要:re-used 文件没有 space,因为它们实际上是 re-used .
    • 它存储元数据,或有关提交本身的信息。这包括诸如谁在何时提交,以及日志消息等内容;而且,对于 Git 自己的操作至关重要,它还包括一些早期提交集的原始哈希 ID。大多数提交仅存储一个 earlier-commit 哈希 ID,我们(和 Git)将其称为 parent。在 Git 存储库中,历史就是这样工作的:每个提交 都会记住它的 parent.
    • 完全read-only。任何提交的任何部分都不能更改。 (这就是允许 de-duplication 和许多其他 Git 魔法的原因。)
  • 存储库还包含允许 Git 到 find 提交的名称,例如 b运行ch 和标签名称。这是通过让一个名称准确存储 one 哈希 ID 来实现的。对于 b运行ch 名称,根据定义,存储的哈希 ID 是 b运行ch 中的 最后一次提交。由于提交存储 parent 哈希 ID,Git 可以从我们决定调用“last in b运行ch X”的任何提交向后工作:X~1 是second-to-last在XX~2就是third-to-last,以此类推。

向 b运行ch 添加新提交的行为包括以下步骤:

  1. 检查提交(使用git checkoutgit switch)通过检查b运行ch(使用相同的命令),因此这是现在的 current b运行ch。此操作会填充 Git 的索引——它包含你的 提议的下一次提交 ——和你的 工作树 ,其中 Git 将所有文件复制成可用的形式。内部 de-duplicated 形式通常不能用于除 Git 本身之外的所有内容。

  2. 你在你的工作树中做了一些事情。 Git 对这部分的控制或影响为零,很多时候,因为您将使用自己的编辑器或编译器或其他任何东西。您可以在此处使用 Git 命令,然后 Git 将能够看到您做了什么,但大多数情况下,Git 不必关心,因为我们继续执行第 3 步:

  3. 你运行git add。这指示 Git 查看更新的工作树文件。 Git 会将这些更新后的文件以更新后的形式 re-compressing 和 de-duplicating 并通常使它们 为下一次提交做好准备 .

  4. 你运行git commit。这会打包新的元数据——您的姓名、当前日期和时间、日志消息等——并添加 当前提交的哈希 ID 以构成新提交的元数据。因此,新提交的 parent 将成为当前提交。 Git 然后快照 此时索引中的所有内容 (这就是为什么 git checkout 在第 1 步中填写它,然后 git add 更新它在步骤 3) 中,连同元数据一起进行新的提交。这为新提交提供了新的哈希 ID,它实际上只是此处整个数据集的加密校验和。

    正是在这一点上,奇迹发生了:git commit 将新提交的哈希 ID 写入当前 b运行ch 名称。所以现在,b运行ch 上的最后一次提交是您的新提交。 这就是 b运行ch 增长的方式, 一次提交一个。没有现有提交更改——none可以更改——但新提交指向是[=435] =] 最后一次提交,现在是 second-to-last 提交。 b运行ch name moves.

你真的需要让所有这些都非常冷才能让子模块工作,因为子模块实际上使用所有这些东西,但随后违反了一些规则。现在开始变得棘手了。我们还需要更仔细地观察 git push,只是片刻。

git push: cross-connecting 一个 Git 存储库与另一个

进行新的 Git 提交,在某些 Git 存储库中,只会进行新的 snapshot-plus-metadata。下一个技巧是将该提交放入某个 other Git 存储库。

如果我们从两个 otherwise-identical Git 存储库开始,每个存储库都有一些提交集和一些 b运行ch 名称标识相同的 last 提交:

... <-F <-G <-H   <--branch-name   [in Repo A]

在 Repo B 中也是如此。但是,在 Repo A 中,我们做:

git checkout branch-name
<do stuff>
git commit

这导致 repo A 包含:

...--F--G--H--I   <-- branch-name

(我很懒惰,懒得在这里正确绘制 commit-to-commit 箭头)。新提交 II,如 HGF,代表一些丑陋的大 random-looking 哈希 ID — 指向现有提交 H。您甚至可以做出不止一次的新提交:

...--F--G--H--I--J   <-- branch-name

现在您 运行 git push origin branch-name,将您的存储库中的新提交发送回“原始”存储库(我们之前称之为“存储库 B”,但我们称它为origin 现在)。

您的 Git 软件套件(“您的 Git”)调用他们的。您的 Git 列出了您最新提交的哈希 ID,即提交 J。他们的 Git 检查他们的存储库,看看他们是否有 J,通过哈希 ID。他们没有(因为你刚刚做到了)。所以他们的Git告诉你的Git:好的,给我!你的Git现在有义务提供J的parent I。他们检查但也没有 I,所以他们也要求那个。您的 Git 现在有义务提供承诺 H。他们检查并——嘿!——这一次他们 do 已经提交 H,所以他们说:不,谢谢,我已经有那个了.

您的 Git 现在不仅知道您必须发送 提交 JI,而且 文件 他们已经有了。他们有提交 H,所以他们也必须提交 G,并提交 F,依此类推。他们有 所有 de-duplicated 文件 与这些提交一起使用。因此,您的 Git 软件套件现在可以计算出一组 最小 的东西来发送给他们,这样他们就可以 重建 提交 I-J.

你的Git这样做;那就是你看到的“计数”和“压缩”等等。他们的 Git 收到这些东西,将其解压缩,并将新提交添加到 他们的 存储库。他们现在有:

...--F--G--H   <-- branch-name
            \
             I--J

在他们的 Git 存储库中。现在我们遇到了一个非常棘手的问题:一般来说,Git如何找到一个提交?答案总是,最终, 通过它的哈希 ID——但这又带来了另一个问题,即:Git 如何找到哈希 ID?他们看起来 运行dom.

虽然我们之前已经说过:Git(软件套件)经常通过使用 b运行ch 名称在某个特定的存储库中找到一些特定的提交。 b运行ch name branch-name,在 your 存储库中,找到 last 提交,现在是 J.我们希望他们的存储库中的同名找到相同的最后一次提交。

因此,您的 Git 软件现在要求他们的 Git 将 他们的 存储库的 b运行ch 名称 branch-name 设置为识别提交 J。他们会这样做如果允许这样做。 “允许”部分可以变得任意复杂——像 GitHub 和 Bitbucket 这样的网站在这里添加各种权限和规则——但是如果我们假设它没问题,并且他们会这样做,那么 他们最终会是:

...--F--G--H--I--J   <-- branch-name

他们的存储库中,你的Git存储库和他们的Git存储库将再次同步,至少对于这个特定的b运行通道名称。

这就是 git push 通常的工作方式:您进行新的提交,将它们添加到 your b运行ch 的末尾,然后您将您的新提交发送给其他一些 Git,并要求他们的软件将相同的提交添加到 同名 的 b运行ch 的末尾 他们的 存储库。 (哇!)

子模块

Git 中的子模块只不过是 两个独立的 mostly-independent Git 存储库。这当然需要大量的解释:

  • 为什么他们只是“大部分”独立? (这到底是什么意思?)
  • 如果他们多一点,他们是多少?

首先,与任何存储库一样,子模块存储库是提交的集合,每个提交都有一个唯一的哈希 ID。我们——或者至少 Git——喜欢将两个存储库之一称为 superproject,另一个称为 submodule。这两个都以字母 S 开头,这很烦人,而且两个词又长又笨拙,所以这里我将使用 R(像这样粗体)作为超级项目 R存储库,S作为S子模块。

(旁注:RS 中的哈希 ID 彼此独立。Git 非常努力- 并且通常会成功 - 使哈希 ID globallyevery Git 存储库中在宇宙中的任何地方都是唯一的。所以没有必要担心关于“污​​染” RS ID,反之亦然。在任何情况下,我们都可以将每个提交哈希 ID 视为完全唯一。通常,使用普通的非RS存储库,我们甚至不必关心关于 ID,因为我们只是使用名称。但是子模块使您必须更加了解 ID。)

首先让R成为超级项目的原因是它列出了来自S[=的原始哈希ID 476=]。它还必须列出 说明: 如果我们已经完成 Rgit clone,我们甚至没有克隆S还没有。因此 R 需要包含 说明 以便您的 Git 软件可以克隆 S.

您给 git clone 的指令非常简单:

git clone <url> <path>

(其中 path 部分甚至是可选的,但在这里,R 将始终指定一个路径——使用那些我们前面提到的正斜杠路径名)。这组指令进入名为 .gitmodules 的文件中。 git submodule add 命令将为您在 R 中设置此文件。使用它来设置 .gitmodules 文件很重要。 Git 即使您 没有 设置它,仍然会创建一个子模块,但是如果没有克隆指令,子模块实际上不会 工作.

请注意,此处没有适当的位置来放置身份验证(用户名和密码名)。这是一个通用的子模块问题。 (你可以将它们作为明文放入.gitmodules文件中,但是不要这样做,这是一个非常糟糕的主意,它们没有加密或受保护。)只要您可以开放访问克隆子模块,它通常不会出现任何实际问题。如果你不这样做,你将不得不以某种方式解决这个问题。

无论如何,您只需要一次 运行:

git submodule add ...

(填写 ... 部分)因此将成为超级项目 R,从而创建 .gitmodules 文件。然后您需要提交生成的 .gitmodules 文件,以便克隆 R 并签出 包含 该文件的提交的人,获取该文件,以便他们的 Git 软件可以 运行 git clone 命令在他们的系统上创建 S

您还需要将 S 放在他们可以克隆它的地方。当然,这意味着首先您需要创建一个 Git 存储库来保存 S。您可以按照创建任何 Git 存储库的方式执行此操作:

git init

或:

git clone

(本地,在您的机器上)以及您在创建存储库的任何托管站点上所做的一切。

现在您有了一个本地存储库 S,您需要将一些提交放入其中。这些提交中包含什么?

好吧,您已经说过您希望 R 中有一个 build/ 目录(文件夹),但实际上并不存储任何内置的R 中的任何提交中的文件。这是子模块实际工作的地方。 R 中的子模块,对于 S,其工作方式是:create me a folder here,然后将子模块克隆到文件夹。或者,如果子模块存储库已经存在——当你首先设置所有这些时它会存在,你刚刚创建了 S——你只需将整个存储库放入你的工作树中 R,姓名build.

请注意,此时 build/.git 将存在于 R 的工作树中。这是因为 Git 存储库隐藏了工作树顶层 .git 目录(文件夹)中的所有 Git 文件。所以你的新人,emty S 存储库仅包含一个 .git/,其中包含 Git 个文件。

您现在可以 运行 在 R 中执行 git submodule add 命令,因为现在您已经有了子模块:

git submodule add <url> build

(你可能想稍等片刻,但你可以此时肯定可以做到——这是你可以做到的最早时间点,因为到目前为止,S 不存在或不在正确的位置。)

您现在可以用文件填充 build/ 目录 ,它位于 R 的工作树中,例如,通过运行ning npm run build,或填充 build/ 目录的任何内容。然后你可以:

(cd build; git add .)

或等效项,以便在 S 中添加构建输出。您现在可以创建第一个提交 in S,或者如果您可以创建 S 中的第二个提交喜欢创建 README.mdLICENSE 等你的初始提交。你现在也可以在 S 中有 b运行ches,因为你现在在 S.[=102 中至少有一个提交=]

虽然您已经回到 R,但现在是 git add build 的时候了——或者,如果您选择延迟它,那么 运行 首先git submodule add。以后您将使用 git add build。这指示正在为 R 操纵 index / staging-area 的 Git 进入 存储库 S 和 运行:

git rev-parse HEAD

S中找到当前提交原始哈希ID

超级项目的 Git 存储库的索引现在获得一个新的 gitlink 条目。一个 gitlink 条目就像一个普通文件,除了它不是 git checkout 将它签出 作为 一个文件,它提供了一个 原始哈希 ID.基本上就是这样:一个路径名——在本例中为 build/——和一个原始哈希 ID。

这个 gitlink 就像提交中的那些 read-only、压缩和 de-duplicated 文件之一。只是它不是存储 文件数据 ,而是存储 提交哈希 ID。该哈希 ID 是 S 中某些提交的哈希 ID,而不是 R 本身中的某些提交。但是现在您已经为 R 更新了 index(或 staging area),您将需要在 R 中创建新的 commit。新的提交将包含任何更新的文件,加上 S 的正确哈希 ID,正如 git add 你 运行 (或那个 git submodule add 运行 给你)。

您在 R(不在 S) 中所做的 下一次提交将列出 当前的哈希 ID S 中提交。因此,一旦您在 S 中提交了构建文件,您就可以 git addRgit addgit commitR.

最后也是最棘手的部分

现在是最后一部分,如果您认为以上所有内容都很复杂和棘手的话,那么这是最棘手的:

  • 您必须 git pushS 中提交子模块,以便它普遍可用。一般而言,您应该首先执行此操作,但实际上您不必这样做。

  • 然后你必须git push超级项目在R中提交,这样其他人才能得到它。当其他人从 R 的另一个克隆获得此提交时,他们将能够从 S.[=102= 中看到正确的哈希 ID ]

  • 然后,如果其他人——比方说你的 co-worker Bob——想要获得构建文件 源代码,他们必须:

    • 获取新的 R 提交。
    • 指示他们的 Git 检查 新的 R 提交。
    • 指示他们的 Git 使用新签出的 R 提交 运行 git fetch in S 以获得新的 S 提交。
    • 指示他们的 Git 实际输入他们的克隆 Sgit checkout 正确的提交。

    他们可以使用 git checkout --recursive 一次完成所有这些操作,或者设置递归结帐选项。但是请注意可能会出错的地方:

  • 他们可能会获得您的新 R 提交并检查它,但根本忘记更新他们的 S .

  • 或者,他们可能会获取您的新 R 提交并检查它,然后尝试检查新提交 in S 在他们克隆的 S 中没有第一个 运行ning git fetch,这样他们就不会 新提交。

  • 或者,他们可能记得他们应该做的一切,但有人忘记了推送新的S承诺人们可以从共享存储库中获取它。他们会收到关于他们的子模块 Git 无法找到请求的提交的错误。

你可以看看如何这可能会变得非常混乱。各种单独的提交很容易以各种方式获得de-synchronized。一旦您确定了程序,并围绕所有内容编写了脚本以确保所有步骤都在正确的时间发生,它就可以很好地工作。但是有很多方法会出错。

停止将存储库边界视为任何实质性的事情。 只有个重要结构在Git是历史。

rm -rf build
git branch build $(git commit-tree -m 'Glitch project' `git mktree <&-`)
git worktree add build
git add build   # you'll get a newbie warning here
git remote add glitch u://r/l
git config remote.glitch.push  +refs/heads/build:refs/heads/build

并且不要将 build 分支推送到您的主仓库,您不希望那里有该历史记录,所以不要将其推送到那里。

git config remote.origin.push :   # this is "matching", see the docs
git config remote.origin.push --add ^refs/heads/build  # don't match this

现在,在构建完成并且您非常喜欢发布之后,

git -C build add .
git -C build commit -m "built $(date)"
git add build
git push glitch

当您从 github 存储库克隆时,您将获得其中包含 build 条目的历史记录,结帐将在那里创建一个空目录,但您不会拥有 build 历史本身。没关系:如果你想要它,你可以从有它的地方获取它然后 git worktree add 它,或者你可以不打扰,git init build 并在本地重新构建。


1only”看似有点强,其实不然。其他一切都是支持、脚手架、基础设施,只是为了帮助检查、分析和扩展历史。