Rüd*_*ger 1 git git-submodules
我想将我的应用程序设置为一些服务,但所有服务都在一个存储库中.所以我想为每个服务添加一个子模块(我目前只有两个子模块).所以我的项目层次结构是:
- root
|--rootDoc.txt
|--.git
|
|---- sub1
|--sub1.txt
|--.git
|---- sub2
|--sub2.txt
|--.git
Run Code Online (Sandbox Code Playgroud)
现在我做了以下更改:
sub1.txtsub1子模块sub2.txtsub2子模块现在我想将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
Run Code Online (Sandbox Code Playgroud)
在这次结账之后我sub2也被改变了,虽然我从sub1-dir(其他子模块所在的位置)检查了.
您可以在标题问题中执行操作("返回到单个子模块中的上一次提交").每个子模块本身都是一个独立的存储库.什么不清楚你实际做了什么.我怀疑你已经创建了一个包含多个子目录的存储库,也许还有另外两个存储库位于一个存储库下但不是子模块.
值得回到这里并定义一些术语.我对Git的术语并不感到兴奋("子模块"和"超级项目"有点笨拙),但我会坚持下去.
一个子模块是一个Git仓库.
一个子项目会是一个Git仓库.
显然这没什么用,所以让我们添加一些限定词:
一个子模块是当前正由另一Git仓库,我们称之为一个Git仓库上层项目.这个子模块Git repo只有一个超级项目.
一个上层项目是当前使用其他Git仓库的子模块一个Git仓库.此超级项目中可能有多个子模块.
(这导致某些Git存储库同时是子模块和超级项目的可能性.这有点像噩梦,你应该尽量避免它,但确实会发生.)
现在,当一个子项目会使得该上层项目使用的子模块,在另一个Git仓库需求的方式上层项目的Git这是否是-至少常命令子模块的Git进入分离的头模式. 任何 Git存储库都可以处于此状态,但大多数普通存储库都不是,除非您处于较长的rebase中间,或者正在使用移动到特定的历史提交.通常情况下,在开发时,你在一个分支上,或者,它与"分离的HEAD"相反:这里的名称以比喻名称的形式附加.因此,重视你来,并重视你的发展.git checkout commit-or-tagmasterdevelopHEADgit checkout masterHEADmastergit checkout developHEAD
(HEAD用这样的全部大写写,总是 - 总是指当前Git存储库中的当前提交.这个的底层实现是.git保存存储库的目录中有一个文件名HEAD.该.git/HEAD文件包含一个分公司名称,在这种情况下,你在那个分支,或者它包含一个提交哈希ID,在这种情况下,你必须在犯下一个分离的头.由于Git的将其存储在一个文件,它有可能在Windows和MacOS使用head的所有小写字母,但最好坚持使用全大写版本.如果你想要一个更容易输入的快捷方式,@它本身也意味着HEAD.)
如果要使用常规存储库,在通过克隆存储库(而不是从头开始创建存储库)开始的系统中,执行以下操作:
git clone <url> [<directory>]
Run Code Online (Sandbox Code Playgroud)
例如,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通常是一个https://或ssh://样式URL,列出一些上游主机/服务器(或云系统,如GitHub)和该主机/服务器上的路径.(注意这git@github.com:path/to/repo.git只是简写ssh://git@github.com/path/to/repo.git.这两个意思完全相同.)
将子模块添加到现有存储库的过程大致相同:
git submodule add <url> [<directory>]
Run Code Online (Sandbox Code Playgroud)
在url这里会也通常开始https://或ssh://.该<directory>是路径内你的资料库,即地方放子模块.
这个URL和路径的原因git submodule add实际上git clone将为您运行.它制作的克隆将是一个普通的Git存储库,因为子模块是一个普通的Git存储库.Git的只需要知道我在哪里得到的克隆和我应该在哪里把它放在这个仓库内.
另一件事git submodule add- 使当前 Git存储库充当该子模块的超级项目的额外部分- 是创建或更新名为的文件.gitmodules,并向超级项目的索引添加条目.
请注意,子项目不必了解其超级项目,并且在过去的糟糕时期,实际上并不了解它.(在现代的Git子项目的.git目录被迁移到上层项目的.git目录下,该.git会在子模块中找到被替换为文件指向的子模块的上层项目的存储区域.)
无论如何,所有这些的副作用是子模块中的提交集仅由子模块的内容决定.超级项目对它没有影响!子模块只是某些现有URL的克隆.
这不是你尝试使用子模块的方式,但在我们开始之前,让我们看看所有这些的正常操作的其余部分.我们有一些超级项目 - 一个本地Git存储库,可能是某些origin存储库的克隆- 我们在这里进行超级项目提交.在这个超级项目中,我们现在创建了一个名为的文件.gitmodules,它提供了另一个 Git存储库的URL和路径.让我们说道路是dir/sub.如果我们运行:
cd dir/sub
Run Code Online (Sandbox Code Playgroud)
我们发现,我们现在正处于一个工作树分离克隆,有其自身的 origin/master和master等; 但是这个克隆有一个分离的HEAD.运行git log显示分离的HEAD提交,然后显示其父代和它们的父代等等,就像历史记录以我们作为分离的HEAD的任何提交结束一样.这是我们的子模块Git存储库.
如果我们cd备份到原始存储库:
cd - # or cd ../..
Run Code Online (Sandbox Code Playgroud)
我们回到主存储库.使用普通的文件系统工具向我们展示了dir/sub现在存在的并且是一个目录.有一个文件(或者如果你的Git是旧的,一个指针)命名dir/sub/.git.如果它是一个文件,它包含一行读数:
gitdir: ../../.git/modules/sub
Run Code Online (Sandbox Code Playgroud)
运行git status显示两个添加的文件:
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: dir/sub
Run Code Online (Sandbox Code Playgroud)
但检查指数 - 这有点棘手; 我将git ls-files在这里使用- 显示dir/sub根本不是一个目录:
$ git ls-files --stage dir/sub
160000 50298bbf97b317f17b3e1cf9287e912fb5de886e 0 dir/sub
Run Code Online (Sandbox Code Playgroud)
具有模式的条目160000是Git称之为gitlink的.
如果您知道这dir/sub是一个gitlink,您可以使用更直接地查看其哈希ID git rev-parse.语法:0:dir/sub表示"索引中的dir/sub(在插槽0处)":
$ git rev-parse :0:dir/sub
50298bbf97b317f17b3e1cf9287e912fb5de886e
Run Code Online (Sandbox Code Playgroud)
这些告诉我们同样的事情,除了如果dir/sub 不是子模块,我们将能够在git ls-files --stage输出中看到它.
这里的一般想法是,在您的超级项目中,您使用某种您个人无法以任何方式控制的第三方库(例如Google gRPC).相反,您编写软件并使其与该库的特定版本一起使用:
$ (cd dir/sub; git checkout v3.2.1)
Run Code Online (Sandbox Code Playgroud)
通过检出子模块中的某个特定标记,可以将分离的HEAD 移动到该特定提交.然后,您需要对您自己的项目 - 您的超级项目进行任何更改 - 使其适用于 v3.2.1或任何版本:
$ ... make some changes ...
$ git add ... files ...
Run Code Online (Sandbox Code Playgroud)
现在已经更新您的文件,你现在还更新gitlink条目,说你上层项目的Git应该git checkout在一个特定的承诺是你现在所拥有的在你的子模块:
$ git add dir/sub # update the gitlink to whatever hash v3.2.1 represents
Run Code Online (Sandbox Code Playgroud)
现在,当您进行新的提交时,超级项目提交将继续列出另一个存储库 - 包含其URL,无论是什么,以及它的路径,dir/sub-in .gitmodules,以及同一个提交声明:此提交适用于分离到<gitlink的子模块子模块哈希>.
所以,每当有人运行git clone你的上层项目,然后做了git checkout的那个特定的子项目会犯,后续:
$ git submodule update
Run Code Online (Sandbox Code Playgroud)
将确保dir/sub具有特定gitlink-ED提交签出,作为分离的头.现在你的超级项目和子模块是同步的,你可以构建.
在您的情况下,您已经拥有子模块Git存储库.它们可能有也可能没有合适的上游存储库.它们存在于sub1和sub2.作为我的例子,我将dir/sub再次使用:
$ git submodule add ./dir/sub dir/sub
Adding existing repo at 'dir/sub' to the index
Run Code Online (Sandbox Code Playgroud)
这里的URL ./dir/sub对其他人来说都是无用的.(它必须从当前工作目录开始./或../相对于当前工作目录 - Git拒绝添加没有前导的子模块./.)
此时,与普通URL相同的事情发生了:Git创建或更新了您.gitmodules的列表URL和路径:
$ cat .gitmodules
[submodule "dir/sub"]
path = dir/sub
url = ./dir/sub
Run Code Online (Sandbox Code Playgroud)
并将与子模块对应的哈希ID HEAD放入索引中,作为下一个提交的gitlink条目:
$ (cd dir/sub; git rev-parse HEAD)
1fdcf14961c81d03496b359389058410f0169782
$ git rev-parse :0:dir/sub
1fdcf14961c81d03496b359389058410f0169782
$ git status --short
A .gitmodules
A dir/sub
Run Code Online (Sandbox Code Playgroud)
因此,如果此时您现在进行新的提交,则新提交将具有.gitmodules使Git存储库尝试管理或克隆所需的和索引条目(如果缺少 - dir/sub基于URL 的其他Git存储库)./dir/sub.
这个URL当然完全没用,除非已经有一个Git存储库dir/sub,但这就是我们如何告诉这个Git它是另一个Git存储库的超级项目dir/sub.你可以用这种方式使用Git,只要你已经有了另一个Git存储库dir/sub,你的超级项目Git就可以了,并命令它.您的超级项目Git将向子模块Git发出的命令是:将此一个特定提交签出,作为分离的HEAD.
假设您进入子模块并使用git checkout签出,甚至创建一些其他提交,可能通过执行git checkout一些分支名称,然后可能像往常一样在存储库中工作并提交.然后你cd回到超级项目并运行git status.你的Git会告诉你,子模块被修改(注意空白之前的M位置):
$ git status --short
M dir/sub
Run Code Online (Sandbox Code Playgroud)
此修改存在,但尚未在您的索引中,即尚未设置为提交:
$ (cd dir/sub; git rev-parse HEAD)
860be47095f79afbf94c62d0c3936a9875905e16
$ git rev-parse :0:dir/sub
1fdcf14961c81d03496b359389058410f0169782
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,即使索引表明下一个提交将包含要使用的指令,子模块也会被分离.**这与同一存储库中的任何已修改文件完全相同,*除了您在此使用以告诉Git:将新的哈希ID放入而不是告诉它复制工作树内容.860be47095f79afbf94c62d0c3936a9875905e161fdcf14961c81d03496b359389058410f0169782git add
因此,一旦我们这样做git add,--short状态输出将M向左移动一个字母:
$ git add dir/sub
$ git status --short
M dir/sub
Run Code Online (Sandbox Code Playgroud)
因为现在超级项目的子模块的索引条目与该子模块的HEAD值不同,但确实与工作树中的实际子模块匹配.所以现在,如果一切准备就绪,我们想告诉我们的超级项目Git命令子模块Git 860be47095f79afbf94c62d0c3936a9875905e16在我们下次提交时使用,我们现在准备进行提交:
$ git commit
[edit a message, etc]
Run Code Online (Sandbox Code Playgroud)
同样,这里的关键是:
.gitmodules根据需要按路径和/或按顺序查找每个子模块.只有超级项目的新克隆显然没有任何克隆的子模块,所以这些.gitmodules条目的优点是:它们提供了URL和路径!HEAD:它获取超级项目Git的实际哈希ID,并将git add该哈希ID提供给超级项目的索引,为您在超级项目中进行的下一次提交做好准备.git checkout作为分离的HEAD,现在是超级项目索引中的一个特定提交哈希ID .如果要使superproject命令成为多个子模块,那么就是git submodule add所有这些子模块.要确保这些子模块获得正确的提交哈希ID作为分离的HEAD签出,您可以输入子模块,将它们放在正确的提交中,然后git add输入超级项目中的子模块.
在现代Git中,该git submodule命令有一些相当花哨的技巧来协调使用远程(origin通常)为子模块中找到的分支名称更新子模块.这里的想法是,如果你是使用,比方说,谷歌GRPC,并且要升级,git submodule可以代替以上几个步骤-的cd-ing成子模块,跑git fetch,跑git checkout,和cd-ing回足下.但是子模块的实际设计仍然是"超级项目命令的超级HEAD":由您来确保超级项目Git存储库记录正确的子模块哈希ID.