summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Lehmann <Jens.Lehmann@web.de>2013-04-05 18:35:27 +0200
committerJunio C Hamano <gitster@pobox.com>2013-12-26 11:59:04 -0800
commitde40c413f0e600668db1ccbf5bdf34694c29843f (patch)
treee65ee43273d832d6274470e3a5045de765c8a3f1
parent00eeddeac8feded2a45ebf31f3d7ab5140cbe21a (diff)
downloadgit-de40c413f0e600668db1ccbf5bdf34694c29843f.tar.gz
submodule: teach unpack_trees() to update submodules
Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
-rw-r--r--entry.c15
-rw-r--r--submodule.c86
-rw-r--r--submodule.h3
-rw-r--r--unpack-trees.c69
-rw-r--r--unpack-trees.h1
5 files changed, 157 insertions, 17 deletions
diff --git a/entry.c b/entry.c
index d1bf6ec3b7..61a2767b16 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 06df5ae07a..3365987d71 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 3657ca8e35..b42ae91eeb 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 ed48d41ff5..fc8855eb9e 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 36a73a6d00..bee874088a 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,