[PATCH 3/3] Teach "git branch" about --new-workdir

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

 



Inspired by contrib/workdir/git-new-workdir, the flag --new-workdir (or
its shortcut, -n) can be used to create a new working directory for the
newly created branch.  All information, except which branch is checked
out (and therefore also the index), will be symlinked from the current
repository.

Example:

	$ git branch -n ~/my-new-topic xy-problem

will create a branch called "xy-problem", which is initially identical
to the current branch, and check out the new branch in ~/my-new-topic/.
You will be able to cherry-pick from, log and diff with the branch
"xy-problem" in the current repository, since most of the metadata is
shared.

Conversely, you can access all the branches in the current repository
from ~/my-new-topic/, too.

A word of warning: switching to _same_ branch that is checked out in
the other repository is asking for trouble.  You are really working not
only on the same object database, but also the same (i.e. not copied)
refs namespace.

Signed-off-by: Johannes Schindelin <johannes.schindelin@xxxxxx>
---
	IMHO this is a better syntax than what is in contrib/, and "git
	branch" is probably the right place for such a thing, from a
	user's perspective.

 Documentation/git-branch.txt |    7 +++-
 builtin-branch.c             |   79 +++++++++++++++++++++++++++++++++++++++--
 t/t3200-branch.sh            |   11 ++++++
 3 files changed, 92 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index bc6aa88..a05c795 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -10,7 +10,8 @@ SYNOPSIS
 [verse]
 'git-branch' [--color | --no-color] [-r | -a]
 	   [-v [--abbrev=<length> | --no-abbrev]]
-'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
+'git-branch' [--track | --no-track] [-l] [-f] <branchname>
+	   [-n <dir> | --new-workdir <dir>] [<start-point>]
 'git-branch' (-m | -M) [<oldbranch>] <newbranch>
 'git-branch' (-d | -D) [-r] <branchname>...
 
@@ -91,6 +92,10 @@ OPTIONS
 --no-abbrev::
 	Display the full sha1s in output listing rather than abbreviating them.
 
+-n\|--new-workdir <dir>::
+	Set up a new working directory which shares all information with the
+	current repository, except which branch is checked out.
+
 <branchname>::
 	The name of the branch to create or delete.
 	The new branch name must pass all checks defined by
diff --git a/builtin-branch.c b/builtin-branch.c
index 5f5c182..cba5fac 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -11,9 +11,10 @@
 #include "commit.h"
 #include "builtin.h"
 #include "remote.h"
+#include "unpack-trees.h"
 
 static const char builtin_branch_usage[] =
-  "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
+  "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] [-n <dir> | --new-workdir <dir>] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
 
 #define REF_UNKNOWN_TYPE    0x00
 #define REF_LOCAL_BRANCH    0x01
@@ -500,6 +501,67 @@ static void rename_branch(const char *oldname, const char *newname, int force)
 		die("Branch is renamed, but update of config-file failed");
 }
 
