通常,在处理一个项目时,您需要从其中使用另一个项目。也许这是第三方开发的库,或者您正在单独开发并在多个父项目中使用。在这些情况下会出现一个常见问题:您希望能够将两个项目视为独立的项目,但仍然能够在另一个项目中使用它们。
这是一个例子。假设您正在开发一个网站并创建Atom提要。您决定使用库而不是编写自己的Atom生成代码。您可能必须从共享库(例如CPAN安装或Ruby gem)中包含此代码,或者将源代码复制到您自己的项目树中。包含库的问题在于,以任何方式定制该库都很困难,并且部署它通常更加困难,因为您需要确保每个客户端都拥有该库。将代码复制到自己的项目中的问题是,当上游更改可用时,您所做的任何自定义更改都很难合并。
Git使用子模块解决了这个问题。子模块允许您将Git存储库保留为另一个Git存储库的子目录。这使您可以将另一个存储库克隆到您的项目中,并使提交分开进行。
我们将逐步开发一个简单的项目,该项目分为一个主项目和几个子项目。
首先,添加一个现有的Git存储库作为我们正在处理的存储库的子模块。要添加新的子模块,请使用该git submodule add命令以及要开始跟踪的项目的绝对或相对URL。在此示例中,我们将添加一个名为“ DbConnector”的库。
$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
默认情况下,子模块会将子项目添加到与存储库相同的目录中,在本例中为“ DbConnector”。如果要将其移至其他位置,可以在命令末尾添加其他路径。
如果此时运行git status,您会注意到一些事情。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: .gitmodules
new file: DbConnector
首先,您应该注意到新.gitmodules文件。这是一个配置文件,用于存储项目的URL和您将其放入其中的本地子目录之间的映射:
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果您有多个子模块,则此文件中将有多个条目。重要的是要注意,此文件与其他文件(例如您的.gitignore文件)一起受版本控制。它与项目的其余部分一起推拉。这就是克隆此项目的其他人如何知道从何处获取子模块项目的方式。
注意 | 由于.gitmodules文件中的URL是其他人首先尝试从中克隆/获取的URL,因此请确保使用他们可以访问的URL。例如,如果您使用与其他人不同的URL来推送,请使用其他人有权访问的URL。您可以在本地覆盖此值以git config submodule.DbConnector.url PRIVATE_URL供自己使用。如果适用,相对URL可能会有所帮助。 |
$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
尽管它DbConnector是工作目录中的子目录,但Git会将其视为子模块,并且不在该目录中时不会跟踪其内容。相反,Git将其视为来自该存储库的特定提交。
如果您想要更好的差异输出,可以将–submodule选项传递给git diff。
$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+ path = DbConnector
+ url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)
提交时,您会看到类似以下的内容:
$ git commit -am 'Add DbConnector module'
[master fb9093c] Add DbConnector module
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 DbConnector
注意输入160000模式DbConnector。这是Git中的一种特殊模式,基本上意味着您将提交记录为目录条目而不是子目录或文件。
最后,推动这些更改:
$ git push origin master
在这里,我们将克隆一个包含子模块的项目。克隆此类项目时,默认情况下会获得包含子模块的目录,但其中没有文件:
$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r--r-- 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
$
该DbConnector目录是存在的,而空。您必须运行两个命令:git submodule init初始化本地配置文件,并git submodule update从该项目中获取所有数据,并检查超级项目中列出的相应提交:
$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
现在您的DbConnector子目录处于您之前提交时的确切状态。
但是,还有另一种方法可以简化。如果传递–recurse-submodules该git clone命令,它将自动初始化和更新存储库中的每个子模块,包括嵌套子模块(如果存储库中的任何子模块本身具有子模块)。
$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
如果您已经克隆了项目却忘记了–recurse-submodules,则可以通过运行将git submodule init和git submodule update步骤合并git submodule update --init。要还初始化,获取和签出任何嵌套的子模块,可以使用万无一失git submodule update --init --recursive。
现在,我们有了一个包含子模块的项目的副本,并将与我们的队友在主项目和子模块项目上进行协作。
在项目中使用子模块的最简单模型是,如果您只是使用一个子项目,并希望不时从中获取更新,而实际上并没有在结帐中进行任何修改。让我们来看一个简单的例子。
如果要在子模块中检查新工作,则可以进入目录并运行,git fetch然后git merge通过上游分支来更新本地代码。
$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
现在,如果您返回主项目并运行,git diff --submodule您可以看到该子模块已更新,并获得了已添加到其中的提交列表。如果您不想–submodule每次运行都输入git diff,可以通过将diff.submoduleconfig值设置为“ log” 将其设置为默认格式。
$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
> more efficient db routine
> better connection routine
如果此时提交,则将在其他人更新时将子模块锁定为拥有新代码。
如果您不想在子目录中手动获取和合并,则还有一种更简便的方法来执行此操作。如果您运行git submodule update --remote,Git将进入您的子模块并为您获取和更新。
$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'
默认情况下,此命令将假定您要将签出更新master为子模块存储库的分支。但是,您可以根据需要将其设置为其他值。例如,如果您希望DbConnector子模块跟踪该存储库的“稳定”分支,则可以在您的.gitmodules文件中设置它(以便其他所有人也都对其进行跟踪),或者仅在您的本地.git/config文件中进行设置。让我们在.gitmodules文件中进行设置:
$ git config -f .gitmodules submodule.DbConnector.branch stable
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'
如果您不-f .gitmodules进行更改,那么它只会为您做出更改,但是使用存储库跟踪该信息可能更有意义,因此其他所有人也都可以这样做。
当我们git status在这一点上运行时,Git将向我们展示我们在子模块上有“新提交”。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: .gitmodules
modified: DbConnector (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
如果设置了配置设置status.submodulesummary,Git还将向您显示子模块更改的简短摘要:
$ git config status.submodulesummary 1
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: .gitmodules
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c3f01dc...c87d55d (4):
> catch non-null terminated lines
在这一点上,如果您运行,git diff我们既可以看到我们已经修改了.gitmodules文件,还可以看到有很多提交已被拉下并准备提交到子模块项目。
$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
这非常酷,因为我们实际上可以在子模块中看到要提交的提交日志。提交后,您也可以在运行后查看该信息git log -p。
$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon
Date: Wed Sep 17 16:37:02 2014 +0200
updating DbConnector for bug fixes
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
默认情况下,Git 在运行时将尝试更新所有子模块git submodule update --remote。如果它们很多,则可能只想传递要尝试更新的子模块的名称。
现在让我们步入协作者的行列,该协作者拥有自己的MainProject存储库本地副本。仅执行git pull以获取新提交的更改是不够的:
$ git pull
From https://github.com/chaconinc/MainProject
fb9093c..0a24cfc master -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
c3f01dc..c87d55d stable -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
.gitmodules | 2 +-
DbConnector | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c87d55d...c3f01dc (4):
< catch non-null terminated lines
< more robust error handling
< more efficient db routine
< better connection routine
no changes added to commit (use "git add" and/or "git commit -a")
默认情况下,该 git pull命令以递归方式获取子模块的更改,如上面第一条命令的输出所示。但是,它不会更新子模块。git status命令的输出显示了该信息,该信息表明子模块已“修改”,并具有“新提交”。此外,显示新提交的方括号指向左(<),表示这些提交记录在MainProject中,但不存在于本地DbConnector检出中。要完成更新,您需要运行git submodule update:
$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
请注意,是在安全方面,你应该运行git submodule update与–init在这时,标志的MainProject提交你刚拔掉添加了新的子模块,并与–recursive标志,如果任何子模块具有嵌套子模块。
如果要自动执行此过程,可以将–recurse-submodules标志添加 到git pull命令中(自Git 2.14起)。这将使Git git submodule update在提取后立即运行,从而将子模块置于正确的状态。此外,如果要使Git始终与配合使用–recurse-submodules,可以将配置选项设置submodule.recurse为true(git pull从Git 2.15开始有效)。此选项将使Git --recurse-submodules对支持它的所有命令使用该标志(除外clone)。
提取超级项目更新时可能会发生一种特殊情况:可能是上游存储库.gitmodules在您提交的提交之一中更改了文件中子模块的URL 。例如,如果子模块项目更改了其托管平台,则会发生这种情况。在这种情况下,如果超级项目引用了在资源库中本地配置的远程子模块中找不到的子模块提交,则git pull --recurse-submodules或可能git submodule update会失败。为了纠正这种情况,git submodule sync需要使用以下命令:
# copy the new URL to your local config
$ git submodule sync --recursive
# update the submodule from the new URL
$ git submodule update --init --recursive
如果您正在使用子模块,那么这样做很有可能是因为您确实想在处理主项目中的代码的同时(或跨多个子模块)处理子模块中的代码。 。否则,您可能会改用较简单的依赖项管理系统(例如Maven或Rubygems)。
因此,现在让我们来看一个示例,该示例与主项目同时对子模块进行更改,并同时提交和发布这些更改。
到目前为止,当我们运行git submodule update命令从子模块存储库中获取更改时,Git会获取更改并更新子目录中的文件,但会将子存储库保持为“分离的HEAD”状态。这意味着没有本地工作分支(例如master,例如)跟踪更改。没有有效的分支跟踪更改,这意味着即使将更改提交到子模块,这些更改也很可能在下次运行时丢失git submodule update。如果要跟踪子模块中的更改,则必须执行一些额外的步骤。
为了将子模块设置为更容易进入和破解,您需要做两件事。您需要进入每个子模块并签出一个分支进行操作。然后,您需要告诉Git如果进行了更改,然后git submodule update --remote从上游进行新工作,该怎么办。这些选项是您可以将它们合并到本地工作中,或者可以尝试在新更改的基础上重新建立本地工作。
首先,让我们进入子模块目录并签出一个分支。
$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'
让我们尝试使用“合并”选项更新子模块。要手动指定,我们可以将–merge选项添加到update调用中。在这里,我们将看到该子模块在服务器上进行了更改,并被合并到其中。
$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
c87d55d..92c7337 stable -> origin/stable
Updating c87d55d..92c7337
Fast-forward
src/main.c | 1 +
1 file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'
如果进入DbConnector目录,则新的更改已合并到本地stable分支中。现在让我们看看当我们对库进行本地更改并且其他人同时向上游推送另一个更改时会发生什么。
$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'Unicode support'
[stable f906e16] Unicode support
1 file changed, 1 insertion(+)
现在,如果我们更新子模块,我们可以看到进行本地更改并且上游也需要更改时发生的情况。
$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: Unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
如果忘记了–rebase或–merge,Git只会将子模块更新为服务器上的任何内容,并将您的项目重置为分离的HEAD状态。
$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
如果发生这种情况,请放心,您可以简单地返回目录并再次签出分支(仍将包含您的工作)并origin/stable手动合并或变基(或任何您想要的远程分支)。
如果您尚未在子模块中提交更改,而是运行了会导致问题的子模块更新,则Git将获取更改,但不会覆盖子模块目录中未保存的工作。
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
5d60ef9..c75e92a stable -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
如果您所做的更改与上游更改有所冲突,Git将在运行更新时通知您。
$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
您可以像往常一样进入子模块目录并修复冲突。
现在,我们在子模块目录中进行了一些更改。其中一些是通过我们的更新从上游引入的,而另一些则是在本地进行的,由于我们尚未推送它们,因此尚未向其他任何人提供。
$ git diff
Submodule DbConnector c87d55d..82d2ad3:
> Merge from origin/stable
> Update setup script
> Unicode support
> Remove unnecessary method
> Add new option for conn pooling
如果我们提交主项目并在不推动子模块更改的情况下进行推送,那么其他尝试检查我们的更改的人将会遇到麻烦,因为他们将无法获得依赖于子模块的更改。这些更改将仅存在于我们的本地副本上。
为了确保不会发生这种情况,您可以要求Git在推送主项目之前检查所有子模块是否已正确推送。该git push命令采用–recurse-submodules可设置为“检查”或“按需”的参数。push如果尚未提交任何已提交的子模块更改,“ check”选项将使失败。
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
如您所见,它还为我们下一步可能要做的工作提供了一些有用的建议。简单的选择是进入每个子模块,然后手动推入遥控器以确保它们在外部可用,然后再次尝试此推。如果您希望所有推送都执行检查行为,则可以通过将该行为设置为默认行为git config push.recurseSubmodules check。
另一种选择是使用“按需”值,它将尝试为您执行此操作。
$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
c75e92a..82d2ad3 stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
3d6d338..9a377d1 master -> master
如您所见,Git进入DbConnector模块并在推送主项目之前将其推送。如果该子模块推送由于某种原因失败,则主项目推送也将失败。您可以通过执行以下操作将此行为设置为默认行为git config push.recurseSubmodules on-demand。
如果您与其他人同时更改子模块引用,则可能会遇到一些问题。也就是说,如果子模块的历史有所不同,并致力于使一个超级项目中的分支有所不同,则可能需要花费一些时间来解决。
如果提交中的一个是另一个的直接祖先(快速合并),那么Git只需选择后者进行合并,这样就可以正常工作。
但是,Git甚至不会尝试进行简单的合并。如果子模块提交分歧并需要合并,您将获得如下所示的内容:
$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
9a377d1..eb974f8 master -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
因此,基本上,这里发生的事情是Git发现两个分支记录了子模块历史中的点,这些点是发散的并且需要合并。它将其解释为“未找到提交合并”,这很令人困惑,但是稍后我们将解释原因。
为了解决这个问题,您需要弄清楚子模块应该处于什么状态。奇怪的是,Git并没有真正给您提供很多信息来帮助您,甚至历史两边提交的SHA-1也没有。幸运的是,很容易弄清楚。如果运行git diff,则可以在尝试合并的两个分支中记录提交的SHA-1。
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
所以,在这种情况下,eb41d76是在我们的辅助模块中的承诺是,我们已经和c771610是提交上游了。如果我们进入子模块目录,它应该已经打开,eb41d76因为合并不会碰到它。如果不是出于某种原因,则可以简单地创建并签出指向它的分支。
重要的是另一端的提交的SHA-1。这是您必须合并并解决的问题。您可以直接尝试与SHA-1进行合并,也可以为它创建一个分支,然后尝试将其合并。我们建议使用后者,即使只是做出更好的合并提交消息也是如此。
因此,我们将进入子模块目录,根据中的第二个SHA-1创建一个名为“ try-merge”的分支git diff,然后手动合并。
$ cd DbConnector
$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135
$ git branch try-merge c771610
$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.
我们在这里遇到了实际的合并冲突,因此,如果我们解决并提交了合并冲突,则可以简单地用结果更新主项目。
$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes
$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
-Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)
$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
首先,我们解决冲突。
然后我们回到主项目目录。
我们可以再次检查SHA-1。
解决冲突的子模块条目。
提交我们的合并。
这可能有点令人困惑,但实际上并不难。
有趣的是,还有Git处理的另一种情况。如果子模块目录中包含一个合并提交,并且该合并提交包含历史记录中的两个提交,则Git会建议您将其作为可能的解决方案。它看到在子模块项目中的某个时刻,有人合并了包含这两个提交的分支,所以也许您会想要一个。
这就是为什么从之前错误消息是“合并之后提交找不到”,因为它不能做这个。这令人困惑,因为谁会期望它尝试这样做?
如果确实找到了一个可接受的合并提交,您将看到以下内容:
$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:
git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"
which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
Git提供的建议命令将更新索引,就好像您已经在运行git add(清除冲突)一样,然后提交。您可能不应该这样做。您可以轻松地进入子模块目录,看看有什么区别,快速进行此提交,进行正确的测试,然后提交。
$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward
$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forward to a common submodule child'
这完成了同样的事情,但是至少通过这种方式,您可以验证它是否可以工作,并且完成后将代码保存在子模块目录中。
您可以采取一些措施来简化子模块的使用。
有一个foreach子模块命令可以在每个子模块中运行一些任意命令。如果您在同一项目中有多个子模块,这将非常有帮助。
例如,假设我们要启动一个新功能或进行错误修复,并且我们在几个子模块中进行工作。我们可以轻松地将所有工作存储在我们所有的子模块中。
$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable
然后,我们可以创建一个新分支,并在所有子模块中切换到该分支。
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
你明白了。您可以做的一件非常有用的事情是对您的主项目以及所有子项目中的更改进行统一的比较。
$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)
commit_pager_choice();
+ url = url_decode(url_orig);
+
/* build alias_argv */
alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
return url_decode_internal(&url, len, NULL, &out, 0);
}
+char *url_decode(const char *url)
+{
+ return url_decode_mem(url, strlen(url));
+}
+
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
在这里我们可以看到我们正在子模块中定义一个函数,并在主项目中调用它。显然,这是一个简化的示例,但是希望它使您对如何使用它有所了解。
您可能想为其中一些命令设置一些别名,因为它们可能很长,并且您无法为大多数命令设置配置选项以使其成为默认值。我们在Git Aliases中介绍了如何设置Git别名,但这是一个示例,说明了您打算在Git中大量使用子模块时可能要设置的内容。
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
这样,您就可以git supdate在想要更新子模块或git spush使用子模块依赖性检查进行推送时简单地运行。
但是,使用子模块并非毫无困难。
例如,对于早于Git 2.13的Git版本,切换其中包含子模块的分支也很棘手。如果创建一个新分支,在其中添加一个子模块,然后切换回不包含该子模块的分支,则该子模块目录仍为未跟踪目录:
$ git --version
git version 2.12.2
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add ..." to include in what will be committed)
CryptoLibrary/
nothing added to commit but untracked files present (use "git add" to track)
删除目录并不困难,但是将目录放在其中可能会有些混乱。如果确实将其删除,然后切换回具有该子模块的分支,则需要运行submodule update --init以重新填充它。
$ git clean -ffdx
Removing CryptoLibrary/
$ git checkout add-crypto
Switched to branch 'add-crypto'
$ ls CryptoLibrary/
$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'
$ ls CryptoLibrary/
Makefile includes scripts src
再次强调,并不是很困难,但可能会造成一些混乱。
较新的Git版本(Git> = 2.13)通过–recurse-submodules在git checkout命令中添加标志来简化所有操作,这将使子模块处于我们要切换到的分支的正确状态。
$ git --version
git version 2.13.3
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
当您在超级项目中的多个分支上工作时,使用–recurse-submodulesof标记git checkout也很有用,每个分支的子模块都指向不同的提交。实际上,如果您在记录了不同提交的子模块的分支之间切换,则在执行时git status,子模块将显示为“已修改”,并指示“新提交”。这是因为在切换分支时,默认情况下不会保留子模块状态。
这可能确实令人困惑,因此始终git checkout --recurse-submodules在项目中包含子模块时建议这样做。对于不带该–recurse-submodules标志的旧版Git ,签出后可以git submodule update --init --recursive用来将子模块置于正确的状态。
幸运的是,你可以让Git(> = 2.14)始终使用 --recurse-submodules通过设置配置选项标志submodule.recurse:git config submodule.recurse true。如上所述,对于每个有–recurse-submodules选项的命令(除外git clone),这还将使Git递归到子模块中。
许多人遇到的另一个主要警告事项涉及从子目录切换到子模块。如果您一直在跟踪项目中的文件,并且想将它们移到子模块中,则必须小心,否则Git会生您的气。假设您在项目的子目录中有文件,并且想要将其切换到子模块。如果删除该子目录然后运行submodule add,则Git会对您大吼:
$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index
您必须先取消登台CryptoLibrary目录。然后,您可以添加子模块:
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
现在,假设您是在分支中完成的。如果尝试切换回这些文件仍在实际树中而不是子模块中的分支,则会出现此错误:
$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting
您可以用来强制将其切换checkout -f,但是要注意不要在其中保存未保存的更改,因为这些更改可能会被该命令覆盖。
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
然后,当您切换回时,CryptoLibrary由于某种原因您将获得一个空目录,并且git submodule update也可能无法修复该目录。您可能需要进入子模块目录并运行a git checkout .来取回所有文件。您可以在submodule foreach脚本中运行它,以针对多个子模块运行它。
重要的是要注意,现在这些子模块将其所有Git数据都保存在顶级项目的.git目录中,因此与许多较旧的Git版本不同,销毁子模块目录不会丢失任何提交或分支。
使用这些工具,子模块可以是在多个相关但仍独立的项目上同时开发的相当简单有效的方法。