From: Jens Lehmann <Jens.Lehmann@xxxxxx> Date: Fri, 5 Apr 2013 18:35:27 +0200 Signed-off-by: Jens Lehmann <Jens.Lehmann@xxxxxx> Signed-off-by: Jonathan Nieder <jrnieder@xxxxxxxxx> --- Also neat, also would benefit from documentation or tests. entry.c | 15 ++++++++-- submodule.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ submodule.h | 3 ++ unpack-trees.c | 69 ++++++++++++++++++++++++++++++++++++---------- unpack-trees.h | 1 + 5 files changed, 157 insertions(+), 17 deletions(-) diff --git a/entry.c b/entry.c index d1bf6ec..61a2767 100644 --- a/entry.c +++ b/entry.c @@ -265,7 +265,7 @@ int checkout_entry(struct cache_entry *ce, if (!check_path(path, len, &st, state->base_dir_len)) { unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); - if (!changed) + if (!changed && (!S_ISDIR(st.st_mode) || !S_ISGITLINK(ce->ce_mode))) return 0; if (!state->force) { if (!state->quiet) @@ -280,9 +280,18 @@ int checkout_entry(struct cache_entry *ce, * just do the right thing) */ if (S_ISDIR(st.st_mode)) { - /* If it is a gitlink, leave it alone! */ - if (S_ISGITLINK(ce->ce_mode)) + if (S_ISGITLINK(ce->ce_mode)) { + if (submodule_needs_update(ce->name)) { + if (is_submodule_populated(ce->name)) { + if (update_submodule(ce->name, ce->sha1, state->force)) + return error("cannot checkout submodule %s", path); + } else { + if (populate_submodule(path, ce->sha1, state->force)) + return error("cannot populate submodule %s", path); + } + } return 0; + } if (!state->force) return error("%s is a directory", path); remove_subtree(path); diff --git a/submodule.c b/submodule.c index 06df5ae..3365987 100644 --- a/submodule.c +++ b/submodule.c @@ -485,6 +485,42 @@ int depopulate_submodule(const char *path) return 0; } +int update_submodule(const char *path, const unsigned char sha1[20], int force) +{ + struct strbuf buf = STRBUF_INIT; + struct child_process cp; + const char *hex_sha1 = sha1_to_hex(sha1); + const char *argv[] = { + "checkout", + force ? "-fq" : "-q", + hex_sha1, + NULL, + }; + const char *git_dir; + + strbuf_addf(&buf, "%s/.git", path); + git_dir = read_gitfile(buf.buf); + if (!git_dir) + git_dir = buf.buf; + if (!is_directory(git_dir)) { + strbuf_release(&buf); + /* The submodule is not populated, so we can't check it out */ + return 0; + } + strbuf_release(&buf); + + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; /* GIT_WORK_TREE doesn't work for git checkout */ + if (run_command(&cp)) + return error("Could not checkout submodule %s", path); + + return 0; +} + void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], @@ -926,6 +962,17 @@ out: return result; } +int is_submodule_populated(const char *path) +{ + int retval = 0; + struct strbuf gitdir = STRBUF_INIT; + strbuf_addf(&gitdir, "%s/.git", path); + if (resolve_gitdir(gitdir.buf)) + retval = 1; + strbuf_release(&gitdir); + return retval; +} + unsigned is_submodule_modified(const char *path, int ignore_untracked) { ssize_t len; @@ -1075,6 +1122,45 @@ int ok_to_remove_submodule(const char *path) return ok_to_remove; } +unsigned is_submodule_checkout_safe(const char *path, const unsigned char sha1[20]) +{ + struct strbuf buf = STRBUF_INIT; + struct child_process cp; + const char *hex_sha1 = sha1_to_hex(sha1); + const char *argv[] = { + "read-tree", + "-n", + "-m", + "HEAD", + hex_sha1, + NULL, + }; + const char *git_dir; + + strbuf_addf(&buf, "%s/.git", path); + git_dir = read_gitfile(buf.buf); + if (!git_dir) + git_dir = buf.buf; + if (!is_directory(git_dir)) { + strbuf_release(&buf); + /* The submodule is not populated, it's safe to check it out */ + /* + * TODO: When git learns to re-populate submodules, a check must be + * added here to assert that no local files will be overwritten. + */ + return 1; + } + strbuf_release(&buf); + + memset(&cp, 0, sizeof(cp)); + cp.argv = argv; + cp.env = local_repo_env; + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.dir = path; + return run_command(&cp) == 0; +} + static int find_first_merges(struct object_array *result, const char *path, struct commit *a, struct commit *b) { diff --git a/submodule.h b/submodule.h index 3657ca8..b42ae91 100644 --- a/submodule.h +++ b/submodule.h @@ -26,6 +26,7 @@ int parse_update_recurse_submodules_arg(const char *opt, const char *arg); int submodule_needs_update(const char *path); int populate_submodule(const char *path, unsigned char sha1[20], int force); int depopulate_submodule(const char *path); +int update_submodule(const char *path, const unsigned char sha1[20], int force); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], @@ -37,9 +38,11 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]); int fetch_populated_submodules(const struct argv_array *options, const char *prefix, int command_line_option, int quiet); +int is_submodule_populated(const char *path); unsigned is_submodule_modified(const char *path, int ignore_untracked); int submodule_uses_gitfile(const char *path); int ok_to_remove_submodule(const char *path); +unsigned is_submodule_checkout_safe(const char *path, const unsigned char sha1[20]); int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20], const unsigned char a[20], const unsigned char b[20], int search); int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name, diff --git a/unpack-trees.c b/unpack-trees.c index ed48d41..fc8855e 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -27,6 +27,9 @@ static const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = { /* ERROR_NOT_UPTODATE_DIR */ "Updating '%s' would lose untracked files in it", + /* ERROR_NOT_UPTODATE_SUBMODULE */ + "Updating submodule '%s' would lose modifications in it", + /* ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN */ "Untracked working tree file '%s' would be overwritten by merge.", @@ -71,6 +74,8 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, msgs[ERROR_NOT_UPTODATE_DIR] = "Updating the following directories would lose untracked files in it:\n%s"; + msgs[ERROR_NOT_UPTODATE_SUBMODULE] = + "Updating the following submodules would lose modifications in it:\n%s"; if (advice_commit_before_merge) msg = "The following untracked working tree files would be %s by %s:\n%%s" @@ -1221,17 +1226,15 @@ static int verify_uptodate_1(const struct cache_entry *ce, if (!lstat(ce->name, &st)) { int flags = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE; unsigned changed = ie_match_stat(o->src_index, ce, &st, flags); - if (!changed) - return 0; - /* - * NEEDSWORK: the current default policy is to allow - * submodule to be out of sync wrt the superproject - * index. This needs to be tightened later for - * submodules that are marked to be automatically - * checked out. - */ - if (S_ISGITLINK(ce->ce_mode)) - return 0; + if (!changed) { + if (!S_ISGITLINK(ce->ce_mode) || !submodule_needs_update(ce->name) || + (ce_stage(ce) ? is_submodule_checkout_safe(ce->name, ce->sha1) + : !is_submodule_modified(ce->name, 1))) + return 0; + } else + if (S_ISGITLINK(ce->ce_mode) && !submodule_needs_update(ce->name)) + return 0; + errno = 0; } if (errno == ENOENT) @@ -1254,6 +1257,36 @@ static int verify_uptodate_sparse(const struct cache_entry *ce, return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE); } +/* + * When a submodule gets turned into an unmerged entry, we want it to be + * up-to-date regarding the merge changes. + */ +static int verify_uptodate_submodule(const struct cache_entry *old, + const struct cache_entry *new, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || (!((old->ce_flags & CE_VALID) || ce_skip_worktree(old)) && (o->reset || ce_uptodate(old)))) + return 0; + if (!lstat(old->name, &st)) { + unsigned changed = ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); + if (!changed) { + if (!S_ISGITLINK(old->ce_mode) || + !submodule_needs_update(new->name) || + is_submodule_checkout_safe(new->name, new->sha1)) + return 0; + } else + if (S_ISGITLINK(old->ce_mode) && !submodule_needs_update(new->name)) + return 0; + errno = 0; + } + if (errno == ENOENT) + return 0; + return o->gently ? -1 : + add_rejected_path(o, ERROR_NOT_UPTODATE_SUBMODULE, old->name); +} + static void invalidate_ce_path(const struct cache_entry *ce, struct unpack_trees_options *o) { @@ -1532,9 +1565,17 @@ static int merged_entry(const struct cache_entry *ce, copy_cache_entry(merge, old); update = 0; } else { - if (verify_uptodate(old, o)) { - free(merge); - return -1; + if (S_ISGITLINK(old->ce_mode) || + S_ISGITLINK(merge->ce_mode)) { + if (verify_uptodate_submodule(old, merge, o)) { + free(merge); + return -1; + } + } else { + if (verify_uptodate(old, o)) { + free(merge); + return -1; + } } /* Migrate old flags over */ update |= old->ce_flags & (CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE); diff --git a/unpack-trees.h b/unpack-trees.h index 36a73a6..bee8740 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -15,6 +15,7 @@ enum unpack_trees_error_types { ERROR_WOULD_OVERWRITE = 0, ERROR_NOT_UPTODATE_FILE, ERROR_NOT_UPTODATE_DIR, + ERROR_NOT_UPTODATE_SUBMODULE, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, ERROR_BIND_OVERLAP, -- 1.8.5.1 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html