我可以 return 到单个子模块中的先前提交吗?
Can I return to previous commit in a single submodule?
我想将我的应用程序设置到一些服务中,但都在一个存储库中。所以我想为每项服务添加一个子模块(目前我只有两个)。所以我的项目层次结构是:
- root
|--rootDoc.txt
|--.git
|
|---- sub1
|--sub1.txt
|--.git
|---- sub2
|--sub2.txt
|--.git
现在我做了以下更改:
- 改变
sub1.txt
- 提交
sub1
子模块
- 将 main 推送到 master
- 改变
sub2.txt
- 提交
sub2
子模块
- 将 main 推送到 master
现在我想 return sub1
-submodule 到最后一次更改之前的状态,但保持 sub2
在其当前状态。如果子模块无法做到这一点,我的问题是否有其他解决方案,或者我是否需要使用两个完全不同的存储库?
编辑:
我尝试了什么:
c:\dev\root\sub1>git log
commit a172db9a5f11738383d28e082db2c22d3f2d3e75 (HEAD -> master, origin/master, origin/HEAD)
Author: %me%
Date: Sun Dec 2 20:24:59 2018 +0100
updated sub2
commit 0becb718a4db9c73b03fa65e332f20c7715463cb
Author: %me%
Date: Sun Dec 2 20:23:40 2018 +0100
sub1 actual now
commit 85d68703bff1af2b95a7ee8d7926d7fd700b1da4
Author: %me%
Date: Sun Dec 2 20:10:50 2018 +0100
Added submodules
commit b3b67de3e54f1db7e56d516af2baaf50541f7ca2
Author: %me%
Date: Sun Dec 2 20:05:44 2018 +0100
initial commit
c:\dev\root\sub1>git checkout 85d68703bff1af2b95a7ee8d7926d7fd700b1da4
Note: checking out '85d68703bff1af2b95a7ee8d7926d7fd700b1da4'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 85d6870 Added submodules
检出后,我的 sub2
也发生了变化,尽管我是从 sub1
-dir(另一个子模块所在的位置)检出的。
您可以按照您的标题问题 ("return to previous commit in a single submodule") 进行操作。每个子模块本身就是一个独立的存储库。不清楚的是你实际做了什么。我怀疑您已经创建了 一个 存储库,其中包含多个 sub-directories,也许另外两个存储库位于一个存储库下,但 不是子模块 .
值得回到这里定义一些术语。我对 Git 的术语并不感兴趣("submodule" 和 "superproject" 有点笨拙)但我会坚持使用它们。
一个子模块是一个Git存储库。
一个超级项目是一个Git存储库。
显然这没什么用,所以让我们添加一些限定符:
A 子模块 是一个 Git 存储库,当前正被另一个 Git 存储库使用,我们称之为 超级项目。这个子模块 Git 仓库只有一个超级项目。
A superproject 是一个 Git 存储库,当前正在使用另一个 Git 存储库作为子模块。这个超级项目中可能有多个子模块。
(这导致某些 Git 存储库同时是子模块 和 超级项目的可能性。这有点像噩梦,你应该尽量避免它,但它确实发生了。)
现在,当超级项目对超级项目用作子模块的另一个 Git 存储库提出要求时,方式 超级项目 Git 会执行此操作至少通常是命令子模块 Git 进入 detached HEAD 模式。 任何 Git 存储库都可以处于这种状态,但大多数普通存储库都不会处于这种状态,除非您处于长期变基过程中,或者正在使用 git checkout <em>commit-or-tag</em>
移动到特定的历史提交。通常,在开发时,你在 master
或 develop
这样的分支上,这与 "detached HEAD" 相对:这里名称 HEAD
被形象地附加到分支名称上.所以 git checkout master
将你的 HEAD
附加到 master
,git checkout develop
将你的 HEAD
附加到开发。
(HEAD
,写成all-capitals这样,总是——总是——指的是当前Git仓库中的当前提交. 其底层实现是 .git
存放存储库的目录中有一个名为 HEAD
的文件。这个 .git/HEAD
文件要么包含一个 分支名称,在这种情况下你在那个分支上,或者它包含一个 提交哈希 ID,在这种情况下你在那个提交上有一个分离的 HEAD。因为 Git将其存储在文件中,在 Windows 和 MacOS 上可以在所有 lower-case 中使用 head
,但最好坚持使用 all-capitals 版本。如果你想要一个快捷方式这更容易输入,@
本身也意味着 HEAD
。)
当您想使用常规存储库时,在您通过克隆存储库(而不是从头创建它)开始的系统中,您可以这样做:
git clone <url> [<directory>]
例如,git clone https://github.com/git/git.git
通过 GitHub 为 Git 克隆 Git 存储库。无论您现在身在何处,都会创建一个 git
目录。如果出于某种原因您希望将克隆放在 /tmp/git
中,您可以使用 git clone https://github.com/git/git.git /tmp/git
。因此,Git 需要两个关键项才能进行克隆:
- 一个URL,和
- 克隆应该去的路径。
URL 通常是 https://
或 ssh://
样式 URL,列出一些上游主机/服务器(或 cloud-system 例如 GitHub) 和该主机/服务器上的路径。 (请注意 git@github.com:path/to/repo.git
只是 shorthand 对于 ssh://git@github.com/path/to/repo.git
。两者的意思完全相同。)
将子模块添加到现有存储库的过程大致相同:
git submodule add <url> [<directory>]
此处的url
通常也通常以https://
或ssh://
开头。 <directory>
是 在 存储库中的路径,即放置子模块的地方。
URL-and-path 的原因是 git submodule add
实际上会 运行 git clone
为您服务。它制作的克隆将是一个普通的 Git 存储库,因为子模块 是 一个普通的 Git 存储库。 Git 只需要知道我从哪里得到克隆 和我应该把它放在这个存储库的什么地方.
git submodule add
会做的另一件事 — 使您的 current Git 存储库充当 超级项目的额外部分 到那个子模块——是创建或更新一个名为 .gitmodules
的文件,并在你的超级项目的索引中添加一个条目。
请注意,子项目不必知道它的超级项目,在糟糕的过去,真的对此一无所知。 (在现代Git 子项目的 .git
目录被迁移到超级项目的 .git
目录中。将在子模块中找到的 .git
替换为 文件 ,该文件将子模块指向其超级项目的保存区域。)
无论如何,所有这一切的副作用是 子模块中的提交集 仅由子模块的内容决定。超级工程对它没有影响!子模块只是一些现有 URL.
的克隆
这不是您尝试使用子模块的方式,但在我们开始之前,让我们看看所有这些正常操作的其余部分。我们有一些超级项目——一个本地 Git 存储库,可能是某个 origin
存储库的克隆——我们在其中进行超级项目提交。在这个超级项目中,我们现在创建了一个名为 .gitmodules
的文件,它给出了 URL 和 另一个 Git 存储库的路径。假设路径是 dir/sub
。如果我们 运行:
cd dir/sub
我们发现我们现在处于 单独的 克隆的 work-tree 中,它有 自己的 origin/master
和 master
等等;但是这个克隆有一个分离的 HEAD。 运行 git log
显示 detached-HEAD 提交,然后是它的 parent(s) 和它们的 parent(s) 等等,就好像历史在任何地方结束一样提交我们作为分离的 HEAD。这是我们的子模块 Git 存储库。
如果我们cd
备份到原始存储库:
cd - # or cd ../..
我们回到了主存储库。使用普通的文件系统工具向我们显示 dir/sub
现在存在并且是一个目录。有一个名为 dir/sub/.git
的文件(或者如果您的 Git 较旧,则为目录)。如果它是一个文件,它包含一行内容:
gitdir: ../../.git/modules/sub
运行 git status
显示两个添加的文件:
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: dir/sub
但是检查 index——这有点棘手;我将在这里使用 git ls-files
——表明 dir/sub
根本不是 目录 :
$ git ls-files --stage dir/sub
160000 50298bbf97b317f17b3e1cf9287e912fb5de886e 0 dir/sub
模式为 160000
的条目 Git 称为 gitlink.
如果你知道dir/sub
是一个gitlink,你可以使用git rev-parse
更直接地查看它的哈希ID。语法 :0:dir/sub
表示 "dir/sub from the index (at slot zero)":
$ git rev-parse :0:dir/sub
50298bbf97b317f17b3e1cf9287e912fb5de886e
这些告诉我们同样的事情,除了如果 dir/sub
不是 子模块,我们将能够在 git ls-files --stage
中看到它输出。
这就是 Git 通常设想的子模块用法
这里的一般想法是,在您的超级项目中,您使用某种您个人无法以任何方式控制的 third-party 库(例如,Google gRPC)。相反,您编写软件并使其与该库的一个特定版本一起工作:
$ (cd dir/sub; git checkout v3.2.1)
通过检查子模块中的某些特定标记,您将分离的 HEAD 移动 到该特定提交。然后,您对自己的项目(您的超级项目)进行任何必要的更改,以使其在 和 v3.2.1 或以下任何版本中工作:
$ ... make some changes ...
$ git add ... files ...
现在更新了你的文件,你现在还更新了gitlink条目你的超级项目Git应该git checkout
你现在在你的子模块中的一个特定提交:
$ git add dir/sub # update the gitlink to whatever hash v3.2.1 represents
现在,当您进行 new 提交时,超级项目提交会继续列出其他存储库 - 及其 URL,无论是什么,以及它的路径,dir/sub
——在你的.gitmodules
、和中,同一个提交声明:这个提交与分离到.
因此,每当某人 运行s git clone
在您的超级项目上,然后对 那个特定的超级项目提交 git checkout
时,一个后续:
$ git submodule update
将确保 dir/sub
已 签出特定的 gitlink-ed 提交 ,作为分离的 HEAD。现在您的超级项目和子模块已同步,您可以构建了。
这不是您尝试使用子模块的方式
对于您的情况,您已经拥有子模块Git 存储库。他们可能有也可能没有合适的上游存储库。它们存在于 sub1
和 sub2
。不过,我将再次使用 dir/sub
作为示例:
$ git submodule add ./dir/sub dir/sub
Adding existing repo at 'dir/sub' to the index
这里的 URL,./dir/sub
,对其他人来说毫无用处。 (它必须以 ./
或 ../
开头才能相对于当前工作目录——Git 拒绝添加没有前导 ./
的子模块。)
此时,与普通 URL 发生的事情相同:Git 已创建或更新您的 .gitmodules
以列出 URL 和路径:
$ cat .gitmodules
[submodule "dir/sub"]
path = dir/sub
url = ./dir/sub
并将子模块HEAD
对应的hash ID放入索引作为下一个committed gitlink entry:
$ (cd dir/sub; git rev-parse HEAD)
1fdcf14961c81d03496b359389058410f0169782
$ git rev-parse :0:dir/sub
1fdcf14961c81d03496b359389058410f0169782
$ git status --short
A .gitmodules
A dir/sub
因此,如果您此时进行新提交,新提交将具有 .gitmodules
和使此 Git 存储库尝试管理或克隆(如果丢失)另一个 Git 存储库到 dir/sub
所需的索引条目,基于 URL ./dir/sub
.
这个URL当然是完全没用的,除非已经一个Git存储库在dir/sub
,但这就是我们告诉这个Git 它是位于 dir/sub
的另一个 Git 存储库的超级项目。您可以以这种方式使用Git,只要您已经另一个Git存储库位于dir/sub
,你的超级项目 Git 会同意并会指挥它。您的超级项目 Git 将向子模块 Git 发出的命令是:检查这个特定的提交,作为一个分离的 HEAD。
超级项目如何看到子模块的变化
假设您进入子模块并使用 git checkout
签出,甚至创建一些 other 提交,也许通过对某些 git checkout
分支名称,然后可能像往常一样在存储库中工作并提交。然后你 cd
回到超级项目并且 运行 git status
。您的 Git 会告诉您子模块已修改(请注意此处的 M
之前的空白 :
$ git status --short
M dir/sub
此修改存在,但尚未在您的 index 中,即尚未设置为提交:
$ (cd dir/sub; git rev-parse HEAD)
860be47095f79afbf94c62d0c3936a9875905e16
$ git rev-parse :0:dir/sub
1fdcf14961c81d03496b359389058410f0169782
如您所见,子模块 是 在 860be47095f79afbf94c62d0c3936a9875905e16
分离的,即使索引表明下一次提交将包含使用 [=108= 的指令]. **这与同一存储库中的任何修改后的文件完全一样,* 除了您在此处使用 git add
来告诉 Git:将新的哈希 ID 放入 而不是而不是告诉它 复制 work-tree 中的内容 .
因此,一旦我们执行 git add
,--short
状态输出会将 M
向左移动一个字母:
$ git add dir/sub
$ git status --short
M dir/sub
因为现在超级项目的子模块索引条目不同于该子模块的 HEAD
值,但与 work-tree 中找到的实际子模块匹配。所以现在,如果一切准备就绪,我们想告诉我们的超级项目 Git 命令子模块 Git 在我们进行的下一次提交中使用 860be47095f79afbf94c62d0c3936a9875905e16
,我们准备好进行该提交现在:
$ git commit
[edit a message, etc]
同样,这里的关键是:
- 每个子模块都是自己的 Git 存储库。
- 超级项目根据需要通过路径 and/or 和
.gitmodules
查找每个子模块。 just 超级项目的新克隆显然还没有克隆任何子模块,所以这就是 .gitmodules
条目的好处:它们提供 URL和路径!
- 超级项目 Git 可以查看每个子模块并找到其
HEAD
: 获取超级项目 Git 实际哈希 ID,并让您 git add
该哈希超级项目索引的 ID,为您在超级项目中进行的 下一个 提交做好准备。
- 或者,超级项目 Git 可以命令子模块 Git 到
git checkout
,作为分离的 HEAD, 中的一个特定提交哈希 ID superproject 现在的索引。
如果你想让你的超级项目命令多个子模块,你 git submodule add
所有这些子模块。为了确保这些子模块获得正确的提交哈希 ID checked-out 作为分离的 HEAD,您 输入 子模块, 将 它们放在右侧提交,然后 git add
超级项目中的子模块。
在现代 Git 中,git submodule
命令有一些 fairly-fancy 技巧来使用 remote 中找到的分支名称来协调更新子模块( origin
,通常)用于子模块。这里的想法是,如果你正在使用,比如说,Google gRPC,并且你想升级,git submodule
可以替换上面的几个步骤——cd
-ing 进入子模块,运行ning git fetch
,运行ning git checkout
,cd
-ing 返回——一步。但是子模块的实际设计仍然是 "detached HEAD as commanded by superproject":确保超级项目 Git 存储库记录正确的子模块哈希 ID 取决于您。
我想将我的应用程序设置到一些服务中,但都在一个存储库中。所以我想为每项服务添加一个子模块(目前我只有两个)。所以我的项目层次结构是:
- root
|--rootDoc.txt
|--.git
|
|---- sub1
|--sub1.txt
|--.git
|---- sub2
|--sub2.txt
|--.git
现在我做了以下更改:
- 改变
sub1.txt
- 提交
sub1
子模块 - 将 main 推送到 master
- 改变
sub2.txt
- 提交
sub2
子模块 - 将 main 推送到 master
现在我想 return sub1
-submodule 到最后一次更改之前的状态,但保持 sub2
在其当前状态。如果子模块无法做到这一点,我的问题是否有其他解决方案,或者我是否需要使用两个完全不同的存储库?
编辑: 我尝试了什么:
c:\dev\root\sub1>git log
commit a172db9a5f11738383d28e082db2c22d3f2d3e75 (HEAD -> master, origin/master, origin/HEAD)
Author: %me%
Date: Sun Dec 2 20:24:59 2018 +0100
updated sub2
commit 0becb718a4db9c73b03fa65e332f20c7715463cb
Author: %me%
Date: Sun Dec 2 20:23:40 2018 +0100
sub1 actual now
commit 85d68703bff1af2b95a7ee8d7926d7fd700b1da4
Author: %me%
Date: Sun Dec 2 20:10:50 2018 +0100
Added submodules
commit b3b67de3e54f1db7e56d516af2baaf50541f7ca2
Author: %me%
Date: Sun Dec 2 20:05:44 2018 +0100
initial commit
c:\dev\root\sub1>git checkout 85d68703bff1af2b95a7ee8d7926d7fd700b1da4
Note: checking out '85d68703bff1af2b95a7ee8d7926d7fd700b1da4'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 85d6870 Added submodules
检出后,我的 sub2
也发生了变化,尽管我是从 sub1
-dir(另一个子模块所在的位置)检出的。
您可以按照您的标题问题 ("return to previous commit in a single submodule") 进行操作。每个子模块本身就是一个独立的存储库。不清楚的是你实际做了什么。我怀疑您已经创建了 一个 存储库,其中包含多个 sub-directories,也许另外两个存储库位于一个存储库下,但 不是子模块 .
值得回到这里定义一些术语。我对 Git 的术语并不感兴趣("submodule" 和 "superproject" 有点笨拙)但我会坚持使用它们。
一个子模块是一个Git存储库。
一个超级项目是一个Git存储库。
显然这没什么用,所以让我们添加一些限定符:
A 子模块 是一个 Git 存储库,当前正被另一个 Git 存储库使用,我们称之为 超级项目。这个子模块 Git 仓库只有一个超级项目。
A superproject 是一个 Git 存储库,当前正在使用另一个 Git 存储库作为子模块。这个超级项目中可能有多个子模块。
(这导致某些 Git 存储库同时是子模块 和 超级项目的可能性。这有点像噩梦,你应该尽量避免它,但它确实发生了。)
现在,当超级项目对超级项目用作子模块的另一个 Git 存储库提出要求时,方式 超级项目 Git 会执行此操作至少通常是命令子模块 Git 进入 detached HEAD 模式。 任何 Git 存储库都可以处于这种状态,但大多数普通存储库都不会处于这种状态,除非您处于长期变基过程中,或者正在使用 git checkout <em>commit-or-tag</em>
移动到特定的历史提交。通常,在开发时,你在 master
或 develop
这样的分支上,这与 "detached HEAD" 相对:这里名称 HEAD
被形象地附加到分支名称上.所以 git checkout master
将你的 HEAD
附加到 master
,git checkout develop
将你的 HEAD
附加到开发。
(HEAD
,写成all-capitals这样,总是——总是——指的是当前Git仓库中的当前提交. 其底层实现是 .git
存放存储库的目录中有一个名为 HEAD
的文件。这个 .git/HEAD
文件要么包含一个 分支名称,在这种情况下你在那个分支上,或者它包含一个 提交哈希 ID,在这种情况下你在那个提交上有一个分离的 HEAD。因为 Git将其存储在文件中,在 Windows 和 MacOS 上可以在所有 lower-case 中使用 head
,但最好坚持使用 all-capitals 版本。如果你想要一个快捷方式这更容易输入,@
本身也意味着 HEAD
。)
当您想使用常规存储库时,在您通过克隆存储库(而不是从头创建它)开始的系统中,您可以这样做:
git clone <url> [<directory>]
例如,git clone https://github.com/git/git.git
通过 GitHub 为 Git 克隆 Git 存储库。无论您现在身在何处,都会创建一个 git
目录。如果出于某种原因您希望将克隆放在 /tmp/git
中,您可以使用 git clone https://github.com/git/git.git /tmp/git
。因此,Git 需要两个关键项才能进行克隆:
- 一个URL,和
- 克隆应该去的路径。
URL 通常是 https://
或 ssh://
样式 URL,列出一些上游主机/服务器(或 cloud-system 例如 GitHub) 和该主机/服务器上的路径。 (请注意 git@github.com:path/to/repo.git
只是 shorthand 对于 ssh://git@github.com/path/to/repo.git
。两者的意思完全相同。)
将子模块添加到现有存储库的过程大致相同:
git submodule add <url> [<directory>]
此处的url
通常也通常以https://
或ssh://
开头。 <directory>
是 在 存储库中的路径,即放置子模块的地方。
URL-and-path 的原因是 git submodule add
实际上会 运行 git clone
为您服务。它制作的克隆将是一个普通的 Git 存储库,因为子模块 是 一个普通的 Git 存储库。 Git 只需要知道我从哪里得到克隆 和我应该把它放在这个存储库的什么地方.
git submodule add
会做的另一件事 — 使您的 current Git 存储库充当 超级项目的额外部分 到那个子模块——是创建或更新一个名为 .gitmodules
的文件,并在你的超级项目的索引中添加一个条目。
请注意,子项目不必知道它的超级项目,在糟糕的过去,真的对此一无所知。 (在现代Git 子项目的 .git
目录被迁移到超级项目的 .git
目录中。将在子模块中找到的 .git
替换为 文件 ,该文件将子模块指向其超级项目的保存区域。)
无论如何,所有这一切的副作用是 子模块中的提交集 仅由子模块的内容决定。超级工程对它没有影响!子模块只是一些现有 URL.
的克隆这不是您尝试使用子模块的方式,但在我们开始之前,让我们看看所有这些正常操作的其余部分。我们有一些超级项目——一个本地 Git 存储库,可能是某个 origin
存储库的克隆——我们在其中进行超级项目提交。在这个超级项目中,我们现在创建了一个名为 .gitmodules
的文件,它给出了 URL 和 另一个 Git 存储库的路径。假设路径是 dir/sub
。如果我们 运行:
cd dir/sub
我们发现我们现在处于 单独的 克隆的 work-tree 中,它有 自己的 origin/master
和 master
等等;但是这个克隆有一个分离的 HEAD。 运行 git log
显示 detached-HEAD 提交,然后是它的 parent(s) 和它们的 parent(s) 等等,就好像历史在任何地方结束一样提交我们作为分离的 HEAD。这是我们的子模块 Git 存储库。
如果我们cd
备份到原始存储库:
cd - # or cd ../..
我们回到了主存储库。使用普通的文件系统工具向我们显示 dir/sub
现在存在并且是一个目录。有一个名为 dir/sub/.git
的文件(或者如果您的 Git 较旧,则为目录)。如果它是一个文件,它包含一行内容:
gitdir: ../../.git/modules/sub
运行 git status
显示两个添加的文件:
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: dir/sub
但是检查 index——这有点棘手;我将在这里使用 git ls-files
——表明 dir/sub
根本不是 目录 :
$ git ls-files --stage dir/sub
160000 50298bbf97b317f17b3e1cf9287e912fb5de886e 0 dir/sub
模式为 160000
的条目 Git 称为 gitlink.
如果你知道dir/sub
是一个gitlink,你可以使用git rev-parse
更直接地查看它的哈希ID。语法 :0:dir/sub
表示 "dir/sub from the index (at slot zero)":
$ git rev-parse :0:dir/sub
50298bbf97b317f17b3e1cf9287e912fb5de886e
这些告诉我们同样的事情,除了如果 dir/sub
不是 子模块,我们将能够在 git ls-files --stage
中看到它输出。
这就是 Git 通常设想的子模块用法
这里的一般想法是,在您的超级项目中,您使用某种您个人无法以任何方式控制的 third-party 库(例如,Google gRPC)。相反,您编写软件并使其与该库的一个特定版本一起工作:
$ (cd dir/sub; git checkout v3.2.1)
通过检查子模块中的某些特定标记,您将分离的 HEAD 移动 到该特定提交。然后,您对自己的项目(您的超级项目)进行任何必要的更改,以使其在 和 v3.2.1 或以下任何版本中工作:
$ ... make some changes ...
$ git add ... files ...
现在更新了你的文件,你现在还更新了gitlink条目你的超级项目Git应该git checkout
你现在在你的子模块中的一个特定提交:
$ git add dir/sub # update the gitlink to whatever hash v3.2.1 represents
现在,当您进行 new 提交时,超级项目提交会继续列出其他存储库 - 及其 URL,无论是什么,以及它的路径,dir/sub
——在你的.gitmodules
、和中,同一个提交声明:这个提交与分离到
因此,每当某人 运行s git clone
在您的超级项目上,然后对 那个特定的超级项目提交 git checkout
时,一个后续:
$ git submodule update
将确保 dir/sub
已 签出特定的 gitlink-ed 提交 ,作为分离的 HEAD。现在您的超级项目和子模块已同步,您可以构建了。
这不是您尝试使用子模块的方式
对于您的情况,您已经拥有子模块Git 存储库。他们可能有也可能没有合适的上游存储库。它们存在于 sub1
和 sub2
。不过,我将再次使用 dir/sub
作为示例:
$ git submodule add ./dir/sub dir/sub
Adding existing repo at 'dir/sub' to the index
这里的 URL,./dir/sub
,对其他人来说毫无用处。 (它必须以 ./
或 ../
开头才能相对于当前工作目录——Git 拒绝添加没有前导 ./
的子模块。)
此时,与普通 URL 发生的事情相同:Git 已创建或更新您的 .gitmodules
以列出 URL 和路径:
$ cat .gitmodules
[submodule "dir/sub"]
path = dir/sub
url = ./dir/sub
并将子模块HEAD
对应的hash ID放入索引作为下一个committed gitlink entry:
$ (cd dir/sub; git rev-parse HEAD)
1fdcf14961c81d03496b359389058410f0169782
$ git rev-parse :0:dir/sub
1fdcf14961c81d03496b359389058410f0169782
$ git status --short
A .gitmodules
A dir/sub
因此,如果您此时进行新提交,新提交将具有 .gitmodules
和使此 Git 存储库尝试管理或克隆(如果丢失)另一个 Git 存储库到 dir/sub
所需的索引条目,基于 URL ./dir/sub
.
这个URL当然是完全没用的,除非已经一个Git存储库在dir/sub
,但这就是我们告诉这个Git 它是位于 dir/sub
的另一个 Git 存储库的超级项目。您可以以这种方式使用Git,只要您已经另一个Git存储库位于dir/sub
,你的超级项目 Git 会同意并会指挥它。您的超级项目 Git 将向子模块 Git 发出的命令是:检查这个特定的提交,作为一个分离的 HEAD。
超级项目如何看到子模块的变化
假设您进入子模块并使用 git checkout
签出,甚至创建一些 other 提交,也许通过对某些 git checkout
分支名称,然后可能像往常一样在存储库中工作并提交。然后你 cd
回到超级项目并且 运行 git status
。您的 Git 会告诉您子模块已修改(请注意此处的 M
之前的空白 :
$ git status --short
M dir/sub
此修改存在,但尚未在您的 index 中,即尚未设置为提交:
$ (cd dir/sub; git rev-parse HEAD)
860be47095f79afbf94c62d0c3936a9875905e16
$ git rev-parse :0:dir/sub
1fdcf14961c81d03496b359389058410f0169782
如您所见,子模块 是 在 860be47095f79afbf94c62d0c3936a9875905e16
分离的,即使索引表明下一次提交将包含使用 [=108= 的指令]. **这与同一存储库中的任何修改后的文件完全一样,* 除了您在此处使用 git add
来告诉 Git:将新的哈希 ID 放入 而不是而不是告诉它 复制 work-tree 中的内容 .
因此,一旦我们执行 git add
,--short
状态输出会将 M
向左移动一个字母:
$ git add dir/sub
$ git status --short
M dir/sub
因为现在超级项目的子模块索引条目不同于该子模块的 HEAD
值,但与 work-tree 中找到的实际子模块匹配。所以现在,如果一切准备就绪,我们想告诉我们的超级项目 Git 命令子模块 Git 在我们进行的下一次提交中使用 860be47095f79afbf94c62d0c3936a9875905e16
,我们准备好进行该提交现在:
$ git commit
[edit a message, etc]
同样,这里的关键是:
- 每个子模块都是自己的 Git 存储库。
- 超级项目根据需要通过路径 and/or 和
.gitmodules
查找每个子模块。 just 超级项目的新克隆显然还没有克隆任何子模块,所以这就是.gitmodules
条目的好处:它们提供 URL和路径! - 超级项目 Git 可以查看每个子模块并找到其
HEAD
: 获取超级项目 Git 实际哈希 ID,并让您git add
该哈希超级项目索引的 ID,为您在超级项目中进行的 下一个 提交做好准备。 - 或者,超级项目 Git 可以命令子模块 Git 到
git checkout
,作为分离的 HEAD, 中的一个特定提交哈希 ID superproject 现在的索引。
如果你想让你的超级项目命令多个子模块,你 git submodule add
所有这些子模块。为了确保这些子模块获得正确的提交哈希 ID checked-out 作为分离的 HEAD,您 输入 子模块, 将 它们放在右侧提交,然后 git add
超级项目中的子模块。
在现代 Git 中,git submodule
命令有一些 fairly-fancy 技巧来使用 remote 中找到的分支名称来协调更新子模块( origin
,通常)用于子模块。这里的想法是,如果你正在使用,比如说,Google gRPC,并且你想升级,git submodule
可以替换上面的几个步骤——cd
-ing 进入子模块,运行ning git fetch
,运行ning git checkout
,cd
-ing 返回——一步。但是子模块的实际设计仍然是 "detached HEAD as commanded by superproject":确保超级项目 Git 存储库记录正确的子模块哈希 ID 取决于您。