submodule--helper: teach push-check to handle HEAD

In 06bf4ad1d (push: propagate remote and refspec with --recurse-submodules) push was taught how to propagate a refspec down to submodules when the '--recurse-submodules' flag is given. The only refspecs that are allowed to be propagated are ones which name a ref which exists in both the superproject and the submodule, with the caveat that 'HEAD' was disallowed. This patch teaches push-check (the submodule helper which determines if a refspec can be propagated to a submodule) to permit propagating 'HEAD' if and only if the superproject and the submodule both have the same named branch checked out and the submodule is not in a detached head state. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Jul 20, 2017 at 10:40 UTC c7be7201a7b71f590325f0d858f909a4c0b443f6
3 files changed +79 -13
builtin/submodule--helper.c
+40 -9
@@ -1108,9 +1108,28 @@ static int resolve_remote_submodule_branch(int argc, const char **argv,
1108 static int push_check(int argc, const char **argv, const char *prefix)
1109 {
1110 struct remote *remote;
1111 + const char *superproject_head;
1112 + char *head;
1113 + int detached_head = 0;
1114 + struct object_id head_oid;
1115
1112 - if (argc < 2)
1113 - die("submodule--helper push-check requires at least 1 argument");
1116 + if (argc < 3)
1117 + die("submodule--helper push-check requires at least 2 arguments");
1118 +
1119 + /*
1120 + * superproject's resolved head ref.
1121 + * if HEAD then the superproject is in a detached head state, otherwise
1122 + * it will be the resolved head ref.
1123 + */
1124 + superproject_head = argv[1];
1125 + argv++;
1126 + argc--;
1127 + /* Get the submodule's head ref and determine if it is detached */
1128 + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
1129 + if (!head)
1130 + die(_("Failed to resolve HEAD as a valid ref."));
1131 + if (!strcmp(head, "HEAD"))
1132 + detached_head = 1;
1133
1134 /*
1135 * The remote must be configured.
@@ -1133,18 +1152,30 @@ static int push_check(int argc, const char **argv, const char *prefix)
1152 if (rs->pattern || rs->matching)
1153 continue;
1154
1136 - /*
1137 - * LHS must match a single ref
1138 - * NEEDSWORK: add logic to special case 'HEAD' once
1139 - * working with submodules in a detached head state
1140 - * ceases to be the norm.
1141 - */
1142 - if (count_refspec_match(rs->src, local_refs, NULL) != 1)
1155 + /* LHS must match a single ref */
1156 + switch (count_refspec_match(rs->src, local_refs, NULL)) {
1157 + case 1:
1158 + break;
1159 + case 0:
1160 + /*
1161 + * If LHS matches 'HEAD' then we need to ensure
1162 + * that it matches the same named branch
1163 + * checked out in the superproject.
1164 + */
1165 + if (!strcmp(rs->src, "HEAD")) {
1166 + if (!detached_head &&
1167 + !strcmp(head, superproject_head))
1168 + break;
1169 + die("HEAD does not match the named branch in the superproject");
1170 + }
1171 + default:
1172 die("src refspec '%s' must name a ref",
1173 rs->src);
1174 + }
1175 }
1176 free_refspec(refspec_nr, refspec);
1177 }
1178 + free(head);
1179
1180 return 0;
1181 }
submodule.c
+15 -3
@@ -828,7 +828,8 @@ static int push_submodule(const char *path,
828 * Perform a check in the submodule to see if the remote and refspec work.
829 * Die if the submodule can't be pushed.
830 */
831 -static void submodule_push_check(const char *path, const struct remote *remote,
831 +static void submodule_push_check(const char *path, const char *head,
832 + const struct remote *remote,
833 const char **refspec, int refspec_nr)
834 {
835 struct child_process cp = CHILD_PROCESS_INIT;
@@ -836,6 +837,7 @@ static void submodule_push_check(const char *path, const struct remote *remote,
837
838 argv_array_push(&cp.args, "submodule--helper");
839 argv_array_push(&cp.args, "push-check");
840 + argv_array_push(&cp.args, head);
841 argv_array_push(&cp.args, remote->name);
842
843 for (i = 0; i < refspec_nr; i++)
@@ -874,10 +876,20 @@ int push_unpushed_submodules(struct sha1_array *commits,
876 * won't be propagated due to the remote being unconfigured (e.g. a URL
877 * instead of a remote name).
878 */
877 - if (remote->origin != REMOTE_UNCONFIGURED)
879 + if (remote->origin != REMOTE_UNCONFIGURED) {
880 + char *head;
881 + struct object_id head_oid;
882 +
883 + head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
884 + if (!head)
885 + die(_("Failed to resolve HEAD as a valid ref."));
886 +
887 for (i = 0; i < needs_pushing.nr; i++)
888 submodule_push_check(needs_pushing.items[i].string,
880 - remote, refspec, refspec_nr);
889 + head, remote,
890 + refspec, refspec_nr);
891 + free(head);
892 + }
893
894 /* Actually push the submodules */
895 for (i = 0; i < needs_pushing.nr; i++) {
t/t5531-deep-submodule-push.sh
+24 -1
@@ -512,7 +512,8 @@ test_expect_success 'push propagating refspec to a submodule' '
512 # Fails when refspec includes an object id
513 test_must_fail git -C work push --recurse-submodules=on-demand origin \
514 "$(git -C work rev-parse branch2):refs/heads/branch2" &&
515 - # Fails when refspec includes 'HEAD' as it is unsupported at this time
515 + # Fails when refspec includes HEAD and parent and submodule do not
516 + # have the same named branch checked out
517 test_must_fail git -C work push --recurse-submodules=on-demand origin \
518 HEAD:refs/heads/branch2 &&
519
@@ -527,4 +528,26 @@ test_expect_success 'push propagating refspec to a submodule' '
528 test_cmp expected_pub actual_pub
529 '
530
531 +test_expect_success 'push propagating HEAD refspec to a submodule' '
532 + git -C work/gar/bage checkout branch2 &&
533 + > work/gar/bage/junk12 &&
534 + git -C work/gar/bage add junk12 &&
535 + git -C work/gar/bage commit -m "Twelfth junk" &&
536 +
537 + git -C work checkout branch2 &&
538 + git -C work add gar/bage &&
539 + git -C work commit -m "updating gar/bage in branch2" &&
540 +
541 + # Passes since the superproject and submodules HEAD are both on branch2
542 + git -C work push --recurse-submodules=on-demand origin \
543 + HEAD:refs/heads/branch2 &&
544 +
545 + git -C submodule.git rev-parse branch2 >actual_submodule &&
546 + git -C pub.git rev-parse branch2 >actual_pub &&
547 + git -C work/gar/bage rev-parse branch2 >expected_submodule &&
548 + git -C work rev-parse branch2 >expected_pub &&
549 + test_cmp expected_submodule actual_submodule &&
550 + test_cmp expected_pub actual_pub
551 +'
552 +
553 test_done