Skip to content

Commit 419f2ec

Browse files
committed
Merge branch 'hv/submodule-recurse-push'
"git push --recurse-submodules" learns to optionally look into the histories of submodules bound to the superproject and push them out. By Heiko Voigt * hv/submodule-recurse-push: push: teach --recurse-submodules the on-demand option Refactor submodule push check to use string list instead of integer Teach revision walking machinery to walk multiple times sequencially
2 parents 8c3a534 + eb21c73 commit 419f2ec

16 files changed

+341
-21
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
/test-mktemp
186186
/test-parse-options
187187
/test-path-utils
188+
/test-revision-walking
188189
/test-run-command
189190
/test-sha1
190191
/test-sigchain

Documentation/git-push.txt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,16 @@ useful if you write an alias or script around 'git push'.
170170
is specified. This flag forces progress status even if the
171171
standard error stream is not directed to a terminal.
172172

173-
--recurse-submodules=check::
174-
Check whether all submodule commits used by the revisions to be
175-
pushed are available on a remote tracking branch. Otherwise the
176-
push will be aborted and the command will exit with non-zero status.
173+
--recurse-submodules=check|on-demand::
174+
Make sure all submodule commits used by the revisions to be
175+
pushed are available on a remote tracking branch. If 'check' is
176+
used git will verify that all submodule commits that changed in
177+
the revisions to be pushed are available on at least one remote
178+
of the submodule. If any commits are missing the push will be
179+
aborted and exit with non-zero status. If 'on-demand' is used
180+
all submodules that changed in the revisions to be pushed will
181+
be pushed. If on-demand was not able to push all necessary
182+
revisions it will also be aborted and exit with non-zero status.
177183

178184

179185
include::urls-remotes.txt[]

Documentation/technical/api-revision-walking.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ function.
5656
returning a `struct commit *` each time you call it. The end of the
5757
revision list is indicated by returning a NULL pointer.
5858

59+
`reset_revision_walk`::
60+
61+
Reset the flags used by the revision walking api. You can use
62+
this to do multiple sequencial revision walks.
63+
5964
Data structures
6065
---------------
6166

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
485485
TEST_PROGRAMS_NEED_X += test-mktemp
486486
TEST_PROGRAMS_NEED_X += test-parse-options
487487
TEST_PROGRAMS_NEED_X += test-path-utils
488+
TEST_PROGRAMS_NEED_X += test-revision-walking
488489
TEST_PROGRAMS_NEED_X += test-run-command
489490
TEST_PROGRAMS_NEED_X += test-sha1
490491
TEST_PROGRAMS_NEED_X += test-sigchain

builtin/push.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,21 @@ static int option_parse_recurse_submodules(const struct option *opt,
284284
const char *arg, int unset)
285285
{
286286
int *flags = opt->value;
287+
288+
if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
289+
TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
290+
die("%s can only be used once.", opt->long_name);
291+
287292
if (arg) {
288293
if (!strcmp(arg, "check"))
289294
*flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
295+
else if (!strcmp(arg, "on-demand"))
296+
*flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
290297
else
291298
die("bad %s argument: %s", opt->long_name, arg);
292299
} else
293-
die("option %s needs an argument (check)", opt->long_name);
300+
die("option %s needs an argument (check|on-demand)",
301+
opt->long_name);
294302

295303
return 0;
296304
}

object.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,14 @@ void object_array_remove_duplicates(struct object_array *array)
286286
array->nr = dst;
287287
}
288288
}
289+
290+
void clear_object_flags(unsigned flags)
291+
{
292+
int i;
293+
294+
for (i=0; i < obj_hash_size; i++) {
295+
struct object *obj = obj_hash[i];
296+
if (obj)
297+
obj->flags &= ~flags;
298+
}
299+
}

object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,6 @@ void add_object_array(struct object *obj, const char *name, struct object_array
7676
void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
7777
void object_array_remove_duplicates(struct object_array *);
7878

79+
void clear_object_flags(unsigned flags);
80+
7981
#endif /* OBJECT_H */

revision.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,6 +2062,11 @@ static void set_children(struct rev_info *revs)
20622062
}
20632063
}
20642064

2065+
void reset_revision_walk(void)
2066+
{
2067+
clear_object_flags(SEEN | ADDED | SHOWN);
2068+
}
2069+
20652070
int prepare_revision_walk(struct rev_info *revs)
20662071
{
20672072
int nr = revs->pending.nr;

revision.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ct
192192
const char * const usagestr[]);
193193
extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
194194

