なぜGitサブモジュールHEADがマスターから切り離されているのですか?


162

私はGitサブモジュールを使用しています。サーバーから変更をプルした後、多くの場合、サブモジュールのヘッドがマスターブランチから切り離されます。

なぜそれが起こるのですか?

私はいつもしなければなりません:

git branch
git checkout master

サブモジュールが常にマスターブランチを指していることを確認するにはどうすればよいですか?


1
あなたはこの答えを読みましたか?stackoverflow.com/questions/1777854/...
ジョニー・Z

@bitoiuサブツリーとGoogle Repoを見ました。私はまだ完璧な解決策を持っていません:(
om471987

1
CI環境でのgitsubmodulesでの私の経験はひどいです。おそらく他の人がより良い経験をしているかもしれません。
bitoiu

@JohnnyZありがとう。サブモジュールはツリーの先頭ではなくコミットを指すことを理解しました。しかし、なぜブランチから切り離されましたか。ブランチが1つある場合は、デフォルトでそれに接続しないでください
om471987

3
サブモジュールが悪いと聞いたからといって、サブモジュールをすぐに却下しないでください。継続的な統合が必要な場合は不十分なソリューションですが、外部プロジェクトからコードを埋め込みたい場合や、すべてのプルを明示的に管理する場合は、ほぼ完璧なソリューションです。これは、組織によって制御されていないフォークされていないモジュールと統合する場合のベストプラクティスであることがよくあります。問題は、それらがまったくうまく機能しない他のあらゆる状況で魅力的な解決策になることです。最善のアドバイスは、それらがどのように機能するかを読み、シナリオを評価することです。
サラG

回答:


176

編集:

有効なソリューションについては@Simba Answerを参照してください

submodule.<name>.update変更したいものです。ドキュメントを参照してください - デフォルトcheckout
submodule.<name>.branchで追跡するリモートブランチを指定します- デフォルトmaster


古い答え:

個人的には、時間の経過とともに機能しなくなる可能性のある外部リンクへの回答をここで嫌い、ここで私の回答を確認します (質問が重複しない限り) -他の件名の行の間の件名をカバーする質問に送信しますが、全体的には次のようになります:「私は答えないで、ドキュメントを読んでください。」

質問に戻りましょう:なぜそれが起こるのですか?

あなたが説明した状況

サーバーから変更をプルした後、多くの場合、サブモジュールのヘッドがマスターブランチから切り離されます。

これは、サブモジュールをあまり頻繁に使用しない場合や、サブモジュールで開始したばかりの場合の一般的なケースです。私は 、サブモジュールのHEADがデタッチされるある時点で全員がそこにいたと述べたことは正しいと思います。

  • 原因:サブモジュールが正しいブランチを追跡していません(デフォルトマスター)。
    解決策:サブモジュールが正しいブランチを追跡していることを確認してください
$ cd <submodule-path>
# if the master branch already exists locally:
# (From git docs - branch)
# -u <upstream>
# --set-upstream-to=<upstream>
#    Set up <branchname>'s tracking information so <upstream>
#    is considered <branchname>'s upstream branch.
#    If no <branchname> is specified, then it defaults to the current branch.
$ git branch -u <origin>/<branch> <branch>
# else:
$ git checkout -b <branch> --track <origin>/<branch>
  • 原因:親リポジトリがサブモジュールブランチを追跡するように設定されていません。
    解決策:次の2つのコマンドで新しいサブモジュールを追加して、サブモジュールがリモートブランチを追跡するようにします。
    • まず、gitにリモコンを追跡するように指示します<branch>
    • チェックアウトの代わりにリベースまたはマージを実行するようにgitに指示します
    • リモートからサブモジュールを更新するようにgitに指示します。
    $ git submodule add -b <branch> <repository> [<submodule-path>]
    $ git config -f .gitmodules submodule.<submodule-path>.update rebase
    $ git submodule update --remote
  • このように既存のサブモジュールを追加していない場合は、簡単に修正できます。
    • まず、追跡したいブランチがサブモジュールにチェックアウトされていることを確認します。
    $ cd <submodule-path>
    $ git checkout <branch>
    $ cd <parent-repo-path>
    # <submodule-path> is here path releative to parent repo root
    # without starting path separator
    $ git config -f .gitmodules submodule.<submodule-path>.branch <branch>
    $ git config -f .gitmodules submodule.<submodule-path>.update <rebase|merge>

一般的なケースでは、上記の構成の問題の1つに関連していたため、DETACHED HEADはすでに修正されています。

DETACHED HEADの修正 .update = checkout

$ cd <submodule-path> # and make modification to your submodule
$ git add .
$ git commit -m"Your modification" # Let's say you forgot to push it to remote.
$ cd <parent-repo-path>
$ git status # you will get
Your branch is up-to-date with '<origin>/<branch>'.
Changes not staged for commit:
    modified:   path/to/submodule (new commits)
# As normally you would commit new commit hash to your parent repo
$ git add -A
$ git commit -m"Updated submodule"
$ git push <origin> <branch>.
$ git status
Your branch is up-to-date with '<origin>/<branch>'.
nothing to commit, working directory clean
# If you now update your submodule
$ git submodule update --remote
Submodule path 'path/to/submodule': checked out 'commit-hash'
$ git status # will show again that (submodule has new commits)
$ cd <submodule-path>
$ git status
HEAD detached at <hash>
# as you see you are DETACHED and you are lucky if you found out now
# since at this point you just asked git to update your submodule
# from remote master which is 1 commit behind your local branch
# since you did not push you submodule chage commit to remote. 
# Here you can fix it simply by. (in submodules path)
$ git checkout <branch>
$ git push <origin>/<branch>
# which will fix the states for both submodule and parent since 
# you told already parent repo which is the submodules commit hash 
# to track so you don't see it anymore as untracked.

しかし、サブモジュールに対してすでにローカルでいくつかの変更を行ってコミットし、コミットした場合、これらをリモートにプッシュしてから、 'git checkout'を実行すると、Gitから次のように通知されます。

$ git checkout <branch>
Warning: you are leaving 1 commit behind, not connected to any of your branches:
If you want to keep it by creating a new branch, this may be a good time to do so with:

一時的なブランチを作成するための推奨されるオプションは良いかもしれません、そしてあなたはこれらのブランチをマージすることができます等。しかし、私は個人的にはgit cherry-pick <hash>この場合にのみ使用します。

$ git cherry-pick <hash> # hash which git showed you related to DETACHED HEAD
# if you get 'error: could not apply...' run mergetool and fix conflicts
$ git mergetool
$ git status # since your modifications are staged just remove untracked junk files
$ rm -rf <untracked junk file(s)>
$ git commit # without arguments
# which should open for you commit message from DETACHED HEAD
# just save it or modify the message.
$ git push <origin> <branch>
$ cd <parent-repo-path>
$ git add -A # or just the unstaged submodule
$ git commit -m"Updated <submodule>"
$ git push <origin> <branch>

サブモジュールをDETACHED HEAD状態にすることができるケースがいくつかありますが、特定のケースをデバッグする方法についてもう少し理解していただければ幸いです。


2
HEAD detachedはのデフォルトの動作ですgit submodule update --remote。シンバの答えを見てください、それが正しい答えであると思います。
magomar

77

branchオプションを追加しても、サブモジュールの切り離された動作とはまったく関係.gitmoduleありません。@mkunglaからの古い回答は正しくないか、廃止されています。

HEADが分離すると、デフォルトの動作であるの。git submodule --helpgit submodule update --remote

まず、追跡するブランチを指定する必要はありませんorigin/master追跡されるデフォルトのブランチです。

-リモート

スーパープロジェクトの記録されたSHA-1を使用してサブモジュールを更新する代わりに、サブモジュールのリモート追跡ブランチのステータスを使用します。使用されるリモートはブランチのリモート(branch.<name>.remote)で、デフォルトはoriginです。使用されるリモートブランチのデフォルトはmasterです。

なぜ

では、なぜHEADは後で切り離されるのupdateですか?これは、デフォルトのモジュール更新動作checkoutが原因です

- チェックアウト

スーパーモジュールに記録されたコミットを、サブモジュールの分離されたHEADでチェックアウトします。これはデフォルトの動作です。このオプションの主な用途は、submodule.$name.update以外の値に設定されたときにオーバーライドすることcheckoutです。

この奇妙な更新動作を説明するには、サブモジュールがどのように機能するかを理解する必要がありますか?

Pro Gitのサブモジュールから始めることからの引用

sbmodule DbConnectorは作業ディレクトリのサブディレクトリですが、Gitはそれをサブモジュールとして認識し、そのディレクトリにいない場合はその内容を追跡しません。代わりに、Gitはそれをそのリポジトリからの特定のコミットと見なします

メインリポジトリは、サブモジュールを特定の時点の状態、つまりコミットID 追跡します。したがって、モジュールを更新すると、コミットIDが新しいものに更新されます。

どうやって

サブモジュールをリモートブランチと自動的にマージする場合は、--mergeまたはを使用します--rebase

- マージ

このオプションはupdateコマンドに対してのみ有効です。スーパープロジェクトに記録されたコミットをサブモジュールの現在のブランチにマージします。このオプションを指定すると、サブモジュールのHEADは切り離されません

--rebase

スーパープロジェクトに記録されたコミットに現在のブランチをリベースします。このオプションを指定すると、サブモジュールのHEADは切り離されません

あなたがする必要があるのは、

git submodule update --remote --merge
# or
git submodule update --remote --rebase

推奨エイリアス:

git config alias.supdate 'submodule update --remote --merge'

# do submodule update with
git supdate

またはに設定することにより、--mergeまたは--rebaseのデフォルトの動作としてを作成するオプションもあります。git submodule updatesubmodule.$name.updatemergerebase

のサブモジュール更新のデフォルトの更新動作を設定する方法の例を次に示します.gitmodule

[submodule "bash/plugins/dircolors-solarized"]
    path = bash/plugins/dircolors-solarized
    url = https://github.com/seebi/dircolors-solarized.git
    update = merge # <-- this is what you need to add

または、コマンドラインで構成します。

# replace $name with a real submodule name
git config -f .gitmodules submodule.$name.update merge

参考文献


6
を使用するgit submodule update --remote --mergeと、サブモジュールが切り離された状態でプルダウンされます。また--rebase、同じ結果で試してみました。
Joe Strout

8
@JoeStroutサブモジュールが既に切り離されている場合は、上記のコマンドで更新を行う前に、切り離された状態を修正します。cdサブモジュールに、サブモジュールを特定のブランチにチェックアウトしgit checkout masterます。
Simba

2
または、これが複数の(再帰的な)サブモジュールにとって面倒すぎる場合は、単に実行してくださいgit submodule foreach --recursive git checkout master
stefanct

1
「gitのしくみ」の説明の一部しか理解していません。TBH私はgitがどのように機能するかを理解することに本当に興味がなく、それを使いたいだけです。これで、切り離されたサブモジュールをで修正できることがわかりましたgit submodule foreach --recursive git checkout master。しかし、どうすればgitが常にそれらを切り離すのを防ぐことができますか?各サブモジュールの構成オプションの設定はオプションではありません!
Nicolas

私にとって、実行git submodule update --remote --mergeしてもサブモジュールは切り離されたHEAD状態のままではありませんでしたが、DIDがサブモジュールを切り離されたHEAD状態のままにするようgit submodule update.gitmoduleファイルを編集した後に実行すると実行されました。
bweber13

41

いつもデタッチしているのに飽きたので、シェルスクリプトを使用してすべてのモジュール用に構築します。私はすべてのサブモジュールがマスター上にあると仮定します:これがスクリプトです:

#!/bin/bash
echo "Good Day Friend, building all submodules while checking out from MASTER branch."

git submodule update 
git submodule foreach git checkout master 
git submodule foreach git pull origin master 

親モジュールから実行してください


2
git submodule foreach git pull origin master-これは私が探していたものです.. kudos
csomakk

シンプルで簡潔!ありがとう!
zhekaus

12

ここで私の答えを確認してください: Gitサブモジュール:ブランチ/タグを指定する

必要に応じて、「branch = master」行を.gitmodulesファイルに手動で追加できます。リンクを読んで、私の意味を確認してください。

編集:ブランチで既存のサブモジュールプロジェクトを追跡するには、代わりにVonCの指示に従ってください:

Gitサブモジュール:ブランチ/タグを指定する


14
回答はインラインであると想定されています。回答にリンクするIIRCは、スタックオーバーフローの偽のパスです。
トニートッパー2014年

1
@TonyTopper別のSOの回答にリンクするだけでも?IIRCのみが外部リンクを失い、リンクが無効になり、答えが役に立たないため、眉をひそめています。それでも、SOの回答にはそのような危険性はありません。SOが消えない限り(そして何が起こっても復元できる場合を除いて)、答えは消えません。また、彼は質問に答えてbranch = master" line into your .gitmoduleくれました。実際には完全な答えであるので、私はその問題を解決しました。
Mecki 2017年

9

サブモジュールにブランチをチェックアウトさせるもう1つの方法.gitmodulesは、ルートフォルダーのファイルに移動し、branch次のようにモジュール構成にフィールドを追加することです。

branch = <branch-name-you-want-module-to-checkout>


15
私にとってこれは機能しません。正しく設定しましたbranch = my_wanted_branch。しかし、git submodule update --remoteそれを実行すると、分離したヘッドとしてチェックアウトされます。
Andrius

これを行ってから、cd sudmodule&git co thebranche&cd ..、そしてgit submodule update --remoteを実行してください。
pdem

スーパープロジェクトがサブモジュールの再帰的な方法で複製されている間、またはサブモジュールが初期化されている間だけ、「。gitmodules」がアクティブに使用されている(読み込まれている)のではないですか?言い換えると、独自のリポジトリでは、「。gitmodules」に配置されたサブモジュール構成の更新から常に利益を得ているわけではないファイルを更新します。私の理解では、 '。gitmodules'は、リポジトリのクローン作成中に作成された設定用のテンプレートです。
Na13-c

3

他の人が言ったように、これが発生する理由は、親リポジトリがサブモジュール内の特定のコミット(のSHA1)への参照しか含まないためです。ブランチについては何も知りません。これが機能する方法です。そのコミットであったブランチは前方(または後方)に移動した可能性があり、親リポジトリがブランチを参照していた場合、それが発生すると簡単に中断する可能性があります。

ただし、特に親リポジトリとサブモジュールの両方で積極的に開発している場合、detached HEAD状態は混乱しやすく、潜在的に危険な場合があります。サブモジュールがdetached HEAD状態のときにコミットを行うと、これらがぶら下がってしまい、作業を簡単に失う可能性があります。(ぶら下がりコミットは通常git reflog、を使用して救出できますが、そもそもそれらを回避する方がはるかに優れています。)

私と同じように、ほとんどの場合、チェックアウトされているコミットを指すブランチがサブモジュールにある場合、同じコミットで分離されたHEAD状態にあるのではなく、そのブランチをチェックアウトします。これを行うには、次のエイリアスをgitconfigファイルに追加します。

[alias]
    submodule-checkout-branch = "!f() { git submodule -q foreach 'branch=$(git branch --no-column --format=\"%(refname:short)\" --points-at `git rev-parse HEAD` | grep -v \"HEAD detached\" | head -1); if [[ ! -z $branch && -z `git symbolic-ref --short -q HEAD` ]]; then git checkout -q \"$branch\"; fi'; }; f"

これで、実行後git submodule updateはを呼び出すだけgit submodule-checkout-branchで済みます。コミットでチェックアウトされているサブモジュールは、ブランチを指しているブランチをチェックアウトします。すべてが同じコミットを指す複数のローカルブランチがない場合は、通常、これで十分です。そうでない場合は、少なくとも、コミットがぶら下がったままではなく、実際のブランチに確実に移動するようになります。

さらに、チェックアウト時にサブモジュールを自動的に更新するようにgitを設定している場合(を使用git config --global submodule.recurse trueこの回答を参照)、このエイリアスを自動的に呼び出すチェックアウト後フックを作成できます。

$ cat .git/hooks/post-checkout 
#!/bin/sh
git submodule-checkout-branch

そして、あなたはどちらか呼び出す必要はありませんgit submodule updategit submodule-checkout-branch、単にやってgit checkout(存在する場合)には、それぞれのコミットにすべてのサブモジュールを更新し、対応する枝をチェックアウトします。


0

最も簡単な解決策は次のとおりです。

git clone --recursive git@github.com:name/repo.git

次に、repoディレクトリでcdし、次のようにします。

git submodule update --init
git submodule foreach -q --recursive 'git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)'
git config --global status.submoduleSummary true

参考資料Gitサブモジュールのベストプラクティス

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.