From d2b17b3220cbd75b1c8e2aa6cfdc5c121749c97d Mon Sep 17 00:00:00 2001 From: Fredrik Gustafsson Date: Sat, 20 Aug 2011 00:08:47 +0200 Subject: 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 Mentored-by: Jens Lehmann Mentored-by: Heiko Voigt Signed-off-by: Junio C Hamano --- submodule.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'submodule.c') diff --git a/submodule.c b/submodule.c index 1ba9646d34..45f508c6fd 100644 --- a/submodule.c +++ b/submodule.c @@ -308,6 +308,114 @@ void set_config_fetch_recurse_submodules(int value) config_fetch_recurse_submodules = value; } +static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +{ + return 1; +} + +static int submodule_needs_pushing(const char *path, const unsigned char sha1[20]) +{ + if (add_submodule_odb(path) || !lookup_commit_reference(sha1)) + return 0; + + if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) { + struct child_process cp; + const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL}; + struct strbuf buf = STRBUF_INIT; + int needs_pushing = 0; + + argv[1] = sha1_to_hex(sha1); + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.out = -1; + cp.dir = path; + if (start_command(&cp)) + die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s", + sha1_to_hex(sha1), path); + if (strbuf_read(&buf, cp.out, 41)) + needs_pushing = 1; + finish_command(&cp); + close(cp.out); + strbuf_release(&buf); + return needs_pushing; + } + + return 0; +} + +static void collect_submodules_from_diff(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + int *needs_pushing = data; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (!S_ISGITLINK(p->two->mode)) + continue; + if (submodule_needs_pushing(p->two->path, p->two->sha1)) { + *needs_pushing = 1; + break; + } + } +} + + +static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing) +{ + const unsigned char (*parents)[20]; + unsigned int i, n; + struct rev_info rev; + + n = commit_list_count(parent); + parents = xmalloc(n * sizeof(*parents)); + + for (i = 0; i < n; i++) { + hashcpy((unsigned char *)(parents + i), parent->item->object.sha1); + parent = parent->next; + } + + init_revisions(&rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = collect_submodules_from_diff; + rev.diffopt.format_callback_data = needs_pushing; + diff_tree_combined(commit->object.sha1, parents, n, 1, &rev); + + free(parents); +} + +int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name) +{ + struct rev_info rev; + struct commit *commit; + const char *argv[] = {NULL, NULL, "--not", "NULL", NULL}; + int argc = ARRAY_SIZE(argv) - 1; + char *sha1_copy; + int needs_pushing = 0; + struct strbuf remotes_arg = STRBUF_INIT; + + strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name); + init_revisions(&rev, NULL); + sha1_copy = xstrdup(sha1_to_hex(new_sha1)); + argv[1] = sha1_copy; + argv[3] = remotes_arg.buf; + setup_revisions(argc, argv, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + while ((commit = get_revision(&rev)) && !needs_pushing) + commit_need_pushing(commit, commit->parents, &needs_pushing); + + free(sha1_copy); + strbuf_release(&remotes_arg); + + return needs_pushing; +} + static int is_submodule_commit_present(const char *path, unsigned char sha1[20]) { int is_present = 0; -- cgit v1.2.1