次のようなコミット ツリーを持つローカル リポジトリがあるとします。
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
master
これは私の最新の安定したリリース コードであり、これはdevelop
私の「次の」リリース コードであり、は に向けて準備中の新機能feature
です。develop
フックを使用して、コミットが HEADの直接の子孫でfeature
ない限り、リモート リポジトリへのプッシュを拒否できるようにしたいと考えています。つまり、機能がオンになっているため、コミット ツリーは次のようになります。f
develop
git rebase
d
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
それで、次のことが可能になります:
feature
?の親ブランチを識別します。- 親ブランチ内のどのコミット
f
の子孫であるかを識別しますか?
そこから、親ブランチの HEAD が何であるかを確認し、f
先行ブランチが親ブランチの HEAD と一致するかどうかを確認して、機能をリベースする必要があるかどうかを判断します。
ベストアンサー1
リモート リポジトリに開発ブランチのコピーがあると仮定すると(最初の説明ではローカル リポジトリにあると説明されていますが、リモートにも存在するようです)、必要なことは達成できるはずですが、アプローチは想定したものとは少し異なります。
Gitの履歴はダグコミット。ブランチ (および一般的な「ref」) は、継続的に拡大するコミット DAG 内の特定のコミットを指す一時的なラベルにすぎません。そのため、ブランチ間の関係は時間の経過とともに変化しますが、コミット間の関係は変化しません。
---o---1 foo
\
2---3---o bar
\
4
\
5---6 baz
baz
は(の古いバージョン) に基づいているようですbar
が、 を削除するとどうなるでしょうかbar
?
---o---1 foo
\
2---3
\
4
\
5---6 baz
baz
これで、は に基づいているように見えますfoo
。しかし、 の祖先はbaz
変更されていません。ラベル (および結果として生じる未解決のコミット) を削除しただけです。では、 に新しいラベルを追加するとどうなるでしょうか4
?
---o---1 foo
\
2---3
\
4 quux
\
5---6 baz
baz
今ではは に基づいているように見えますquux
。それでも、祖先は変更されておらず、ラベルのみが変更されています。
6
ただし、「 commit はcommit の子孫であるか3
?」と尋ねている場合(および3
が完全な SHA-1 コミット名であると想定)、およびラベルが存在するかどうか6
に関係なく、答えは「はい」になります。bar
quux
したがって、「プッシュされたコミットは、 developブランチの現在の先端の子孫ですか?」などの質問をすることはできますが、「プッシュされたコミットの親ブランチは何ですか?」という質問を確実に行うことはできません。
あなたが望むことに近そうな、最も信頼できる質問は次のとおりです。
プッシュされたコミットのすべての祖先( developの現在の先端とその祖先を除く)のうち、 developの現在の先端を親として持つもの:
- そのようなコミットが少なくとも 1 つ存在しますか?
- このようなコミットはすべて単一親コミットですか?
これは次のように実装できます。
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
echo "'$basename' is missing, call for help!"
exit 1
fi
parents_of_children_of_base="$(
git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
,) echo "must descend from tip of '$basename'"
exit 1 ;;
,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
exit 1 ;;
,*) exit 0 ;;
esac
これにより、制限したい内容の一部はカバーされますが、すべてがカバーされるわけではありません。
参考までに、拡張された例の履歴を以下に示します。
A master
\
\ o-----J
\ / \
\ | o---K---L
\ |/
C--------------D develop
\ |\
F---G---H | F'--G'--H'
| |\
| | o---o---o---N
\ \ \ \
\ \ o---o---P
\ \
R---S
上記のコードは、、、またはを受け入れながら、およびH
を拒否するために使用できますが、およびも受け入れます(これらはマージを伴いますが、 developの先端はマージしません)。S
H'
J
K
N
L
P
と も拒否するにはL
、P
質問を変更して尋ねます。
プッシュされたコミットのすべての祖先( developの現在の先端とその祖先を除く)について:
- 2 つの親を持つコミットはありますか?
- そうでない場合、少なくとも 1 つのそのようなコミットに、その (唯一の) 親であるdevelopmentの現在のヒントがありますか?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
echo "'$basename' is missing, call for help!"
exit 1
fi
parents_of_commits_beyond_base="$(
git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
*\ *) echo "must not push merge commits (rebase instead)"
exit 1 ;;
*"$baserev"*) exit 0 ;;
*) echo "must descend from tip of '$basename'"
exit 1 ;;
esac