push: Don't push a repository with unpushed submodules

When working with submodules it is easy to forget to push a submodule to the server but pushing a super-project that contains a commit for that submodule. The result is that the superproject points at a submodule commit that is not available on the server. This adds the option --recurse-submodules=check to push. When using this option git will check that all submodule commits that are about to be pushed are present on a remote of the submodule. To be able to use a combined diff, disabling a diff callback has been removed from combined-diff.c. Signed-off-by: Fredrik Gustafsson <iveqy@iveqy.com> Mentored-by: Jens Lehmann <Jens.Lehmann@web.de> Mentored-by: Heiko Voigt <hvoigt@hvoigt.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Fredrik Gustafsson committed Aug 20, 2011 at 00:08 UTC d2b17b3220cbd75b1c8e2aa6cfdc5c121749c97d
7 files changed +231
Documentation/git-push.txt
+6
@@ -162,6 +162,12 @@ useful if you write an alias or script around 'git push'.
162 is specified. This flag forces progress status even if the
163 standard error stream is not directed to a terminal.
164
165 +--recurse-submodules=check::
166 + Check whether all submodule commits used by the revisions to be
167 + pushed are available on a remote tracking branch. Otherwise the
168 + push will be aborted and the command will exit with non-zero status.
169 +
170 +
171 include::urls-remotes.txt[]
172
173 OUTPUT
builtin/push.c
+19
@@ -8,6 +8,7 @@
8 #include "remote.h"
9 #include "transport.h"
10 #include "parse-options.h"
11 +#include "submodule.h"
12
13 static const char * const push_usage[] = {
14 "git push [<options>] [<repository> [<refspec>...]]",
@@ -219,6 +220,21 @@ static int do_push(const char *repo, int flags)
220 return !!errs;
221 }
222
223 +static int option_parse_recurse_submodules(const struct option *opt,
224 + const char *arg, int unset)
225 +{
226 + int *flags = opt->value;
227 + if (arg) {
228 + if (!strcmp(arg, "check"))
229 + *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
230 + else
231 + die("bad %s argument: %s", opt->long_name, arg);
232 + } else
233 + die("option %s needs an argument (check)", opt->long_name);
234 +
235 + return 0;
236 +}
237 +
238 int cmd_push(int argc, const char **argv, const char *prefix)
239 {
240 int flags = 0;
@@ -236,6 +252,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
252 OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
253 OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
254 OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
255 + { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check",
256 + "controls recursive pushing of submodules",
257 + PARSE_OPT_OPTARG, option_parse_recurse_submodules },
258 OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
259 OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
260 OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
submodule.c
+108
@@ -308,6 +308,114 @@ void set_config_fetch_recurse_submodules(int value)
308 config_fetch_recurse_submodules = value;
309 }
310
311 +static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
312 +{
313 + return 1;
314 +}
315 +
316 +static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
317 +{
318 + if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
319 + return 0;
320 +
321 + if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
322 + struct child_process cp;
323 + const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
324 + struct strbuf buf = STRBUF_INIT;
325 + int needs_pushing = 0;
326 +
327 + argv[1] = sha1_to_hex(sha1);
328 + memset(&cp, 0, sizeof(cp));
329 + cp.argv = argv;
330 + cp.env = local_repo_env;
331 + cp.git_cmd = 1;
332 + cp.no_stdin = 1;
333 + cp.out = -1;
334 + cp.dir = path;
335 + if (start_command(&cp))
336 + die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s",
337 + sha1_to_hex(sha1), path);
338 + if (strbuf_read(&buf, cp.out, 41))
339 + needs_pushing = 1;
340 + finish_command(&cp);
341 + close(cp.out);
342 + strbuf_release(&buf);
343 + return needs_pushing;
344 + }
345 +
346 + return 0;
347 +}
348 +
349 +static void collect_submodules_from_diff(struct diff_queue_struct *q,
350 + struct diff_options *options,
351 + void *data)
352 +{
353 + int i;
354 + int *needs_pushing = data;
355 +
356 + for (i = 0; i < q->nr; i++) {
357 + struct diff_filepair *p = q->queue[i];
358 + if (!S_ISGITLINK(p->two->mode))
359 + continue;
360 + if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
361 + *needs_pushing = 1;
362 + break;
363 + }
364 + }
365 +}
366 +
367 +
368 +static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing)
369 +{
370 + const unsigned char (*parents)[20];
371 + unsigned int i, n;
372 + struct rev_info rev;
373 +
374 + n = commit_list_count(parent);
375 + parents = xmalloc(n * sizeof(*parents));
376 +
377 + for (i = 0; i < n; i++) {
378 + hashcpy((unsigned char *)(parents + i), parent->item->object.sha1);
379 + parent = parent->next;
380 + }
381 +
382 + init_revisions(&rev, NULL);
383 + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
384 + rev.diffopt.format_callback = collect_submodules_from_diff;
385 + rev.diffopt.format_callback_data = needs_pushing;
386 + diff_tree_combined(commit->object.sha1, parents, n, 1, &rev);
387 +
388 + free(parents);
389 +}
390 +
391 +int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
392 +{
393 + struct rev_info rev;
394 + struct commit *commit;
395 + const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
396 + int argc = ARRAY_SIZE(argv) - 1;
397 + char *sha1_copy;
398 + int needs_pushing = 0;
399 + struct strbuf remotes_arg = STRBUF_INIT;
400 +
401 + strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
402 + init_revisions(&rev, NULL);
403 + sha1_copy = xstrdup(sha1_to_hex(new_sha1));
404 + argv[1] = sha1_copy;
405 + argv[3] = remotes_arg.buf;
406 + setup_revisions(argc, argv, &rev, NULL);
407 + if (prepare_revision_walk(&rev))
408 + die("revision walk setup failed");
409 +
410 + while ((commit = get_revision(&rev)) && !needs_pushing)
411 + commit_need_pushing(commit, commit->parents, &needs_pushing);
412 +
413 + free(sha1_copy);
414 + strbuf_release(&remotes_arg);
415 +
416 + return needs_pushing;
417 +}
418 +
419 static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
420 {
421 int is_present = 0;
submodule.h
+1
@@ -29,5 +29,6 @@ int fetch_populated_submodules(int num_options, const char **options,
29 unsigned is_submodule_modified(const char *path, int ignore_untracked);
30 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
31 const unsigned char a[20], const unsigned char b[20]);
32 +int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
33
34 #endif
t/t5531-deep-submodule-push.sh
+87
@@ -32,4 +32,91 @@ test_expect_success push '
32 )
33 '
34
35 +test_expect_success 'push if submodule has no remote' '
36 + (
37 + cd work/gar/bage &&
38 + >junk2 &&
39 + git add junk2 &&
40 + git commit -m "Second junk"
41 + ) &&
42 + (
43 + cd work &&
44 + git add gar/bage &&
45 + git commit -m "Second commit for gar/bage" &&
46 + git push --recurse-submodules=check ../pub.git master
47 + )
48 +'
49 +
50 +test_expect_success 'push fails if submodule commit not on remote' '
51 + (
52 + cd work/gar &&
53 + git clone --bare bage ../../submodule.git &&
54 + cd bage &&
55 + git remote add origin ../../../submodule.git &&
56 + git fetch &&
57 + >junk3 &&
58 + git add junk3 &&
59 + git commit -m "Third junk"
60 + ) &&
61 + (
62 + cd work &&
63 + git add gar/bage &&
64 + git commit -m "Third commit for gar/bage" &&
65 + test_must_fail git push --recurse-submodules=check ../pub.git master
66 + )
67 +'
68 +
69 +test_expect_success 'push succeeds after commit was pushed to remote' '
70 + (
71 + cd work/gar/bage &&
72 + git push origin master
73 + ) &&
74 + (
75 + cd work &&
76 + git push --recurse-submodules=check ../pub.git master
77 + )
78 +'
79 +
80 +test_expect_success 'push fails when commit on multiple branches if one branch has no remote' '
81 + (
82 + cd work/gar/bage &&
83 + >junk4 &&
84 + git add junk4 &&
85 + git commit -m "Fourth junk"
86 + ) &&
87 + (
88 + cd work &&
89 + git branch branch2 &&
90 + git add gar/bage &&
91 + git commit -m "Fourth commit for gar/bage" &&
92 + git checkout branch2 &&
93 + (
94 + cd gar/bage &&
95 + git checkout HEAD~1
96 + ) &&
97 + >junk1 &&
98 + git add junk1 &&
99 + git commit -m "First junk" &&
100 + test_must_fail git push --recurse-submodules=check ../pub.git
101 + )
102 +'
103 +
104 +test_expect_success 'push succeeds if submodule has no remote and is on the first superproject commit' '
105 + git init --bare a
106 + git clone a a1 &&
107 + (
108 + cd a1 &&
109 + git init b
110 + (
111 + cd b &&
112 + >junk &&
113 + git add junk &&
114 + git commit -m "initial"
115 + ) &&
116 + git add b &&
117 + git commit -m "added submodule" &&
118 + git push --recurse-submodule=check origin master
119 + )
120 +'
121 +
122 test_done
transport.c
+9
@@ -10,6 +10,7 @@
10 #include "refs.h"
11 #include "branch.h"
12 #include "url.h"
13 +#include "submodule.h"
14
15 /* rsync support */
16
@@ -1041,6 +1042,14 @@ int transport_push(struct transport *transport,
1042 flags & TRANSPORT_PUSH_MIRROR,
1043 flags & TRANSPORT_PUSH_FORCE);
1044
1045 + if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {
1046 + struct ref *ref = remote_refs;
1047 + for (; ref; ref = ref->next)
1048 + if (!is_null_sha1(ref->new_sha1) &&
1049 + check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))
1050 + die("There are unpushed submodules, aborting.");
1051 + }
1052 +
1053 push_ret = transport->push_refs(transport, remote_refs, flags);
1054 err = push_had_errors(remote_refs);
1055 ret = push_ret | err;
transport.h
+1
@@ -101,6 +101,7 @@ struct transport {
101 #define TRANSPORT_PUSH_MIRROR 8
102 #define TRANSPORT_PUSH_PORCELAIN 16
103 #define TRANSPORT_PUSH_SET_UPSTREAM 32
104 +#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
105
106 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
107