git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 14/21] checkout: support checking out into a new working directory

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Dec 14, 2013, 10:55 UTC
Message-ID
<1387018507-21999-15-git-send-email-pclouds@gmail.com>
In-Reply-To
<1387018507-21999-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-checkout.txt         |  6 +++
 Documentation/gitrepository-layout.txt |  3 +-
 builtin/checkout.c                     | 94 ++++++++++++++++++++++++++++++++++
 path.c                                 |  3 +-
 4 files changed, 104 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 91294f8..06076c8 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -225,6 +225,12 @@ This means that you can use `git checkout -p` to selectively discard
 edits from your current working tree. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
 
+--to=<path>::
+	Check out a new branch in a separate working directory at
+	`<path>`. A new repository is initialized at `<path>` that
+	shares everything with the current repository except working
+	directory specific files.
+
 <branch>::
 	Branch to checkout; if it refers to a branch (i.e., a name that,
 	when prepended with "refs/heads/", is a valid ref), then that
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 7ce31d4..3c6149e 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -222,7 +222,8 @@ repos/<id>::
 	If a repository's .git is a file contains two lines `gitsuper:
 	<path>` and `repo: <id>`. The directory `<path>/repos/<id>`
 	contains the real non-shared part of .git directory of the
-	repository in question (e.g. HEAD or index).
+	repository in question (e.g. HEAD or index). Such .git files
+	are created by `git checkout --to`.
 
 SEE ALSO
 --------
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 904fd71..95a1a61 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -48,6 +48,9 @@ struct checkout_opts {
 	const char *prefix;
 	struct pathspec pathspec;
 	struct tree *source_tree;
+
+	const char *new_worktree;
+	const char **saved_argv;
 };
 
 static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -250,6 +253,9 @@ static int checkout_paths(const struct checkout_opts *opts,
 		die(_("Cannot update paths and switch to branch '%s' at the same time."),
 		    opts->new_branch);
 
+	if (opts->new_worktree)
+		die(_("'%s' cannot be used with updating paths"), "--to");
+
 	if (opts->patch_mode)
 		return run_add_interactive(revision, "--patch=checkout",
 					   &opts->pathspec);
@@ -808,6 +814,82 @@ static int switch_branches(const struct checkout_opts *opts,
 	return ret || writeout_error;
 }
 
+static void write_to_file(const char *path, const char *fmt, ...)
+{
+	va_list params;
+	FILE *fp = fopen(path, "w");
+	if (!fp)
+		die_errno(_("could not create file '%s'"), path);
+	va_start(params, fmt);
+	vfprintf(fp, fmt, params);
+	va_end(params);
+	fclose(fp);
+}
+
+static int checkout_new_worktree(const struct checkout_opts *opts,
+				 struct branch_info *new)
+{
+	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
+	const char *path = opts->new_worktree;
+	struct stat st;
+	const char *name;
+	struct child_process cp;
+	int counter = 0, len;
+
+	if (!new->commit)
+		die(_("no branch specified"));
+
+	len = strlen(path);
+	if (!len || is_dir_sep(path[len - 1]))
+		die(_("'--to' argument '%s' cannot end with a slash"), path);
+
+	for (name = path + len - 1; name > path; name--)
+		if (is_dir_sep(*name)) {
+			name++;
+			break;
+		}
+	strbuf_addstr(&sb_repo, git_path("repos/%s", name));
+	len = sb_repo.len;
+	if (safe_create_leading_directories_const(sb_repo.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_repo.buf);
+	while (!stat(sb_repo.buf, &st)) {
+		counter++;
+		strbuf_setlen(&sb_repo, len);
+		strbuf_addf(&sb_repo, "%d", counter);
+	}
+	name = sb_repo.buf + len - strlen(name);
+	if (mkdir(sb_repo.buf, 0777))
+		die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+
+	strbuf_addf(&sb_git, "%s/.git", path);
+	if (safe_create_leading_directories_const(sb_git.buf))
+		die_errno(_("could not create leading directories of '%s'"),
+			  sb_git.buf);
+
+	write_to_file(sb_git.buf, "gitsuper: %s\ngitdir: %s\n",
+		      real_path(get_git_dir()), name);
+	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+	write_to_file(sb.buf, "%s\n", sha1_to_hex(new->commit->object.sha1));
+
+	if (!opts->quiet)
+		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
+
+	/*
+	 * Rerun checkout in the new worktree. This way is safer than
+	 * set_git_dir() because a path relative to cwd could have
+	 * been cached somewhere undetected.
+	 */
+	setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
+	setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
+	setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	cp.argv = opts->saved_argv;
+	return run_command(&cp);
+}
+
 static int git_checkout_config(const char *var, const char *value, void *cb)
 {
 	if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1069,6 +1151,9 @@ static int checkout_branch(struct checkout_opts *opts,
 		die(_("Cannot switch branch to a non-commit '%s'"),
 		    new->name);
 
+	if (opts->new_worktree)
+		return checkout_new_worktree(opts, new);
+
 	if (!new->commit && opts->new_branch) {
 		unsigned char rev[20];
 		int flag;
@@ -1111,6 +1196,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			 N_("do not limit pathspecs to sparse entries only")),
 		OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 				N_("second guess 'git checkout no-such-branch'")),
+		OPT_STRING(0, "to", &opts.new_worktree, N_("path"),
+			   N_("check a branch out in a separate working directory")),
 		OPT_END(),
 	};
 
@@ -1119,6 +1206,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	opts.overwrite_ignore = 1;
 	opts.prefix = prefix;
 
+	opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
+	memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
+
 	gitmodules_config();
 	git_config(git_checkout_config, &opts);
 
@@ -1127,6 +1217,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, options, checkout_usage,
 			     PARSE_OPT_KEEP_DASHDASH);
 
+	if (getenv("GIT_CHECKOUT_NEW_WORKTREE"))
+		/* recursive execution from checkout_new_worktree() */
+		opts.new_worktree = NULL;
+
 	if (conflict_style) {
 		opts.merge = 1; /* implied */
 		git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
diff --git a/path.c b/path.c
index e51fc35..1a2478a 100644
--- a/path.c
+++ b/path.c
@@ -93,7 +93,8 @@ static void update_super_dir(char *buf, size_t n, int git_dir_len)
 {
 	const char *super_dir_list[] = {
 		"branches", "hooks", "info", "logs", "modules",
-		"objects", "refs", "remotes", "rr-cache", "svn", NULL
+		"objects", "refs", "remotes", "repos", "rr-cache",
+		"svn", NULL
 	};
 	const char *super_top_file_list[] = {
 		"config", "packed-refs", "shallow", NULL
-- 
1.8.5.1.77.g42c48fa
Previous: Nguyễn Thái Ngọc DuyNext: Nguyễn Thái Ngọc Duy
Message 41 of 49 in “Support multiple worktrees”
  1. 0/7 Support multiple worktreesNguyễn Thái Ngọc Duy, Dec 11, 2013
  2. 1/7 Make git_path() beware of file relocation in $GIT_DIRNguyễn Thái Ngọc Duy, Dec 11, 2013
  3. Junio C HamanoDec 13, 2013
  4. 2/7 Add new environment variable $GIT_SUPER_DIRNguyễn Thái Ngọc Duy, Dec 11, 2013
  5. Junio C HamanoDec 13, 2013
  6. Duy NguyenDec 14, 2013
  7. Junio C HamanoDec 14, 2013
  8. 3/7 setup.c: add split-repo support to .git filesNguyễn Thái Ngọc Duy, Dec 11, 2013
  9. Junio C HamanoDec 13, 2013
  10. Jonathan NiederDec 13, 2013
  11. Duy NguyenDec 14, 2013
  12. Duy NguyenDec 23, 2013
  13. 4/7 setup.c: add split-repo support to is_git_directory()Nguyễn Thái Ngọc Duy, Dec 11, 2013
  14. 5/7 setup.c: reduce cleanup sites in setup_explicit_git_dir()Nguyễn Thái Ngọc Duy, Dec 11, 2013
  15. 6/7 setup.c: add split-repo support to setup_git_directory*Nguyễn Thái Ngọc Duy, Dec 11, 2013
  16. 7/7 init: add --split-repo with the same functionality as git-new-workdirNguyễn Thái Ngọc Duy, Dec 11, 2013
  17. 00/21 Support multiple worktreesNguyễn Thái Ngọc Duy, Dec 14, 2013
  18. 01/21 path.c: avoid PATH_MAX as buffer size from get_pathname()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  19. Torsten BögershausenDec 15, 2013
  20. Duy NguyenDec 15, 2013
  21. Antoine PelisseDec 15, 2013
  22. 02/21 path.c: rename vsnpath() to git_vsnpath()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  23. Ramsay JonesDec 14, 2013
  24. Duy NguyenDec 15, 2013
  25. Ramsay JonesDec 15, 2013
  26. Duy NguyenDec 16, 2013
  27. Jonathan NiederDec 16, 2013
  28. Junio C HamanoDec 16, 2013
  29. Ramsay JonesDec 16, 2013
  30. 03/21 path.c: move git_path() closer to similar functions git_pathdup()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  31. 04/21 Make git_path() aware of file relocation in $GIT_DIRNguyễn Thái Ngọc Duy, Dec 14, 2013
  32. 05/21 reflog: use avoid constructing .lock path with git_pathNguyễn Thái Ngọc Duy, Dec 14, 2013
  33. 06/21 fast-import: use git_path() for accessing .git dir instead of get_git_dir()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  34. 07/21 Add new environment variable $GIT_SUPER_DIRNguyễn Thái Ngọc Duy, Dec 14, 2013
  35. 08/21 setup.c: refactor path manipulation out of read_gitfile()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  36. 09/21 setup.c: add split-repo support to .git filesNguyễn Thái Ngọc Duy, Dec 14, 2013
  37. 10/21 setup.c: add split-repo support to is_git_directory()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  38. 11/21 setup.c: reduce cleanup sites in setup_explicit_git_dir()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  39. 12/21 environment.c: support super .git file specified by $GIT_DIRNguyễn Thái Ngọc Duy, Dec 14, 2013
  40. 13/21 setup: support $GIT_SUPER_DIR as well as super .git filesNguyễn Thái Ngọc Duy, Dec 14, 2013
  41. 14/21 checkout: support checking out into a new working directoryNguyễn Thái Ngọc Duy, Dec 14, 2013
  42. 15/21 checkout: clean up half-prepared directories in --to modeNguyễn Thái Ngọc Duy, Dec 14, 2013
  43. 16/21 setup.c: keep track of the .git file location if readNguyễn Thái Ngọc Duy, Dec 14, 2013
  44. 17/21 prune: strategies for split repositoriesNguyễn Thái Ngọc Duy, Dec 14, 2013
  45. 18/21 refs: adjust reflog path for repos/<id>/HEADNguyễn Thái Ngọc Duy, Dec 14, 2013
  46. 19/21 refs: detach split repos' HEAD when the linked ref is updated/deletedNguyễn Thái Ngọc Duy, Dec 14, 2013
  47. 20/21 refs.c: refactor do_head_ref(... to do_one_head_ref("HEAD", ...Nguyễn Thái Ngọc Duy, Dec 14, 2013
  48. 21/21 revision: include repos/../HEAD in --allNguyễn Thái Ngọc Duy, Dec 14, 2013
  49. Duy NguyenDec 15, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.