Git:如何使外部存储库和嵌入式存储库作为常规/独立存储库工作?

63

我有一个大项目(假设为A repo),其中有一个子文件夹来自B repo。当我从A repo提交时,我会遇到以下警告:

warning: adding embedded git repository: extractor/annotator-server
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add <url> extractor/annotator-server
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint:   git rm --cached extractor/annotator-server
hint:
hint: See "git help submodule" for more information.

我看过 git-submodulegit-subtree:

在另一个 Git 仓库中维护 Git 仓库

https://www.atlassian.com/blog/git/alternatives-to-git-submodule-git-subtree

但我不喜欢它们,因为它们需要额外的配置。


我想要的是,例如:

像这样的结构:

A/
--- a.py

--- B/
--- B/b.py

当我改变 B/b.py 时。

  1. 如果我在路径 A/ 上,git add 可以检测到 B/b.py 的更改,git push 只会将其提交到 A 存储库中。

    git add .   (would add changes under A/  )
    git push   (would push changes under A/  )
    git pull   (would pull changes under A/  )
    git clone XXX:A  (would clone all files under A/ ,    A/B/ is just looks like plain folder with all files, not a repo )
    
    如果我在路径 A/B/ 上,git add 只会添加 B/b.py 的更改到 B 仓库中,git push 也只会将其提交到 B 仓库中。
    结果为:如果我在路径 A/B/上,git add 只会添加B/b.py的更改到B仓库中,git push也只会提交到B仓库。
    git add .   (would add changes under B/ , but not add changes to A repo)
    git push   (would push changes under B/ , but not push changes to A repo)
    git pull   (would clone changes under B/ ,  )
    git clone XXX:B  (would clone all files under B/  )
    
  2. 要在另一台机器上同步A和B,只需执行

  3. git clone A
    rm -rf A/B/
    git clone B ./B
    git add . && git commit 'sync with B'
    

换句话说,A 和 B 作为一个独立的仓库。

但事实上,A 仓库将 B 仓库视为子模块:

A 仓库:https://github.com/eromoe/test

B 仓库:https://github.com/eromoe/test2


如何强制 A 仓库跟踪 A/ 下的所有文件,并使 B 仓库跟踪 A/B/ 下的所有文件? 我想让 A 和 B 作为一个自包含的仓库,不需要任何其他配置。


Git不可行,请使用SVN。也许SVN也无法满足您的需求。 - ElpieKay
1
我只想将整个子 .git 结构保存到一个 git 仓库中,为什么这是不可能的。子模块不会存储你的本地分支或未提交的文件。 - Kamil Dziedzic
9个回答

38
您可以使用以下命令将test2库中的文件添加到test库中,如下所示:
# In local test repo
rm -rf test2
git clone https://github.com/eromoe/test2
git add test2/
git commit -am 'add files from test2 repo to test repo'
git push

注意:

应该使用 git add test2/(带斜杠,不是 git add test2)。

git add test2/ 会将 test2 文件夹及其文件视为测试仓库的普通文件夹和文件(创建模式为100644)。

git add test2 会将 test2 文件夹视为测试仓库的子模块(创建模式为160000)。