+static struct lock_file lockfile;
+
+/* This function free()s path */
+static int create_new_workdir(char *path, const char *branch_name)
+{
+	static const char *links[] = {
+		"config", "refs", "logs/refs", "objects", "info", "hooks",
+		"packed-refs", "remotes", "rr-cache", NULL
+	};
+	const char *git_dir = get_git_dir(), *absolute;
+	struct stat st;
+	unsigned char sha1[20];
+	char *ref;
+	struct object *object;
+	struct unpack_trees_options opts;
+	struct object_list trees;
+	int i, fd;
+
+	if (!has_symlinks)
+		return error("New workdir not possible without symlinks");
+	/* make sure that GIT_DIR is an absolute path */
+	if ((absolute = make_absolute_path(git_dir)) != git_dir &&
+			setup_new_git_dir(absolute))
+		return 1;
+	if (!lstat(git_path("config"), &st) && S_ISLNK(st.st_mode))
+		return error("Will not create a workdir from a workdir");
+	if (safe_create_leading_directories(path) ||
+			mkdir(path, 0777) || chdir(path))
+		return error("Could not create '%s'", path);
+	if (mkdir(DEFAULT_GIT_DIR_ENVIRONMENT, 0777) ||
+			chdir(DEFAULT_GIT_DIR_ENVIRONMENT) ||
+			mkdir("logs", 0777))
+		return error("Could not set up '%s/%s'",
+				path, DEFAULT_GIT_DIR_ENVIRONMENT);
+	for (i = 0; links[i]; i++)
+		if (symlink(git_path(links[i]), links[i]))
+			return error("Could not link '%s'", links[i]);
+	if (chdir("..") || setup_new_git_dir(DEFAULT_GIT_DIR_ENVIRONMENT))
+		return error("Error setting up new workdir");
+	fd = hold_locked_index(&lockfile, 1);
+	if (fd < 0)
+		return error("Could not lock index");
+	if (dwim_ref(branch_name, strlen(branch_name), sha1, &ref) != 1 ||
+			create_symref("HEAD", ref, "new workdir") ||
+			!(object = parse_object(sha1)) ||
+			object->type != OBJ_COMMIT)
+		return error("Could not checkout HEAD");
+	free(ref);
+	trees.item = &((struct commit *)object)->tree->object;
+	trees.next = NULL;
+
+	memset(&opts, 0, sizeof(opts));
+	opts.update = 1;
+	opts.merge = 1;
+	opts.head_idx = 1;
+	opts.fn = oneway_merge;
+	return unpack_trees(&trees, &opts) ||
+		write_cache(fd, active_cache, active_nr) ||
+		close(fd) || commit_locked_index(&lockfile);
+}
+
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
 	int delete = 0, force_delete = 0, force_create = 0;
@@ -507,6 +569,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 	int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
 	int reflog = 0, track;
 	int kinds = REF_LOCAL_BRANCH;
+	char *new_workdir = NULL;
 	int i;
 
 	git_config(git_branch_config);
@@ -587,11 +650,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 			branch_use_color = 0;
 			continue;
 		}
+		if (!strcmp(arg, "--new-workdir") || !strcmp(arg, "-n")) {
+			if (++i >= argc)
+				usage(builtin_branch_usage);
+			new_workdir = xstrdup(argv[i]);
+			continue;
+		}
 		usage(builtin_branch_usage);
 	}
 
 	if ((delete && rename) || (delete && force_create) ||
-	    (rename && force_create))
+	    (rename && force_create) || (new_workdir && (delete || rename)))
 		usage(builtin_branch_usage);
 
 	head = resolve_ref("HEAD", head_sha1, 0, NULL);
@@ -615,10 +684,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		rename_branch(head, argv[i], force_rename);
 	else if (rename && (i == argc - 2))
 		rename_branch(argv[i], argv[i + 1], force_rename);
-	else if (i == argc - 1 || i == argc - 2)
+	else if (i == argc - 1 || i == argc - 2) {
 		create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head,
 			      force_create, reflog, track);
-	else
+		if (new_workdir)
+			return create_new_workdir(new_workdir, argv[i]);
+	} else
 		usage(builtin_branch_usage);
 
 	return 0;
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index ef1eeb7..d9ec82a 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -202,4 +202,15 @@ test_expect_success \
 	 test -f .git/logs/refs/heads/g/h/i &&
 	 diff expect .git/logs/refs/heads/g/h/i'
 
+test_expect_success 'create new workdir' '
+	git branch -n new-workdir forked &&
+	test -d new-workdir &&
+	(cd new-workdir &&
+	 test $HEAD = $(git rev-parse HEAD) &&
+	 test refs/heads/forked = $(git symbolic-ref HEAD) &&
+	 git fsck &&
+	 git diff-files --quiet &&
+	 git diff-index --quiet --cached HEAD)
+'
+
 test_done
-- 
1.5.3.rc2.29.gc4640f

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

  Powered by Linux