195+
extern void reset_revision_walk(void);
195196
extern int prepare_revision_walk(struct rev_info *revs);
196197
extern struct commit *get_revision(struct rev_info *revs);
197198
extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);

submodule.c

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -357,21 +357,19 @@ static void collect_submodules_from_diff(struct diff_queue_struct *q,
357357
void *data)
358358
{
359359
int i;
360-
int *needs_pushing = data;
360+
struct string_list *needs_pushing = data;
361361

362362
for (i = 0; i < q->nr; i++) {
363363
struct diff_filepair *p = q->queue[i];
364364
if (!S_ISGITLINK(p->two->mode))
365365
continue;
366-
if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
367-
*needs_pushing = 1;
368-
break;
369-
}
366+
if (submodule_needs_pushing(p->two->path, p->two->sha1))
367+
string_list_insert(needs_pushing, p->two->path);
370368
}
371369
}
372370

373-
374-
static void commit_need_pushing(struct commit *commit, int *needs_pushing)
371+
static void find_unpushed_submodule_commits(struct commit *commit,
372+
struct string_list *needs_pushing)
375373
{
376374
struct rev_info rev;
377375

@@ -382,14 +380,15 @@ static void commit_need_pushing(struct commit *commit, int *needs_pushing)
382380
diff_tree_combined_merge(commit, 1, &rev);
383381
}
384382

385-
int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
383+
int find_unpushed_submodules(unsigned char new_sha1[20],
384+
const char *remotes_name, struct string_list *needs_pushing)
386385
{
387386
struct rev_info rev;
388387
struct commit *commit;
389388
const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
390389
int argc = ARRAY_SIZE(argv) - 1;
391390
char *sha1_copy;
392-
int needs_pushing = 0;
391+
393392
struct strbuf remotes_arg = STRBUF_INIT;
394393

395394
strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
@@ -401,13 +400,62 @@ int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remote
401400
if (prepare_revision_walk(&rev))
402401
die("revision walk setup failed");
403402

404-
while ((commit = get_revision(&rev)) && !needs_pushing)
405-
commit_need_pushing(commit, &needs_pushing);
403+
while ((commit = get_revision(&rev)) != NULL)
404+
find_unpushed_submodule_commits(commit, needs_pushing);
406405

406+
reset_revision_walk();
407407
free(sha1_copy);
408408
strbuf_release(&remotes_arg);
409409

410-
return needs_pushing;
410+
return needs_pushing->nr;
411+
}
412+
413+
static int push_submodule(const char *path)
414+
{
415+
if (add_submodule_odb(path))
416+
return 1;
417+
418+
if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
419+
struct child_process cp;
420+
const char *argv[] = {"push", NULL};
421+
422+
memset(&cp, 0, sizeof(cp));
423+
cp.argv = argv;
424+
cp.env = local_repo_env;
425+
cp.git_cmd = 1;
426+
cp.no_stdin = 1;
427+
cp.dir = path;
428+
if (run_command(&cp))
429+
return 0;
430+
close(cp.out);
431+
}
432+
433+
return 1;
434+
}
435+
436+
int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)
437+
{
438+
int i, ret = 1;
439+
struct string_list needs_pushing;
440+
441+
memset(&needs_pushing, 0, sizeof(struct string_list));
442+
needs_pushing.strdup_strings = 1;
443+
444+
if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))
445+
return 1;
446+
447+
for (i = 0; i < needs_pushing.nr; i++) {
448+
const char *path = needs_pushing.items[i].string;
449+
fprintf(stderr, "Pushing submodule '%s'\n", path);
450+
if (!push_submodule(path)) {
451+
fprintf(stderr, "Unable to push submodule '%s'\n", path);
452+
ret = 0;
453+
}
454+
}
455+
456+
string_list_clear(&needs_pushing, 0);
457+
458+
return ret;
411459
}
412460

413461
static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
@@ -741,6 +789,7 @@ static int find_first_merges(struct object_array *result, const char *path,
741789
if (in_merge_bases(b, &commit, 1))
742790
add_object_array(o, NULL, &merges);
743791
}
792+
reset_revision_walk();
744793

745794
/* Now we've got all merges that contain a and b. Prune all
746795
* merges that contain another found merge and save them in

0 commit comments

Comments
 (0)