简体   繁体   English

Git 如何同时在 repo 和子模块上结帐?

[英]How to Git checkout on repo and submodules simultaneously?

I'm currently working on a repo with its own files, as well as two submodules which have files in them as well.我目前正在开发一个带有自己文件的存储库,以及两个包含文件的子模块。 I want to be able to checkout branches on all three of them at once.我希望能够同时检查所有三个分支。

During my workflow, I create and move between branches in all three repos simultaneously (so for example if I start working on a new feature, I'll make a new feature branch on the main repo, as well as a new branch with the same name on both submodule's repos).在我的工作流程中,我同时在所有三个 repo 中创建和移动分支(例如,如果我开始研究一个新功能,我将在主 repo 上创建一个新功能分支,以及一个具有相同功能的新分支两个子模块的 repos 上的名称)。 It's important to note this is less of a personal preference and more a convention where I am working.重要的是要注意,这与其说是个人偏好,不如说是我工作的惯例。

Whenever I checkout a branch on the main repo (Even if it's using --recurse-submodules), the subrepos are detached heads and not the latest commit on their branch of the same name.每当我在主仓库上签出一个分支时(即使它使用 --recurse-submodules),子仓库都是分离的头,而不是其同名分支上的最新提交。

Is this even possible to do?这甚至可能吗? If so, how can one do this?如果是这样,怎么能做到这一点?

TL;DR TL;博士

It takes at least two shell commands, though you can put them all on one line, or make a shell or Git alias for them:它至少需要两个 shell 命令,尽管您可以将它们全部放在一行上,或者为它们创建一个 shell 或 Git 别名:

git checkout <branch>
git submodule foreach --recursive 'git checkout <branch>'

You only need the --recursive if some submodule(s) have their own submodule(s) and you want to apply the foreach recursively.如果某些子模块有自己的子模块并且您想递归地应用 foreach,则只需要--recursive

Long

Submodules are supposed to be in detached-HEAD mode.子模块应该处于 detached-HEAD 模式。 That's how they work: the superproject repository contains, as part of each commit, the hash ID of the correct commit for the submodule.这就是它们的工作方式:作为每个提交的一部分,超级项目存储库包含子模块正确提交的 hash ID。

That said, sometimes, to make a new superproject commit, you'd like to stop using the commit specified by some previous superproject commit, and start using some other commit, in one or more of your submodules.也就是说,有时,要进行超级项目提交,您希望在一个或多个子模块中停止使用某个先前超级项目提交指定的提交,并开始使用其他一些提交。 To do that, you must tell a submodule-controlling-Git-command to git checkout some other commit in that submodule.为此,您必须将 submodule-controlling-Git-command 告诉git checkout该子模块中的其他一些提交。

For instance, suppose we are in the superproject, which I'll call R :例如,假设我们在超级项目中,我将其称为R

$ cd R
$ git status
... superproject status shows up here ...

Within R , we have dir/subm , which is a submodule.R中,我们有dir/subm ,它是一个子模块。 If we run:如果我们运行:

$ git rev-parse :dir/subm

(note the leading colon, which is gitrevisions syntax for accessing the index) we get the raw hash ID stored in Git's index (that is, the index for R , as currently checked out in our current working directory, which is our working tree here). (注意前导冒号,这是用于访问索引的 gitrevisions 语法)我们得到存储在 Git 索引中的原始 hash ID(即R的索引,当前在我们当前的工作目录中签出,这是我们的工作树在这里)。 That's the commit that the superproject says should be checked out, as a detached HEAD.这就是超级项目所说的应该作为独立的 HEAD 签出的提交。 So if we then run:所以如果我们再运行:

$ cd dir/subm
$ git status

we can expect to see HEAD detached at hash , where the hash is the one we just saw above.我们可以预期HEAD detached at hash ,其中hash是我们刚刚在上面看到的那个。 That's because the superproject Git ran:那是因为超级项目 Git 运行:

(cd dir/subm && git checkout --detach $hash)

with $hash set to that hash ID. $hash设置为 hash ID。 But now that we're in dir/subm , we have a regular ordinary Git repository.但是现在我们dir/subm中,我们有一个普通的 Git 存储库。 We can run:我们可以运行:

git checkout dev

or whatever we like: we're in the submodule repository S now, not the superproject repository R .或任何我们喜欢的:我们现在在子模块存储库S中,而不是超级项目存储库R Technically we're in the working tree of S .从技术上讲,我们在S工作树中。 There will be a directory named .git here, or, in modern Git, a file named .git containing the path to the S repository, contained within ../../.git/modules/ somewhere, most likely.这里将有一个名为.git的目录,或者,在现代 Git 中,一个名为.git文件包含S存储库的路径,最有可能包含在../../.git/modules/某处。 But the key is that we're in a Git repository so we can run git checkout , do work, run git add , and then run git commit . But the key is that we're in a Git repository so we can run git checkout , do work, run git add , and then run git commit . That way, we can make an entirely new commit, if we need to.这样,如果需要,我们可以进行全新的提交。

If we just need to pick some existing commit, well, we can run git log and find it and git checkout the appropriate commit.如果我们只需要选择一些现有的提交,那么我们可以运行git log并找到它,然后git checkout出适当的提交。 One way or another, now that we're sitting in S , we pick out or create the commit we want.一种或另一种方式,现在我们坐在S中,我们选择或创建我们想要的提交。

Now we just have to pop back up to the working tree for R .现在我们只需要弹回R的工作树。 Since we got here by doing down twice—into dir/subm/ —we get there by going up twice:因为我们通过两次向下(进入dir/subm/ )到达这里,所以我们通过两次向上到达那里:

cd ../..

Now that we're back in R , we run:现在我们回到R ,我们运行:

git add dir/subm

(If you have an ancient Git, be very careful to leave off the trailing slash here!) This updates Git's index for R so that instead of the previous detached HEAD commit hash ID, it now contains the actual, current HEAD hash ID for S . (If you have an ancient Git, be very careful to leave off the trailing slash here!) This updates Git's index for R so that instead of the previous detached HEAD commit hash ID, it now contains the actual, current HEAD hash ID for S . The R Git got this by running: R Git 通过运行:

(cd dir/subm; git rev-parse HEAD)

which printed out the hash ID of the commit we just forced into place in S by working there.它打印出我们刚刚通过在那里工作在S中强制到位的提交的 hash ID。

Since Git makes a new commit from whatever is in Git's index, we can now, or some time soon in the future, run git commit here, in R , to make a new commit.由于 Git 从 Git 索引中的任何内容进行的提交,我们现在或不久的将来可以在此处运行git commit ,在R中进行的提交, This new commit will tell anyone using this repository later to have their R Git command their S Git to git checkout --detach $hash where $hash is the hash we just had our Git stuff into Git's index. This new commit will tell anyone using this repository later to have their R Git command their S Git to git checkout --detach $hash where $hash is the hash we just had our Git stuff into Git's index.

In other words, a future checkout will still use a detached HEAD in S , but now it will use this one.换句话说,未来的结帐仍然会在S中使用分离的 HEAD,但现在它将使用这个

Now, as to this part:现在,关于这部分:

During my workflow, I create and move between branches in all three repos simultaneously (so for example if I start working on a new feature, I'll make a new feature branch on the main repo, as well as a new branch with the same name on both submodule's repos).在我的工作流程中,我同时在所有三个 repo 中创建和移动分支(例如,如果我开始研究一个新功能,我将在主 repo 上创建一个新功能分支,以及一个具有相同功能的新分支两个子模块的 repos 上的名称)。 It's important to note this is less of a personal preference and more a convention where I am working.重要的是要注意,这与其说是个人偏好,不如说是我工作的惯例。

That's all fine.没关系。 You can (cd dir/subm; git checkout -b feature/xyzzy $hash) to create a new branch named feature/xyzzy and put yourself on that branch, in the submodules.您可以(cd dir/subm; git checkout -b feature/xyzzy $hash)创建一个名为feature/xyzzy的新分支,并将自己放在该分支的子模块中。 The superproject Git doesn't care about that, it only cares—at some point, when you run git add dir/subm —what HEAD resolves to, hash-ID-wise, in dir/subm at that time .超级项目 Git 不关心这一点,它只关心 - 在某些时候,当你运行git add dir/subm - HEAD解析为,hash-ID-wise,在dir/subm It doesn't matter whether the submodule HEAD is attached or not.是否附加了子模块HEAD并不重要。 The name —if any.—in the submodule is irrelevant to the superproject.子模块中的名称(如果有的话)与超级项目无关。

Note that the name in the submodule S is relevant, though, when you go to git push commits from S to some other Git.请注意,子模块S中的名称相关的,但是,当您从 go 到git push提交从S推送到其他一些 Git 时。 So this part almost requires an attached HEAD, with a branch name.所以这部分几乎需要一个附加的 HEAD,带有一个分支名称。 (It is possible to develop on a detached HEAD, then use git push url-or-remote HEAD:refs/heads/ branch , but that's not a fun way of working. For one thing, it's too easy to accidentally lose track of the right commit hash ID that way. It's much nicer to put S on a branch name to do work in it.) So that's why you might use git checkout -b feature/xyzzy $hash . (可以在分离的 HEAD 上开发,然后使用git push url-or-remote HEAD:refs/heads/ branch ,但这不是一种有趣的工作方式。一方面,很容易不小心忘记以这种方式正确提交 hash ID。将S放在分支名称上以在其中工作好得多。)这就是为什么您可以使用git checkout -b feature/xyzzy $hash的原因。 But: where should $hash come from, here?但是: $hash应该从哪里来? Well, that part is up to you.好吧,那部分取决于你。 Instead of a raw hash ID, you can use HEAD or some existing branch name or some existing remote-tracking name or whatever you like.除了原始的 hash ID,您可以使用HEAD或一些现有的分支名称或一些现有的远程跟踪名称或任何您喜欢的名称。 Git just needs some commit ID to put into the new branch name, if you're creating a new branch name. Git 只需要一些提交 ID 来放入新的分支名称,如果你正在创建一个新的分支名称。

If there's an origin/feature/xyzzy name that contains the correct hash ID, you don't even have to fish it out.如果有一个包含正确 hash ID 的origin/feature/xyzzy名称,您甚至不必将其找出来。 Remember, S is an ordinary Git repository.请记住, S是一个普通的 Git 存储库。 So git checkout feature/xyzzy , while in S , means "use the existing name, but if it doesn't exist, use the DWIM mode to look for origin/whatever".所以git checkout feature/xyzzy ,而在S中,意思是“使用现有名称,但如果它不存在,则使用 DWIM 模式查找来源/任何内容”。 The only catch here is that you must run these commands in the S submodule.这里唯一的问题是您必须S子模块中运行这些命令。

Let's me recommend a post-checkout hook for the superproject like this:让我为超级项目推荐一个post-checkout挂钩,如下所示:

#!/bin/sh

# post-checkout hook that switches submodule branches to the same branch as the superproject

prev_HEAD="$1"
new_HEAD="$2"
new_branch="$3"

if [ "$new_branch" = 1 ]; then
    branch_name=`git symbolic-ref -q HEAD`
    if test -n "$branch_name"; then
        git submodule foreach git checkout "$branch_name"
    else
        echo "WARNING: detached HEAD"
        git submodule update
    fi
fi

exit 0

For recursive checkout: git submodule foreach --recursive and git submodule update --recursive .对于递归结帐: git submodule foreach --recursivegit submodule update --recursive

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM