【问题标题】:How do I commit/push my build folder into another git repository and not into the main repository?如何将构建文件夹提交/推送到另一个 git 存储库而不是主存储库?
【发布时间】:2021-10-09 03:02:23
【问题描述】:

所以,我最近制作了一个 React 应用程序,并将其发布在 GitHub 上。但是,我想将输出(运行npm run build 后的build 文件夹)发布到故障应用程序。由于所有 Glitch 应用程序都有一个 git 存储库,我认为这将是执行此操作的最佳方法。这是我想要的结构:

  • 我的主要git repo,推送到 GitHub。 此存储库忽略 build 文件夹。
  • 另一个“子”git 存储库,仅将 build 的内容推送到 Glitch。

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

我也对如何设置子模块感到困惑,因此也可以提供一个示例/解释。

~阿尤什

【问题讨论】:

    标签: reactjs git github git-submodules glitch-framework


    【解决方案1】:

    我不完全确定您在此处想要一个子模块,但子模块可以让您按照您的描述进行操作。不过,子模块很棘手。人们称它们为 sob-modules 是有原因的。 ?

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

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

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

    • git push 命令推送提交

    • commit 是一个东西(从技术上讲,是一个 commit 对象,但人们使用这个词非常松散,因此这里是松散的“thingy”术语?)具有以下内容特点:

      • 它有一个唯一的哈希 ID。
      • 它存储文件。请注意,提交不存储 文件夹,仅存储 文件。这些文件的路径名包含嵌入的斜杠(始终为正斜杠,即使您在 Windows 系统上提取带有反斜杠的提交文件)。这最终会在以后变得很重要,但是如果您愿意,您可以将它们视为充满文件的文件夹,只要您记得 Git can't store an empty folder properly(因为它只存储 文件)。这些文件作为完整快照存储,尽管它们被压缩并且——重要的是——在存储库中的所有提交中进行了重复数据删除。因此,通常一些新的提交会重复使用之前提交的 30,000 个文件这一事实并不重要:重复使用的文件不占用空间,因为它们实际上是重复使用的。
      • 它存储元数据,或关于提交本身的信息。这包括诸如谁提交、何时提交、日志消息等内容;而且,对于 Git 自身的操作至关重要,它还包括一些早期提交的原始哈希 ID。大多数提交只存储一个较早提交的哈希 ID,我们(和 Git)称之为 parent。这就是历史在 Git 存储库中的运作方式:每个提交记住它的父级
      • 它是完全只读的。任何提交的任何部分都不能更改。 (这就是允许重复数据删除以及许多其他 Git 魔法的原因。)
    • 存储库还包含允许 Git 查找提交的名称(例如分支和标签名称)。这通过让一个名称准确存储 one 哈希 ID 来实现。对于分支名称,根据定义,存储的哈希 ID 是分支中的最后一次提交。由于提交存储父哈希 ID,Git 可以从我们决定调用“分支中的最后一个 X”的任何提交向后工作:X~1X 中的倒数第二个,X~2 是第三个-last,以此类推。

    向分支添加新提交的行为包括以下步骤:

    1. 签出那个提交(使用git checkoutgit switch)通过签出那个分支(使用相同的命令),所以现在这是当前分支。此操作会填充 Git 的索引(保存您的提议的下一次提交)和您的 工作树,其中 Git 将所有文件复制到可用表单中。除了 Git 本身,内部的、去重的表单通常对所有东西都无法使用。

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

    3. 你运行git add。这指示 Git 查看更新的工作树文件。 Git 会将这些更新后的文件以更新后的形式复制回 Git 的索引(也称为 暂存区),重新压缩和重复数据删除,通常使它们为下一次提交做好准备。

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

      这时神奇的事情发生了:git commit将新提交的哈希 ID 写入当前分支名称。所以现在,分支上的最后一个提交是你的新提交。 这是一个分支的增长方式,一次提交。没有现有提交更改——没有可以更改——但新提交指向最后一次提交,现在是第二次提交-最后一次提交。 分支名称 移动

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

    git push:将一个 Git 存储库与另一个交叉连接

    在某些 Git 存储库中进行新的 Git 提交,只会创建新的快照加元数据。下一个技巧是将该提交放入某个 other Git 存储库。

    如果我们从两个相同的 Git 存储库开始,每个都有一些提交集和一些分支名称,标识相同的 last 提交:

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

    在回购 B 中也是如此。但是,在回购 A 中,我们这样做了:

    git checkout branch-name
    <do stuff>
    git commit
    

    导致回购 A 包含:

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

    (我变得懒惰,不费心在此处正确绘制提交到提交的箭头)。新提交II,如HGF,代表一些看起来很丑的随机哈希ID——指向现有提交H。你甚至可以做出不止一个新的提交:

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

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

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

    您的 Git 现在不仅知道您必须发送 commits JI,还知道 他们已经拥有哪些文件。他们有提交H,所以他们也必须有提交G,并提交F,等等。他们拥有所有重复数据删除的文件,这些文件与这些提交有关。因此,您的 Git 软件套件现在可以计算出最少 组数据以发送给他们,以便他们可以重构 提交I-J

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

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

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

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

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

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

    他们的存储库中,您的 Git 存储库和他们的 Git 存储库将再次同步,至少对于这个特定的分支名称。

    这就是git push 通常的工作方式:你进行新的提交,将它们添加到 你的 分支的末尾,然后你将新的提交发送到其他 Git,并询问他们的软件将相同的提交添加到 their 存储库中 same name 的分支的末尾。 (哇!)

    子模块

    Git 中的子模块只不过是两个独立的、大部分独立的 Git 存储库。这当然需要很多解释:

    • 为什么它们只是“大部分”独立? (这甚至意味着什么?)
    • 如果他们多一点,他们更多是什么?

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

    (旁注:RS 中的哈希 ID 是相互独立的。Git 非常努力地尝试——并且通常会成功——制作哈希 ID 全局在世界各地的每个 Git 存储库中都是唯一的。因此无需担心 S ID 会“污染”R反之亦然。在任何情况下,我们都可以将每个提交哈希 ID 视为完全唯一。通常,对于普通的非RS存储库,我们不会'甚至不必关心 ID,因为我们只使用名称。但是子模块让您必须更加了解 ID。)

    R 之所以成为超级项目 首先是它列出了来自S 的原始哈希ID。它还必须列出说明:如果我们已经完成了 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 的子模块的工作方式是:在此处创建一个文件夹,然后将子模块克隆到该文件夹​​中。或者,如果子模块存储库已经存在——就像你一开始设置所有这些时一样,你刚刚创建了S——你只需将整个存储库放入 R 的工作树中,名称为build

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

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

    git submodule add <url> build
    

    (您可能想稍等片刻,但您可以在这一点上绝对可以做到 - 这是您可以做到的最早时间,因为到目前为止,S 不存在或尚未出现在正确的位置。)

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

    (cd build; git add .)
    

    或等效项,以便在 S 中添加构建输出。您现在可以在 in S 中创建第一个提交,如果您想创建 README.md 和 @,也可以在 S 中创建第二个提交987654396@ 等你的初始提交。您现在也可以在 S 中拥有分支,因为您现在在 S 中至少有一个提交。

    现在您回到 R 中,是时候使用 git add build 了——或者,如果您选择延迟它,请先运行 git submodule add。将来您将使用git add build。这指示正在为 R 操作 索引/暂存区域的 Git 进入 存储库 S 并运行:

    git rev-parse HEAD
    

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

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

    这个 gitlink 就像提交中的只读、压缩和去重文件之一。只是它没有存储文件数据,而是存储了一个提交哈希ID。该哈希 ID 是 S 中的某个提交,而不是 R 本身中的某个提交。但是现在您已经更新了 Rindex(或 暂存区),您需要进行新的 commit 在 R 中。新的提交将包含所有更新的文件,以及 S 的正确哈希 ID,正如您刚才运行的 git add(或为您运行的 git submodule add)找到的那样。

    您在 R(不在 S 中) 中进行的下一次提交将列出 当前S 中的提交的哈希 ID 。因此,一旦您在 S 中提交了构建文件,您就可以在 Rgit add 它们,在 Rgit commit。 p>

    最后也是最棘手的部分

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

    • 您必须git push S 中的子模块提交,以便它普遍可用。一般来说,您应该先这样做,尽管实际上您不必这样做。

    • 然后你必须git push R 中的超级项目提交,以便其他人可以得到它。当其他人从 R 的另一个克隆获得此提交时,他们将能够从 S 中看到正确的哈希 ID。

    • 然后,如果其他人(比如说您的同事 Bob)想要同时获得构建文件源,他们必须:

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

      他们可以使用git checkout --recursive 一次性完成所有操作,或者设置递归结帐选项。请注意可能出现的问题:

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

    • 或者,他们可能会获取您的新 R 提交并将其签出,然后尝试签出新提交 in S 而无需先在他们的 S 克隆中运行 git fetch,这样他们就没有 新的提交。

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

    您可以看到这会变得非常混乱。各种单独的提交很容易以各种方式去同步。一旦你完成了程序,并在所有内容上都有脚本,以确保所有步骤都在正确的时间发生,它就可以很好地工作。但是出错的方法有很多。

    【讨论】:

    • +1 哇,这是一个巨大的帮助!谢谢你惊人的解释!!!但是,我只想从中遗漏一件事。在你的解释中,你解释了如何S 可以从 R 获得。但是,我想将 S 设置为“隐藏”,包括存储库 URL(将 S 推送到 Glitch)和所有实际文件,所以它就像从来没有那里。有什么方法可以用 Git 子模块做到这一点?
    • 另外,在开始时,你说“我不完全确定你想要一个子模块”。那么,我应该改用什么?我是否应该尝试将build 文件夹放在我的.gitignore(它已经是)中,然后在build 中创建一个新的git 存储库?或者,您是否正在考虑另一种方法?
    • 否:存在 gitlink 的事实在提交中很明显,因为 gitlink 就在那里。你可以省略克隆指令,这样你就有一个无法克隆的gitlink——它产生了我有时称之为半途而废的子模块。不过,它们很烦人,因为现在你有这个奇怪的 gitlink 会导致问题。 (要制作其中之一,请跳过git submodule add 步骤。)
    • 与其拥有一个子模块,不如考虑拥有一堆 tar 文件或其他存档,特定构建。任何时候你有一个发布版本的东西,你构建发布档案并将它贴在一个可以获得的地方,以供那些不想自己构建它的人使用。
    • 问题是,Glitch 使用git 来从您的计算机上传文件。你是在建议我做一个 Github Release,然后使用 Github Action 之类的东西自动推送到 Glitch 吗?
    【解决方案2】:
    1. 要忽略特定文件夹/文件,可以使用 .gitignore 文件
    2. 要将特定文件夹/“子”git 存储库推送到不同的存储库,方法是在该特定文件夹上初始化新 git,方法是运行“git init”和“git remote add”。

    例子:

    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 
    

    【讨论】:

      【解决方案3】:

      停止将存储库边界视为任何实质性的东西。 唯一 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 条目的历史记录,并且 checkout 将在那里创建一个空目录,但您不会拥有 build 历史记录本身。没关系:如果你想要它,你可以从某个有它的地方获取它,然后 git worktree add 它,或者你可以不打扰,git init build 并在本地重做构建。


      1 "only" 可能看起来有点强,但实际上并非如此。其他一切都是支持、脚手架、基础设施,只是为了帮助检查、分析、扩展历史。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 好的,但是我将如何配置子模块?我对此有点困惑,请您解释/举个例子吗?
        猜你喜欢
        • 2014-10-20
        • 2015-09-14
        • 2013-02-23
        • 1970-01-01
        • 1970-01-01
        • 2020-10-14
        • 2015-03-01
        • 2020-11-27
        相关资源
        最近更新 更多