[RFC/PATCH] Add multiple workdir support to branch/checkout

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When using 'git new-workdir', there is no safety mechanism to prevent the
same branch from being checked out twice, nor to prevent a checked out
branch from being deleted.

By teaching 'checkout' to record the workdir path using
'branch.<name>.checkout' when switching branches, we can easily check if a
branch is already checked out in another workdir before switching to that
branch. Similarly, we can now add a check before deleting a branch.

Allow 'checkout -f' to force the checkout and issue a warning
instead of an error.

Guard this behavior behind 'core.recordCheckouts', which we will
teach 'git new-workdir' to set in a followup commit.

Note: when switching away from a branch, we set 'branch.<name>.checkout'
to the empty string, instead of deleting it entirely, since git_config()
otherwise leaves behind an empty section which it does not re-use.

Signed-off-by: Jay Soffian <jaysoffian@xxxxxxxxx>
---
 builtin/branch.c   |   10 ++++++++++
 builtin/checkout.c |   39 +++++++++++++++++++++++++++++++++++++++
 remote.c           |    4 ++++
 remote.h           |    1 +
 4 files changed, 54 insertions(+), 0 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index f49596f826..6ce1a5b133 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -182,6 +182,16 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
 			ret = 1;
 			continue;
 		}
+		if (kinds == REF_LOCAL_BRANCH) {
+			struct branch *branch = branch_get(bname.buf);
+			if (branch->work_tree && strlen(branch->work_tree)) {
+				error(_("Cannot delete the branch '%s' "
+					"which is currently checked out in '%s'"),
+				      bname.buf, branch->work_tree);
+				ret = 1;
+				continue;
+			}
+		}
 
 		free(name);
 
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5e356a6c61..26259a41a7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -33,6 +33,7 @@ struct checkout_opts {
 	int force_detach;
 	int writeout_stage;
 	int writeout_error;
+	int record_checkouts;
 
 	/* not set by parse_options */
 	int branch_exists;
@@ -709,12 +710,35 @@ static void orphaned_commit_warning(struct commit *commit)
 	for_each_ref(clear_commit_marks_from_one_ref, NULL);
 }
 
+static void record_checkout(const char *name, const char *work_tree)
+{
+	struct strbuf key = STRBUF_INIT;
+	strbuf_addf(&key, "branch.%s.checkout", name);
+	git_config_set(key.buf, work_tree);
+	strbuf_release(&key);
+}
+
+static void check_if_checked_out(struct checkout_opts *opts, const char *name)
+{
+	struct branch *branch = branch_get(name);
+	if (branch->work_tree && strlen(branch->work_tree) &&
+	    strcmp(branch->work_tree, get_git_work_tree())) {
+		if (opts->force)
+			warning(_("branch '%s' is currently checked out"
+				  " in '%s'"), name, branch->work_tree);
+		else
+			die(_("branch '%s' is currently checked out"
+			      " in '%s'"), name, branch->work_tree);
+	}
+}
+
 static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 {
 	int ret = 0;
 	struct branch_info old;
 	unsigned char rev[20];
 	int flag;
+
 	memset(&old, 0, sizeof(old));
 	old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag));
 	old.commit = lookup_commit_reference_gently(rev, 1);
@@ -734,6 +758,9 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 		parse_commit(new->commit);
 	}
 
+	if (opts->record_checkouts)
+		check_if_checked_out(opts, new->name);
+
 	ret = merge_working_tree(opts, &old, new);
 	if (ret)
 		return ret;
@@ -743,6 +770,14 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 
 	update_refs_for_switch(opts, &old, new);
 
+	if (opts->record_checkouts) {
+		const char *work_tree = get_git_work_tree();
+		struct branch *branch = branch_get(old.name);
+		if (branch->work_tree && !strcmp(branch->work_tree, work_tree))
+			record_checkout(old.name, "");
+		record_checkout(new->name, work_tree);
+	}
+
 	ret = post_checkout_hook(old.commit, new->commit, 1);
 	free((char *)old.path);
 	return ret || opts->writeout_error;
@@ -756,6 +791,10 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.recordcheckouts")) {
+		struct checkout_opts *opts = cb;
+		opts->record_checkouts = git_config_bool(var, value);
+	}
 	if (!prefixcmp(var, "submodule."))
 		return parse_submodule_config_option(var, value);
 
diff --git a/remote.c b/remote.c
index b8ecfa5d95..2bc063dae8 100644
--- a/remote.c
+++ b/remote.c
@@ -364,6 +364,10 @@ static int handle_config(const char *key, const char *value, void *cb)
 			if (!value)
 				return config_error_nonbool(key);
 			add_merge(branch, xstrdup(value));
+		} else if (!strcmp(subkey, ".checkout")) {
+			if (!value)
+				return config_error_nonbool(key);
+			branch->work_tree = xstrdup(value);
 		}
 		return 0;
 	}
diff --git a/remote.h b/remote.h
index 9a30a9dba6..4103ec7e31 100644
--- a/remote.h
+++ b/remote.h
@@ -126,6 +126,7 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec);
 struct branch {
 	const char *name;
 	const char *refname;
+	const char *work_tree;
 
 	const char *remote_name;
 	struct remote *remote;
-- 
1.7.7.4.g39e02c

--
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


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]