Commit de43efcf27cea1cd5cb633e057dc447ac0d7f4cf

Jason Haslam 2016-06-28T16:07:25

submodule: Try to fetch when update fails to find the target commit in the submodule.

diff --git a/include/git2/submodule.h b/include/git2/submodule.h
index bc94eac..540ecf5 100644
--- a/include/git2/submodule.h
+++ b/include/git2/submodule.h
@@ -154,13 +154,19 @@ typedef struct git_submodule_update_options {
 	 * in the working directory for the newly cloned repository.
 	 */
 	unsigned int clone_checkout_strategy;
+
+	/**
+	 * Allow fetching from the submodule's default remote if the target
+	 * commit isn't found. Enabled by default.
+	 */
+	int allow_fetch;
 } git_submodule_update_options;
 
 #define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1
 #define GIT_SUBMODULE_UPDATE_OPTIONS_INIT \
-	{ GIT_CHECKOUT_OPTIONS_VERSION, \
+	{ GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \
 		{ GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \
-	GIT_FETCH_OPTIONS_INIT, GIT_CHECKOUT_SAFE }
+	GIT_FETCH_OPTIONS_INIT, GIT_CHECKOUT_SAFE, 1 }
 
 /**
  * Initializes a `git_submodule_update_options` with default values.
@@ -176,7 +182,9 @@ GIT_EXTERN(int) git_submodule_update_init_options(
 /**
  * Update a submodule. This will clone a missing submodule and
  * checkout the subrepository to the commit specified in the index of
- * containing repository.
+ * the containing repository. If the submodule repository doesn't contain
+ * the target commit (e.g. because fetchRecurseSubmodules isn't set), then
+ * the submodule is fetched using the fetch options supplied in options.
  *
  * @param submodule Submodule object
  * @param init If the submodule is not initialized, setting this flag to true
diff --git a/src/submodule.c b/src/submodule.c
index c903cf9..86ad53b 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -93,6 +93,7 @@ static git_config_backend *open_gitmodules(git_repository *repo, int gitmod);
 static git_config *gitmodules_snapshot(git_repository *repo);
 static int get_url_base(git_buf *url, git_repository *repo);
 static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo);
+static int lookup_default_remote(git_remote **remote, git_repository *repo);
 static int submodule_load_each(const git_config_entry *entry, void *payload);
 static int submodule_read_config(git_submodule *sm, git_config *cfg);
 static int submodule_load_from_wd_lite(git_submodule *);
@@ -1131,7 +1132,7 @@ int git_submodule_update(git_submodule *sm, int init, git_submodule_update_optio
 			if (error != GIT_ENOTFOUND)
 				goto done;
 
-			if (error == GIT_ENOTFOUND && !init) {
+			if (!init) {
 				giterr_set(GITERR_SUBMODULE, "Submodule is not initialized.");
 				error = GIT_ERROR;
 				goto done;
@@ -1171,9 +1172,20 @@ int git_submodule_update(git_submodule *sm, int init, git_submodule_update_optio
 		 * update the workdir contents of the subrepository, and set the subrepository's
 		 * head to the new commit.
 		 */
-		if ((error = git_submodule_open(&sub_repo, sm)) < 0 ||
-			(error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0 ||
-			(error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 ||
+		if ((error = git_submodule_open(&sub_repo, sm)) < 0)
+			goto done;
+
+		/* Look up the target commit in the submodule. */
+		if ((error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0) {
+			/* If it isn't found then fetch and try again. */
+			if (error != GIT_ENOTFOUND || !update_options.allow_fetch ||
+				(error = lookup_default_remote(&remote, sub_repo)) < 0 ||
+				(error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 ||
+				(error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0)
+				goto done;
+		}
+
+		if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 ||
 			(error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0)
 			goto done;