Git 子存储库

标签 git dependencies repository

我是一个小型固件团队的成员,我们使用私有(private) Git 服务器进行版本控制。我们的代码库通常包含平台特定代码、多个平台使用的通用代码以及我们开发的微处理器制造商提供的 SDK 的文件夹。

目前我们的存储库以通用代码为中心;每个平台在存储库中都有一个文件夹。这至少有两个严重的后果;

  • 当我们为一个平台推送更改时,它对于共享相同公共(public)代码的所有其他平台都是可见的。这使提交历史与多个平台的发布变得困惑,并且在过去导致我们对我们正在审查哪个平台的更改感到困惑。
  • 对公共(public)代码的更改需要针对存储库中的所有平台进行验证,然后才能提交,从而造成不必要的痛苦。 (昨天有人没有这样做,并进行了更改,导致我们的几个旧平台无法构建。这个特定的存储库中有 5 个平台)

  • 我正在 push 为每个平台建立一个存储库,以便我们可以对通用代码进行更改并定期升级 SDK,而不必被迫更新和验证我们支持的所有平台。由于我们是一个五人团队,这些问题通常会在发布前的紧要关头出现,这将为我们节省大量不必要的脑损伤和时间。当然,困难的部分在于实现。

    现在我正在想象一个系统,其中每个平台都有自己的存储库,每个公共(public)代码库都有自己的存储库,我们使用的每个 SDK 都有自己的存储库。平台存储库将链接到或以其他方式引用它们用作“子存储库”的公共(public)代码和 SDK。

    我已经阅读了关于子模块的内容,但我认为它们会导致比它们修复的更多的问题;最近我们不得不调试三年前的固件,这需要将我们的本地代码库重置为问题版本以进行静态分析。据我了解,子模块的本地版本与其所在的存储库分离,这意味着将存储库重置为旧提交将使子模块相对于存储库的其余部分留在“ future ”。在调试旧代码的上下文中,这种行为是 100% Not Acceptable 。

    这是我想要实现的项目符号列表; (按重要性排序)
  • 将特定于平台的代码与通用代码分开,以便对通用代码和/或 SDK 的更改不会对其他平台产生不可预见的后果。
  • 为每个平台、每个通用代码库和每个 SDK 创建新的存储库。
  • 将远程存储库克隆到本地机器应该是一个步骤;在平台构建之前,不应强制新用户 pull 每个子存储库。
  • 此外;恢复旧代码,包括用于构建任何给定平台的 XXX-YYY 版本的通用代码和 SDK,必须成为可能。
  • 编辑:上次我不得不“恢复”代码时,我对正在调试的构建进行了硬重置,然后对比我知道的比它早一年的构建进行了软重置,这很好,但只有这样我的语法高亮器中的 Git 插件才会显示对我来说已知的好和已知的坏之间的变化。

    顺便说一下,这个特定的平台是整体的,这个问题并不适用于它,但为了争论,我们会说它确实适用。
  • 在平台的维护者引入新的公共(public)代码之前,修改公共(public)代码不应影响平台。 (我相信通过添加公共(public)代码的标记提交之一而不是公共(public)代码的主提交,子树可以做到这一点)
  • 只读子存储库是可接受的限制。 (即无法在平台的存储库中修改子存储库或无法从平台的存储库推送对子存储库的更改)
  • SmartGit/HG 支持是可取的,因为除了我们的一名成员之外,所有人都在使用它。
  • 编写一次性任务脚本是可以接受的,但我不想用为它做 Git 工作的脚本来污染平台。

  • 我也读过关于子树的文章,但目前我不确定它们是否会直觉上允许我想要的行为。我的主要问题是:Git 支持这种功能吗?如果是,该功能是由子树实现的还是由我还不知道的另一种方法实现的?

    最佳答案

    我只是在这里插一句,因为我认为 @VonC 的回答并没有完全解释为什么子模块确实解决了 @JacaByte 的担忧。

    I think they would cause more issues than they would fix; recently we had to debug three year old firmware, which required resetting our local codebase to the problem release to do static analysis. As I understand the local version of the submodule is detached from the repository in which it resides, which means resetting the repository to an old commit will leave the submodule in the "future" with respect to the rest of the repository.



    这并不完全不正确。然而,说子模块不允许您检查旧版本的代码库是不正确的 .这种混淆源于 git checkout不会更新子模块工作树,而是将这项工作留给您(git submodule update --init --recursive 会为您完成这项工作)。

    子模块是用于控制存储库依赖项的绝佳工具。 事实上,他们做了你想让他们做的事情:将特定版本的代码与特定版本的依赖关系关联起来。具体来说,“子模块”实际上只是一个文本文件,其中包含与子模块 repo 上的提交相对应的 SHA-1 哈希(Git 将有关远程子模块的元信息存储在 .gitmodules 中,这就是它如何知道从哪里获取子模块 repo )。

    然而,使用子模块可能会很棘手。它要求您对 Git 模型本身(提交和工作树/暂存/存储库)以及子模块系统本身有很好的理解。但是如果你确保人们知道他们在做什么(这是一个非常大的假设),你就可以避免用脚射击自己。

    您似乎非常关心回滚到旧版本代码库的能力,@VonC 的答案似乎并没有消除您的担忧,所以我将在此处提供如何执行此操作的演练:

    我会用我的 personal .dotfiles repo例如(我将 Vim 扩展作为子模块引入;在撰写本文时, HEADd9c0a797ad45a0d2fd92a07d3c3802528ed7b82a ):
    $ git clone https://github.com/sxlijin/.dotfiles dotfiles
    Cloning into 'dotfiles'...
    remote: Counting objects: 350, done.
    remote: Compressing objects: 100% (31/31), done.
    remote: Total 350 (delta 12), reused 0 (delta 0), pack-reused 318
    Receiving objects: 100% (350/350), 86.61 KiB | 0 bytes/s, done.
    Resolving deltas: 100% (176/176), done.
    $ cd dotfiles/
    $ git submodule update --init --recursive
    Submodule 'bundle/jedi-vim' (http://github.com/davidhalter/jedi-vim) registered for path 'vim/bundle/jedi-vim'
    Submodule 'bundle/nerdtree' (https://github.com/scrooloose/nerdtree.git) registered for path 'vim/bundle/nerdtree'
    Submodule 'bundle/supertab' (https://github.com/ervandew/supertab.git) registered for path 'vim/bundle/supertab'
    Submodule 'vim/bundle/vim-flake8' (https://github.com/nvie/vim-flake8.git) registered for path 'vim/bundle/vim-flake8'
    Submodule 'bundle/vim-pathogen' (https://github.com/tpope/vim-pathogen.git) registered for path 'vim/bundle/vim-pathogen'
    Cloning into '/home/pockets/dotfiles/vim/bundle/jedi-vim'...
    warning: redirecting to https://github.com/davidhalter/jedi-vim/
    Cloning into '/home/pockets/dotfiles/vim/bundle/nerdtree'...
    Cloning into '/home/pockets/dotfiles/vim/bundle/supertab'...
    Cloning into '/home/pockets/dotfiles/vim/bundle/vim-flake8'...
    Cloning into '/home/pockets/dotfiles/vim/bundle/vim-pathogen'...
    Submodule path 'vim/bundle/jedi-vim': checked out '8cf616b0887276e026aefdf68bc0311b83eec381'
    Submodule 'jedi' (https://github.com/davidhalter/jedi.git) registered for path 'vim/bundle/jedi-vim/jedi'
    Cloning into '/home/pockets/dotfiles/vim/bundle/jedi-vim/jedi'...
    Submodule path 'vim/bundle/jedi-vim/jedi': checked out 'f05c0714c701ab784bd344aa063acd216fb45ec0'
    Submodule path 'vim/bundle/nerdtree': checked out '281701021c5001332a862da80175bf585d24e2e8'
    Submodule path 'vim/bundle/supertab': checked out 'cdaa5c27c5a7f8b08a43d0b2e65929512299e33a'
    Submodule path 'vim/bundle/vim-flake8': checked out '91818a7d5f5a0af5139e9adfedc9d00fa963e699'
    Submodule path 'vim/bundle/vim-pathogen': checked out '7ba2e1b67a8f8bcbafedaf6763580390dfd93436'
    

    最后一条命令 git submodule update --init --recursive查看存储在 HEAD 中的子模块哈希值,并更新了我的工作树(它是 而不是 将它们更新到各自存储库中的最新提交;即 git submodule update --remote ),在相应的路径上添加相应存储库的内容,以递归方式(因此,如果我的任何子模块具有子模块,那么这些存储库的内容也会添加到我的工作树中)。

    现在,碰巧我在 HEAD~2 中更新了我的 Vim 插件:
    $ git show HEAD~2 -- vim/bundle/*
    commit 27bfe76851991026bd026b4bf2ab10d6ecbc6f74
    Author: First Last <first.last@email.domain>
    Date:   Thu Feb 2 13:33:30 2017 -0600
    
        update dependencies
    
    diff --git a/vim/bundle/jedi-vim b/vim/bundle/jedi-vim
    index f191ccd..8cf616b 160000
    --- a/vim/bundle/jedi-vim
    +++ b/vim/bundle/jedi-vim
    @@ -1 +1 @@
    -Subproject commit f191ccd6fb7f3bc2272a34d6230487caf64face7
    +Subproject commit 8cf616b0887276e026aefdf68bc0311b83eec381
    diff --git a/vim/bundle/nerdtree b/vim/bundle/nerdtree
    index eee431d..2817010 160000
    --- a/vim/bundle/nerdtree
    +++ b/vim/bundle/nerdtree
    @@ -1 +1 @@
    -Subproject commit eee431dbd44111c858c6d33ffd366cae1f17f8b3
    +Subproject commit 281701021c5001332a862da80175bf585d24e2e8
    diff --git a/vim/bundle/supertab b/vim/bundle/supertab
    index 6651177..cdaa5c2 160000
    --- a/vim/bundle/supertab
    +++ b/vim/bundle/supertab
    @@ -1 +1 @@
    -Subproject commit 66511772a430a5eaad7f7d03dbb02e8f33c4a641
    +Subproject commit cdaa5c27c5a7f8b08a43d0b2e65929512299e33a
    

    假设现在我的 Vim 插件似乎有些奇怪,我怀疑上述提交是造成这种奇怪现象的原因,所以我想在更新它们之前将我的插件回滚到它们原来的样子。
    $ git checkout -b testing HEAD~2^
    M       vim/bundle/jedi-vim
    M       vim/bundle/nerdtree
    M       vim/bundle/supertab
    Switched to a new branch 'testing'
    $ git status
    On branch testing
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   vim/bundle/jedi-vim (new commits)
            modified:   vim/bundle/nerdtree (new commits)
            modified:   vim/bundle/supertab (new commits)
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

    好吧,现在事情似乎有点奇怪。进行此 checkout 时,我的工作树没有任何更改,因此 checkout 似乎正在对我的工作树进行更改?这里发生了什么?
    $ git diff
    diff --git a/vim/bundle/jedi-vim b/vim/bundle/jedi-vim
    index f191ccd..8cf616b 160000
    --- a/vim/bundle/jedi-vim
    +++ b/vim/bundle/jedi-vim
    @@ -1 +1 @@
    -Subproject commit f191ccd6fb7f3bc2272a34d6230487caf64face7
    +Subproject commit 8cf616b0887276e026aefdf68bc0311b83eec381
    diff --git a/vim/bundle/nerdtree b/vim/bundle/nerdtree
    index eee431d..2817010 160000
    --- a/vim/bundle/nerdtree
    +++ b/vim/bundle/nerdtree
    @@ -1 +1 @@
    -Subproject commit eee431dbd44111c858c6d33ffd366cae1f17f8b3
    +Subproject commit 281701021c5001332a862da80175bf585d24e2e8
    diff --git a/vim/bundle/supertab b/vim/bundle/supertab
    index 6651177..cdaa5c2 160000
    --- a/vim/bundle/supertab
    +++ b/vim/bundle/supertab
    @@ -1 +1 @@
    -Subproject commit 66511772a430a5eaad7f7d03dbb02e8f33c4a641
    +Subproject commit cdaa5c27c5a7f8b08a43d0b2e65929512299e33a
    

    嗯,好的,所以这很有趣。出于某种原因,git diff是说在我当前的工作树中 check out 的子模块的版本不匹配 - 但这个差异看起来很像 git show我上面做的。我想知道 HEAD 中实际上有哪些子模块...
    $ git ls-tree HEAD -- vim/bundle/
    160000 commit f191ccd6fb7f3bc2272a34d6230487caf64face7  vim/bundle/jedi-vim
    160000 commit eee431dbd44111c858c6d33ffd366cae1f17f8b3  vim/bundle/nerdtree
    160000 commit 66511772a430a5eaad7f7d03dbb02e8f33c4a641  vim/bundle/supertab
    160000 commit 91818a7d5f5a0af5139e9adfedc9d00fa963e699  vim/bundle/vim-flake8
    160000 commit 7ba2e1b67a8f8bcbafedaf6763580390dfd93436  vim/bundle/vim-pathogen
    

    啊哈!我们走了 - git checkout只是没有更新工作树中子模块的版本!不过,Git 本身仍然知道应该检查这些子模块的哈希值。原来有一个命令可以为您执行此操作:
    $ git submodule update --init --recursive
    Submodule path 'vim/bundle/jedi-vim': checked out 'f191ccd6fb7f3bc2272a34d6230487caf64face7'
    Submodule path 'vim/bundle/jedi-vim/jedi': checked out '2ba78ab725f1e02dfef8bc50b0204cf656e8ee23'
    Submodule path 'vim/bundle/nerdtree': checked out 'eee431dbd44111c858c6d33ffd366cae1f17f8b3'
    Submodule path 'vim/bundle/supertab': checked out '66511772a430a5eaad7f7d03dbb02e8f33c4a641'
    

    要单独解决您的疑虑:
  • Break platform specific code away from common code so changes to common code and/or the SDK don't have unforeseen consequences on other platforms.



    是的。子模块。
  • Create new repositories for each platform, each common code base and each SDK.



    这在很大程度上取决于您如何布置当前的代码库。可能是 git subtree可能有你想要的(特别是 git subtree split 允许你将子树中的文件提取到一个单独的 Git 存储库中,充满提交历史,但我不知道它与像你一样大和老的存储库工作得如何似乎在描述 - 有关更多详细信息,请参阅 man git subtree)。
  • Cloning a remote repository to a local machine should be a one step process; new users should not be forced to pull each sub-repository before the platform will build.



    不。子模块在设计上不跟踪子存储库的内容:这是子存储库的工作。他们所做的就是在子仓库中保留一个指向特定提交的指针。
  • In addition; restoring old code, including the common code and SDK that was used to build version XXX-YYY of any given platform, must be possible. Modifying the common code should not effect the platforms until the maintainer of the platform pulls in the new common code. (I believe this is possible with subtrees by adding one of the common code's tagged commits instead of the common code's master)



    子模块通过设计做到这一点。这就是为什么他们指向特定提交而不是远程提交。
  • A read-only sub-repository is an acceptable limitation. (i.e. the sub-repository either cannot be modified in the platform's repository or changes to the sub-repository cannot be pushed from the platform's repository)



    子模块通常应被视为只读子存储库。从子模块推送更新是可能的,但会给用户带来更多开销,以确保他们不会搞砸正在使用的子模块的版本。
  • SmartGit/HG support is desirable as all but one of our members uses it.



    这里没有任何保证。您可能需要联系所有 3 个开发社区(Git、SmartGit、Mercurial)来解决这个问题。
  • Scripting one-time tasks is acceptable, but I don't want to pollute platforms with scripts that do Git's job for it.



    取决于你说的有多复杂。我在上面已经表明,检查旧版本的代码只是两个命令:checkoutsubmodule update --init --recursive ,但不清楚你在要求什么。
  • 关于Git 子存储库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42182903/

    相关文章:

    git - 使用提交的 Git 子模块时有哪些选项?

    git - 使用 Git 将远程 'origin' 更改为 'upstream'

    java - cxf Spring Maven依赖冲突

    c# - 支持按分区键查询而无需更改接口(interface)的存储库

    datetime - Delphi:格式化日期时间,不带午夜时间部分

    eclipse - eclipse 的 git 插件有多稳定?

    git - 如何返回一些提交,但仍然在 git 中保留提交历史?

    Eclipse 中依赖于 super 项目的 Java 子项目

    python - CMake 有没有办法利用 `swig -MM` 生成的依赖项?

    Spring Data Hazelcast 存储库错误