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

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

 



On Wed, Oct 5, 2011 at 12:07 AM, Junio C Hamano <gitster@xxxxxxxxx> wrote:
> Jay Soffian <jaysoffian@xxxxxxxxx> writes:
>> @@ -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);
>
> The close brace we can see in the context closes "if (!new->name) {", so
> this codepath is very well prepared to be called with new->name == NULL.
>
> Is check_if_checked_out() prepared to be called with name == NULL and do
> the right thing?
>
>> @@ -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);
>> +     }
>> +
>
> Likewise for new->name, but also old.name which is only set when old.path
> is set and begins with "refs/heads/" and otherwise NULL.

I was more looking for feedback on the idea than the implementation, but
here's a better implementation. Still an RFC so no tests yet.

-- >8 --
Subject: [RFC/PATCH] Teach branch/checkout about workdirs

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.

Teach 'checkout' to record the workdir path using 'branch.<name>.checkout'
when switching branches. We can then easily check if a branch is already
checked out in another workdir before switching to that branch. Add a
similar 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 |   45 +++++++++++++++++++++++++++++++++++++++++++++
 remote.c           |    4 ++++
 remote.h           |    1 +
 4 files changed, 60 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..b3c658ffd4 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;
@@ -508,6 +509,20 @@ static void detach_advice(const char *old_path, const char *new_name)
 	fprintf(stderr, fmt, new_name);
 }
 
+static void record_checkout(const char *name, const char *new_work_tree)
+{
+	struct strbuf key = STRBUF_INIT;
+	strbuf_addf(&key, "branch.%s.checkout", name);
+	if (new_work_tree) { /* reserve name */
+		git_config_set(key.buf, new_work_tree);
+	} else { /* release name if we reserved it */
+		struct branch *branch = branch_get(name);
+		if (!strcmp(branch->work_tree, get_git_work_tree()))
+			git_config_set(key.buf, "");
+	}
+	strbuf_release(&key);
+}
+
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -556,6 +571,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 				detach_advice(old->path, new->name);
 			describe_detached_head(_("HEAD is now at"), new->commit);
 		}
+		if (opts->record_checkouts && old->name)
+			record_checkout(old->name, NULL);
 	} else if (new->path) {	/* Switch branches. */
 		create_symref("HEAD", new->path, msg.buf);
 		if (!opts->quiet) {
@@ -580,6 +597,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 			if (!file_exists(ref_file) && file_exists(log_file))
 				remove_path(log_file);
 		}
+		if (opts->record_checkouts) {
+			if (old->name)
+				record_checkout(old->name, NULL);
+			record_checkout(new->name, get_git_work_tree());
+		}
 	}
 	remove_branch_state();
 	strbuf_release(&msg);
@@ -709,6 +731,23 @@ static void orphaned_commit_warning(struct commit *commit)
 	for_each_ref(clear_commit_marks_from_one_ref, NULL);
 }
 
+static void check_if_checked_out(struct checkout_opts *opts, const char *name)
+{
+	struct branch *branch;
+	if (!opts->record_checkouts)
+		return;
+	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;
@@ -732,6 +771,8 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
 		if (!new->commit)
 			die(_("You are on a branch yet to be born"));
 		parse_commit(new->commit);
+	} else if (opts->record_checkouts) {
+		check_if_checked_out(opts, new->name);
 	}
 
 	ret = merge_working_tree(opts, &old, new);
@@ -756,6 +797,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]