From: Junio C Hamano Date: Fri, 2 Sep 2011 20:07:58 +0000 (-0700) Subject: Merge branch 'fg/submodule-ff-check-before-push' X-Git-Tag: v1.7.7-rc1~25 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/c63750abc30c017d13d6be9e5a90d6d2c56b60b6?hp=-c Merge branch 'fg/submodule-ff-check-before-push' * fg/submodule-ff-check-before-push: push: Don't push a repository with unpushed submodules --- c63750abc30c017d13d6be9e5a90d6d2c56b60b6 diff --combined Documentation/git-push.txt index 49c6e9fa51,6ae6ba3c2e..aede48877f --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@@ -162,6 -162,12 +162,12 @@@ useful if you write an alias or script is specified. This flag forces progress status even if the standard error stream is not directed to a terminal. + --recurse-submodules=check:: + Check whether all submodule commits used by the revisions to be + pushed are available on a remote tracking branch. Otherwise the + push will be aborted and the command will exit with non-zero status. + + include::urls-remotes.txt[] OUTPUT @@@ -327,12 -333,12 +333,12 @@@ a case where you do mean to lose histor Examples -------- -git push:: +`git push`:: Works like `git push `, where is the current branch's remote (or `origin`, if no remote is configured for the current branch). -git push origin:: +`git push origin`:: Without additional configuration, works like `git push origin :`. + @@@ -344,45 -350,45 +350,45 @@@ use `git config remote.origin.push HEAD the ones in the examples below) can be configured as the default for `git push origin`. -git push origin ::: +`git push origin :`:: Push "matching" branches to `origin`. See in the <> section above for a description of "matching" branches. -git push origin master:: +`git push origin master`:: Find a ref that matches `master` in the source repository (most likely, it would find `refs/heads/master`), and update the same ref (e.g. `refs/heads/master`) in `origin` repository with it. If `master` did not exist remotely, it would be created. -git push origin HEAD:: +`git push origin HEAD`:: A handy way to push the current branch to the same name on the remote. -git push origin master:satellite/master dev:satellite/dev:: +`git push origin master:satellite/master dev:satellite/dev`:: Use the source ref that matches `master` (e.g. `refs/heads/master`) to update the ref that matches `satellite/master` (most probably `refs/remotes/satellite/master`) in the `origin` repository, then do the same for `dev` and `satellite/dev`. -git push origin HEAD:master:: +`git push origin HEAD:master`:: Push the current branch to the remote ref matching `master` in the `origin` repository. This form is convenient to push the current branch without thinking about its local name. -git push origin master:refs/heads/experimental:: +`git push origin master:refs/heads/experimental`:: Create the branch `experimental` in the `origin` repository by copying the current `master` branch. This form is only needed to create a new branch or tag in the remote repository when the local name and the remote name are different; otherwise, the ref name on its own will work. -git push origin :experimental:: +`git push origin :experimental`:: Find a ref that matches `experimental` in the `origin` repository (e.g. `refs/heads/experimental`), and delete it. -git push origin {plus}dev:master:: +`git push origin {plus}dev:master`:: Update the origin repository's master branch with the dev branch, allowing non-fast-forward updates. *This can leave unreferenced commits dangling in the origin repository.* Consider the diff --combined submodule.c index f71d5ed8b0,45f508c6fd..7a76edf911 --- a/submodule.c +++ b/submodule.c @@@ -32,7 -32,7 +32,7 @@@ static int add_submodule_odb(const cha const char *git_dir; strbuf_addf(&objects_directory, "%s/.git", path); - git_dir = read_gitfile_gently(objects_directory.buf); + git_dir = read_gitfile(objects_directory.buf); if (git_dir) { strbuf_reset(&objects_directory); strbuf_addstr(&objects_directory, git_dir); @@@ -308,6 -308,114 +308,114 @@@ void set_config_fetch_recurse_submodule 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; @@@ -479,7 -587,7 +587,7 @@@ int fetch_populated_submodules(int num_ strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name); strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf); strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name); - git_dir = read_gitfile_gently(submodule_git_dir.buf); + git_dir = read_gitfile(submodule_git_dir.buf); if (!git_dir) git_dir = submodule_git_dir.buf; if (is_directory(git_dir)) { @@@ -517,7 -625,7 +625,7 @@@ unsigned is_submodule_modified(const ch const char *git_dir; strbuf_addf(&buf, "%s/.git", path); - git_dir = read_gitfile_gently(buf.buf); + git_dir = read_gitfile(buf.buf); if (!git_dir) git_dir = buf.buf; if (!is_directory(git_dir)) { diff --combined transport.c index 98c577804f,fa279d531f..d2725e57dc --- a/transport.c +++ b/transport.c @@@ -10,6 -10,7 +10,7 @@@ #include "refs.h" #include "branch.h" #include "url.h" + #include "submodule.h" /* rsync support */ @@@ -482,18 -483,14 +483,18 @@@ static int set_git_option(struct git_tr static int connect_setup(struct transport *transport, int for_push, int verbose) { struct git_transport_data *data = transport->data; + struct strbuf sb = STRBUF_INIT; if (data->conn) return 0; - data->conn = git_connect(data->fd, transport->url, - for_push ? data->options.receivepack : - data->options.uploadpack, + strbuf_addstr(&sb, for_push ? data->options.receivepack : + data->options.uploadpack); + if (for_push && transport->verbose < 0) + strbuf_addstr(&sb, " --quiet"); + data->conn = git_connect(data->fd, transport->url, sb.buf, verbose ? CONNECT_VERBOSE : 0); + strbuf_release(&sb); return 0; } @@@ -1045,6 -1042,14 +1046,14 @@@ int transport_push(struct transport *tr flags & TRANSPORT_PUSH_MIRROR, flags & TRANSPORT_PUSH_FORCE); + if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) { + struct ref *ref = remote_refs; + for (; ref; ref = ref->next) + if (!is_null_sha1(ref->new_sha1) && + check_submodule_needs_pushing(ref->new_sha1,transport->remote->name)) + die("There are unpushed submodules, aborting."); + } + push_ret = transport->push_refs(transport, remote_refs, flags); err = push_had_errors(remote_refs); ret = push_ret | err;