所以这就是为什么 git add . 不起作用,谢谢。嗯...如果有一种方法可以强制 git add . 添加所有文件,那将是最理想的。 - Mithril
4
感谢你强调“test2”和“test2/”之间的区别。 - gizembrh
2
这很有帮助,但如果我还想让它像普通文件夹一样添加内部 test2/ 仓库中的 .git 文件夹(即路径为 test2/.git/*),该怎么做? - Gabriel Staples
我对自己的问题的回答:https://dev59.com/WVYN5IYBdhLWcg3wuaJx#62368415 - Gabriel Staples

35

可能是git已提醒该存储库。这对我很有帮助:

    git rm --cached 拥有存储库的文件夹
    git commit -m "删除缓存存储库"
    git add 拥有存储库的文件夹/
    git commit -m "添加文件夹"
    git push

16
2023年9月2日更新:“正确”的方法是通过git子模块来完成。但是,这需要一些学习和经验。如果急于或只是存档项目,请使用下面的粗暴方法。如果想要学习git子模块以便在git中进行正确的“仓库内部”软件开发,请参阅本答案底部标题为“更多关于git子模块”的部分。

手动、粗暴的方法:

对于任何希望将一堆git仓库存档到一个更大的父仓库中的人来说,最简单的粗暴解决方案就是将所有嵌套的.git文件夹重命名为其他任何名称,例如:.git改为..git。现在,git add -A将像任何其他正常文件夹一样将它们全部添加到父git项目中,您可以轻松地在父仓库中git commit所有内容。完成。

自动、粗暴的方法:

使用git-disable-repos.sh

https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles的一部分)。
我在周末刚写了这个脚本,并已经在许多项目中使用过它。它运行得非常好!请查看文件顶部的注释以获取详细信息和安装说明,然后运行git disable-repos -h来查看帮助菜单。
安装:
git clone https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles.git
cd eRCaGuy_dotfiles/useful_scripts
mkdir -p ~/bin
ln -si "${PWD}/git-disable-repos.sh" ~/bin/git-disable-repos
# If this is the first time using your ~/bin dir, log out and
# log back in now. Otherwise, just re-source your .bashrc file:
. ~/.bashrc

这是标准的使用模式:
cd path/to/parent/repo
# Do a dry-run to see which repos will be temporarily disabled
git disable-repos --true_dryrun
# Now actually disable them: disable all git repos in this dir and below
git disable-repos --true
# re-enable just the parent repo
mv ..git .git
# quit tracking the subrepo as a single file (required
# if you previously tried to add it to your main repo before
# disabling it as a git repo)
git rm --cached path/to/subrepo
# add all files, including the now-disabled sub-repos, to the parent repo
git add -A
# commit all files
git commit

这将提交所有子仓库,包括它们的(现在是..git).git文件夹和所有git工件,作为普通文件,到父级git仓库中。您有100%的控制权!想要只更新一个子仓库?那么进入该仓库并将其一个..git文件夹手动重命名为.git,然后像正常一样使用该子仓库,完成后再次运行git disable-repos --true(或手动将.git重新命名为..git),并将其提交到父仓库中。我的git disable-repos脚本的美妙之处在于,如果需要的话,它可以快速而无缝地同时禁用或启用数百个子仓库,而手动操作则不切实际。

也许我的用例很奇怪:我需要将大量内容提交到一个仓库中,直到以后能够清理并单独分离出每个子仓库,但它确实满足了我的需求。

以下是git disable-repos -h的完整帮助菜单输出

$ git disable-repos -h

'git disable-repos' version 0.3.0
  - Rename all ".git" subdirectories in the current directory to "..git" to temporarily
    "disable" them so that they can be easily added to a parent git repo as if they weren't 
    git repos themselves (".git" <--> "..git").
  - Why? See my StackOverflow answer here: https://dev59.com/WVYN5IYBdhLWcg3wuaJx#62368415
  - See also the "Long Description" below.
  - NB: if your sub-repo's dir is already being tracked in your git repo, accidentally, stop 
    tracking it with this cmd: 'git rm --cached path/to/subrepo' in order to be able to 
    start tracking it again fully, as a normal directory, after disabling it as a sub-repo 
    with this script. To view all tracked files in your repo, use 'git ls-files'. 
      - References: 
        1. https://dev59.com/pXM_5IYBdhLWcg3wrFMt#1274447
        2. https://dev59.com/zYXca4cB1Zd3GeqPPe_j#27416839
        3. https://dev59.com/7moy5IYBdhLWcg3wdd1v#14406253

Usage: 'git disable-repos [positional_parameters]'
  Positional Parameters:
    '-h' OR '-?'         = print this help menu, piped to the 'less' page viewer
    '-v' OR '--version'  = print the author and version
    '--true'             = Disable all repos by renaming all ".git" subdirectories --> "..git"
        So, once you do 'git disable-repos --true' **from within the parent repo's root directory,** 
        you can then do 'mv ..git .git && git add -A' to re-enable the parent repo ONLY and 
        stage all files and folders to be added to it. Then, run 'git commit' to commit them. 
        Prior to running 'git disable-repos --true', git would not have allowed adding all 
        subdirectories since it won't normally let you add sub-repos to a repo, and it recognizes 
        sub-repos by the existence of their ".git" directories.  
    '--true_dryrun'      = dry run of the above
    '--false'            = Re-enable all repos by renaming all "..git" subdirectories --> ".git"
    '--false_dryrun'     = dry run of the above
    '--list'             = list all ".git" and "..git" subdirectories

Common Usage Examples:
 1. To rename all '.git' subdirectories to '..git' **except for** the one immediately in the current 
    directory, so as to not disable the parent repo's .git dir (assuming you are in the parent 
    repo's root dir when running this command), run this:

        git disable-repos --true  # disable all git repos in this dir and below
        mv ..git .git             # re-enable just the parent repo

    Be sure to do a dry run first for safety, to ensure it will do what you expect:

        git disable-repos --true_dryrun

 2. To recursively list all git repos within a given folder, run this command from within the 
    folder of interest:

        git disable-repos --list

 3. Assuming you tried to add a sub-repo to your main git repo previously, BEFORE you deleted or 
    renamed the sub-repo's .git dir to disable the sub-repo, this is the process to disable 
    the sub-repo, remove it from your main repo's tracking index, and now re-add it to your 
    main repo as a regular directory, including all of its sub-files and things:

    Description: remove sub-repo as a sub-repo, add it as a normal directory, and commit
    all of its files to your main repo:

    Minimum Set of Commands (just gets the job done without printing extra info.):

        git disable-repos --true  # disable all repos in this dir and below 
        mv ..git .git             # re-enable just the main repo
        # quit tracking the subrepo as a single file
        git rm --cached path/to/subrepo
        # start tracking the subrepo as a normal folder
        git add -A
        git commit

    Full Set of Commands (let's you see more info. during the process):
    
        git disable-repos --true  # disable all repos in this dir and below 
        mv ..git .git             # re-enable just the main repo
        git ls-files path/to/subrepo  # see what is currently tracked in the subrepo dir 
        # quit tracking the subrepo as a single file
        git rm --cached path/to/subrepo
        git status
        # start tracking the subrepo as a normal folder
        git add -A
        git status
        git commit


Long Description: 
I want to archive a bunch of small git repos inside a single, larger repo, which I will back up on 
GitHub until I have time to manually pull out each small, nested repo into its own stand-alone
GitHub repo. To do this, however, 'git' in the outer, parent repo must NOT KNOW that the inner
git repos are git repos! The easiest way to do this is to just rename all inner, nested '.git' 
folders to anything else, such as to '..git', so that git won't recognize them as stand-alone
repositories, and so that it will just treat their contents like any other normal directory
and allow you to back it all up! Thus, this project is born. It will allow you to quickly
toggle the naming of any folder from '.git' to '..git', or vice versa. Hence the name of this
project: git-disable-repos. 
See my answer here: 
https://dev59.com/WVYN5IYBdhLWcg3wuaJx#62368415

This program is part of: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles

其他更复杂的工具:

对于寻求更“专业”解决方案的人来说,以下是最受欢迎的解决方案,按照最受欢迎(似乎也是最受支持?)的顺序排列:

  1. git submodule - https://git-scm.com/docs/git-submodule - 这是内置在 git 中的官方支持工具。
  2. git subtree - https://www.atlassian.com/git/tutorials/git-subtree
  3. git subrepo - https://github.com/ingydotnet/git-subrepo

哪个是最好的?我无法说,但它们对我来说都很令人困惑,所以在这种情况下,我选择了上面描述的手动、蛮力选项,因为这样最符合我的预期目的,直到有一天我能找到时间将每个子仓库拆分成自己单独维护的仓库放在GitHub上。

关于 git submodule 的更多信息:

2023年9月2日更新:我现在已经使用了几年的git submodule。要了解基本命令和其他内容,请参阅我在eRCaGuy_dotfiles repo here中标题为"Git submodules and Git LFS: how to clone this repo and all git submodules and git lfs files"的部分。(另外,不要使用git lfs。请参阅我的question hereanswer here,两者都包含了解释和原因。)

2020年9月21日更新:马丁·欧文(Martin Owen)在2016年5月的一篇文章("Git Submodules vs Git Subtrees")中对git submodulegit subtree进行了很好的比较,并普遍偏向于git submodule。然而,当时作者甚至不知道git subrepo的存在,并且除了在评论中提到它时没有提及。 git submodule似乎是内置在git中的官方支持工具。虽然它看起来确实有一定的学习曲线,但我计划在我的下一个项目中使用它,现在我准备开放该项目并开始工作,而且它依赖于子git仓库。我打算从这里开始学习它:
Atlassian's Bitbucket提供了一个简短的介绍:https://www.atlassian.com/git/tutorials/git-submodule 这里是官方的git submodule文档:https://git-scm.com/book/en/v2/Git-Tools-Submodules 另外,你还可以参考以下内容:
1. 我在如何更新存储库中的所有git子模块(两种方法实现两个非常不同的功能!)上的回答。
附加参考资料:
  1. https://medium.com/@porteneuve/mastering-git-subtrees-943d29a798ec
  2. 何时使用git subtree?
  3. https://webmasters.stackexchange.com/questions/84378/how-can-i-create-a-git-repo-that-contains-several-other-git-repos
  4. Git如何将嵌套的git仓库视为普通文件/文件夹处理?
  5. Git:如何使外部仓库和嵌入式仓库作为常规/独立的仓库工作?
  6. https://www.atlassian.com/git/tutorials/git-subtree

关键词:git添加子仓库;git添加子目录;git添加嵌套仓库;git添加.git文件夹和文件


1
你好,我非常喜欢这个答案,而且我的使用情况也类似,但是我无法使用你的脚本。 当我运行命令 git disable-repos --true_dryrun 时,我会收到一个 disable-repos 不是 git 命令 的错误提示。 - hargun3045
@hargun3045,你首先遵循了安装说明吗?如果是这样,并且这是第一次创建 ~/bin 目录,你必须重新加载你的 ~/.bashrc 文件,使用 . ~/.bashrc 命令,或者注销并重新登录以使其生效。然后再尝试该命令。 - Gabriel Staples
这是为了让 ~/.profile 将这个新的 ~/bin 目录添加到你的可执行路径 PATH 中,如此所示:https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/0c4682cfb96826dc524c5f8c9bdce155f3391742/home/.profile#L36。 - Gabriel Staples

4

我从那个特定的文件夹中删除了.git。然后我运行了这个命令。

git add folder_which_has_deleted_dot_git
git commit -m "Changed to standalone repo"

然后之后,我能够追踪该文件夹并将其转换为一个常规的/独立的存储库。


4
如果您不关心 B A 正在使用的确切版本,您可以保留当前设置(嵌套 git 存储库)。除了“嵌入式存储库”警告之外,两个存储库都将按照您的期望行为进行操作,每个存储库仅添加、提交和推送其自己的存储库。
注意:您可以使用 git config advice.addEmbeddedRepo 使该警告更短或为空。

4
使用git version 2.12.2.windows.2版本时,默认设置类似于使用子模块。您可以查看我问题中的示例存储库,或尝试克隆https://github.com/eromoe/test,您会发现test2存储库下的文件未被添加到test存储库中。 - Mithril
@Mithril 是的,这就是它应该工作的方式:A 应该忽略嵌套的 B 存储库。 - VonC
在你的测试仓库中,你所记录的只是另一个仓库的 git 链接(SHA1),而没有记录它的 URL。请参考我之前的回答 https://stackoverflow.com/a/35334520/6309。 - VonC
@Mithril 那仍然意味着默认情况下,A 和 B 将作为独立的仓库: 你可以忽略那个 gitlink 记录。(或者将 test2 加入到你的测试 .gitignore 文件中,在这种情况下,test2 不会甚至出现在 test 中) - VonC

3
为了更详细地解释rost shan的答案,请参考这里
我在Ubuntu 20.04上开发Rails应用程序时遇到了这个问题。
当我运行命令git add .时,会出现以下错误:
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint: 
hint:   git submodule add <url> letsencrypt_cred
hint: 
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint: 
hint:   git rm --cached letsencrypt_cred
hint: 
hint: See "git help submodule" for more information.

以下是我如何解决这个问题的方法:

取消Git中已经为我要推送的仓库暂存的所有文件:

git rm --cached letsencrypt_cred

或者

git rm -f --cached letsencrypt_cred (to force removal)

提交所有存储在当前目录下的文件:
git commit -m "modify credentials"

添加您想要推送到的远程仓库:

git remote add origin https://github.com/promisepreston/letsencrypt_cred.git

将文件推送到远程代码库

git push -u origin main

或者

git push -u origin master

That's all.

I hope this helps


2

您在一个git仓库中添加了另一个git仓库。

外部git仓库将忽略内部git仓库。

内部git仓库被称为子模块。

假设您有以下文件和目录

project 
   |- file1.html
   |- file2.css
   |- resources
       |- info1.json
       |- info2.json

如果您在项目目录中创建了一个git仓库,并且在资源目录中有一个git仓库,那么资源目录中的所有文件都将被项目目录中的git仓库忽略。
一个嵌套在另一个git仓库中的git仓库被称为子模块。换句话说,一个带有git仓库的目录位于另一个目录中,该目录也具有git仓库。子模块不必在直接子目录中,它可以在更高的一级、两级或更多级别上。
有时这是您想要做的事情,但如果您想要收集来自多个目录的文件并将其放入一个仓库中,则只需要一个git仓库即可覆盖整个项目。因此,子模块不是您想要发生的事情。
当创建git仓库时,它会创建一个名为.git的隐藏目录,这就是git工具知道它正在使用git仓库的方式。关于您过去版本的代码、GitHub远程位置等所有信息都存储在这个.git目录中的文件中。
如果您在项目目录中有一个git仓库,并且在资源目录中有另一个git仓库,那么您的文件系统实际上会像这样:
 project 
   |- .git
   |- file1.html
   |- file2.css
   |- resources
       |- .git
       |- info1.json
       |- info2.json

如果您从项目目录添加和提交文件,您将在已添加和提交的文件中看到资源目录的条目,但info1.json和info2.json文件将不会被添加。
如果您希望项目目录具有包含所有内容的一个git存储库,请按照以下步骤操作。
在资源目录(内部带有git存储库的目录)中:删除.git文件夹。您需要启用隐藏文件才能在资源管理器/查找器中看到它。
使用命令提示符或git bash(Windows)或终端(Mac,Linux)并导航到项目目录。您需要在子模块上面的目录中 - 因此在此示例中,项目目录(外部目录,应包含所有文件)。然后运行以下命令
git rm --cached resources
但用自己的目录名替换资源。--cached部分非常重要,如果您遗漏它,它将不可逆地删除您的资源目录!
现在,您应该能够使用git add命令将资源目录中的所有文件添加到主项目的存储库中,并提交这些文件。

1
这些都是很好的解决方案,但如果像我一样,你只是想将一堆东西推到github作为备份(我的ubuntu虚拟机坏了,我只能从终端访问东西),那么一个更简单的、在许多情况下足够好用的解决方案是将你的文件打包。如果你有7zip或类似的软件,一个简单的7z a backup.7z *就可以解决问题。然后你可以添加、提交并推送那个backup.7z文件。

1
我想把另一个仓库仅作为一个带有一堆文件的目录/模块添加进来,所以我只是删除了另一个仓库内的.git 文件,然后它就像魔法一样运行了!
根据刘玛丽娜的答案中的片段进行修改:
# In local test repo
rm -rf test2
git clone https://github.com/eromoe/test2

cd test2
rm -rf .git
cd ..

git add test2/
git commit -m "add files from test2 repo to test repo"
git push origin main

免责声明:我不是 git 专家。上述解决方案适用于我的情况,因此我发布了它。如果这个解决方案在存储库中引入了任何不良影响,请在下面留言。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接