Commit dcb0f7c061554de06db5879361b22eab3517a4ee

Russell Belfer 2013-05-15T14:54:02

Fix checkout of submodules with no .gitmodules It is possible for there to be a submodule in a repository with no .gitmodules file (for example, if the user forgot to commit the .gitmodules file). In this case, core Git will just create an empty directory as a placeholder for the submodule but otherwise ignore it. We were generating an error and stopping the checkout. This makes our behavior match that of core git.

diff --git a/include/git2/index.h b/include/git2/index.h
index d23c3a8..bde38a9 100644
--- a/include/git2/index.h
+++ b/include/git2/index.h
@@ -57,6 +57,8 @@ GIT_BEGIN_DECL
 
 #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE)
 
+#define GIT_IDXENTRY_STAGE(E) (((E)->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT)
+
 /** Time used in a git index entry */
 typedef struct {
 	git_time_t seconds;
diff --git a/src/checkout.c b/src/checkout.c
index 4d019db..5820f62 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -820,6 +820,31 @@ static int checkout_update_index(
 	return git_index_add(data->index, &entry);
 }
 
+static int checkout_submodule_update_index(
+	checkout_data *data,
+	const git_diff_file *file)
+{
+	struct stat st;
+
+	/* update the index unless prevented */
+	if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0)
+		return 0;
+
+	git_buf_truncate(&data->path, data->workdir_len);
+	if (git_buf_puts(&data->path, file->path) < 0)
+		return -1;
+
+	if (p_stat(git_buf_cstr(&data->path), &st) < 0) {
+		giterr_set(
+			GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
+		return GIT_ENOTFOUND;
+	}
+
+	st.st_mode = GIT_FILEMODE_COMMIT;
+
+	return checkout_update_index(data, file, &st);
+}
+
 static int checkout_submodule(
 	checkout_data *data,
 	const git_diff_file *file)
@@ -836,8 +861,17 @@ static int checkout_submodule(
 			data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
 		return error;
 
-	if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0)
+	if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0) {
+		/* I've observed repos with submodules in the tree that do not
+		 * have a .gitmodules - core Git just makes an empty directory
+		 */
+		if (error == GIT_ENOTFOUND) {
+			giterr_clear();
+			return checkout_submodule_update_index(data, file);
+		}
+
 		return error;
+	}
 
 	/* TODO: Support checkout_strategy options.  Two circumstances:
 	 * 1 - submodule already checked out, but we need to move the HEAD
@@ -848,26 +882,7 @@ static int checkout_submodule(
 	 * command should probably be able to.  Do we need a submodule callback?
 	 */
 
-	/* update the index unless prevented */
-	if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) {
-		struct stat st;
-
-		git_buf_truncate(&data->path, data->workdir_len);
-		if (git_buf_puts(&data->path, file->path) < 0)
-			return -1;
-
-		if ((error = p_stat(git_buf_cstr(&data->path), &st)) < 0) {
-			giterr_set(
-				GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
-			return error;
-		}
-
-		st.st_mode = GIT_FILEMODE_COMMIT;
-
-		error = checkout_update_index(data, file, &st);
-	}
-
-	return error;
+	return checkout_submodule_update_index(data, file);
 }
 
 static void report_progress(
diff --git a/src/index.c b/src/index.c
index 53edb48..bdbe545 100644
--- a/src/index.c
+++ b/src/index.c
@@ -106,7 +106,7 @@ static void index_entry_reuc_free(git_index_reuc_entry *reuc);
 
 GIT_INLINE(int) index_entry_stage(const git_index_entry *entry)
 {
-	return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
+	return GIT_IDXENTRY_STAGE(entry);
 }
 
 static int index_srch(const void *key, const void *array_member)