Subject: [PATCH v2 07/19] worktree setup: restore original state when things go wrong

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sun, 21 Mar 2010 17:30:34 +0700

In current state of setup_git_directory_gently(), when a repository is
not found, many things will not be the same as when setup procedure is
started (while they should be). For one thing, current working
directory will not be the same in certain cases.

This patch reorders actions in clear steps and rolls back if one step
is wrong. (Actually the last step, setting git_dir, can't be rolled
back by this patch).

The steps and their state change are:

 1. Looking in working directory for repository candidates (or $GIT_DIR
    if it is set)

    This step may move $(cwd) to somewhere.

 2. Set internal variables to record repository features, based only
    on repository location and environment variables.

    This step sets variables:
    - inside_git_dir
    - inside_work_tree

 3. Inspect $GIT_DIR/config to see if it's a valid repository.

    This step may change variables:
    - repository_format_version
    - shared_repository
    - is_bare_repository_cfg
    - git_work_tree_cfg

 4. Save the repository location for later use.

    This step calls setup_git_dir() to set many variables in environment.c

 5. Calculate prefix (relative path to the original current working
    directory)

Setup procedure is completed at step 5. Stopping at any other steps
is considered a setup failure.

In case of setup failure, if it's not fatal and nongit_ok is not NULL,
prefix must be calculated before returning to caller. In other words:

  if (!fatal && gently)
    goto step_5;

Things that go wrong before step 4 will be cleaned up by
unset_git_directory(). Step 4 is irreversible, for now.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 cache.h |    1 +
 setup.c |   46 ++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/cache.h b/cache.h
index 3bd219c..8e9d818 100644
--- a/cache.h
+++ b/cache.h
@@ -418,6 +418,7 @@ extern const char **get_pathspec(const char *prefix, const char **pathspec);
 extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
+extern void unset_git_directory(const char *prefix);
 extern const char *prefix_path(const char *prefix, int len, const char *path);
 extern const char *prefix_filename(const char *prefix, int len, const char *path);
 extern int check_filename(const char *prefix, const char *name);
diff --git a/setup.c b/setup.c
index 2c1b64f..4de7bf0 100644
--- a/setup.c
+++ b/setup.c
@@ -323,6 +323,33 @@ const char *read_gitfile_gently(const char *path)
 	return path;
 }
 
+void unset_git_directory(const char *prefix)
+{
+	/*
+	 * FIXME:
+	 * chdir(prefix) may be enough for most of cases,
+	 * if original cwd is outside worktree, prefix
+	 * will always be set NULL, thus impossible to move
+	 * back to orignal cwd
+	 */
+	if (prefix && chdir(prefix))
+		die("Cannot change to '%s'", prefix);
+
+	if (startup_info) {
+		startup_info->prefix = NULL;
+		startup_info->have_repository = 0;
+	}
+
+	/* Initialized in setup_git_directory_gently_1() */
+	inside_work_tree = -1;
+	inside_git_dir = -1;
+
+	/* Initialized in check_repository_format_version() */
+	repository_format_version = 0xFF;
+	shared_repository = PERM_UMASK;
+	is_bare_repository_cfg = -1;
+	git_work_tree_cfg = NULL;
+}
 /*
  * We cannot decide in this function whether we are in the work tree or
  * not, since the config can only be read _after_ this function was called.
@@ -403,6 +430,13 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
 	 * - ../ (bare)
 	 * - ../../.git/
 	 *   etc.
+	 *
+	 * When a repository is found:
+	 * - inside_git_dir/inside_work_tree are set
+	 * - check_repository_format_gently() is called
+	 *   if repo version is not supported, restore cwd
+	 * - set_git_dir
+	 * - calculate and return prefix
 	 */
 	offset = len = strlen(cwd);
 	for (;;) {
@@ -410,13 +444,15 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
 		if (!gitfile_dir && is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
 			gitfile_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 		if (gitfile_dir) {
-			if (set_git_dir(gitfile_dir))
-				die("Repository setup failed");
 			inside_git_dir = 0;
 			if (!work_tree_env)
 				inside_work_tree = 1;
-			if (check_repository_format_gently(gitfile_dir, nongit_ok))
+			if (check_repository_format_gently(gitfile_dir, nongit_ok)) {
+				unset_git_directory(offset != len ? cwd + offset + 1: NULL);
 				return NULL;
+			}
+			if (set_git_dir(gitfile_dir))
+				die("Repository setup failed");
 			root_len = offset_1st_component(cwd);
 			git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len);
 			break;
@@ -425,8 +461,10 @@ static const char *setup_git_directory_gently_1(int *nongit_ok)
 			inside_git_dir = 1;
 			if (!work_tree_env)
 				inside_work_tree = 0;
-			if (check_repository_format_gently(".", nongit_ok))
+			if (check_repository_format_gently(".", nongit_ok)) {
+				unset_git_directory(offset != len ? cwd + offset + 1: NULL);
 				return NULL;
+			}
 			if (offset != len) {
 				root_len = offset_1st_component(cwd);
 				cwd[offset > root_len ? offset : root_len] = '\0';
-- 
1.7.0.2.425.gb99f1