checkout: support checking out into a new working directory
"git checkout --to" sets up a new working directory with a .git file pointing to $GIT_DIR/worktrees/<id>. It then executes "git checkout" again on the new worktree with the same arguments except "--to" is taken out. The second checkout execution, which is not contaminated with any info from the current repository, will actually check out and everything that normal "git checkout" does. Helped-by: Marc Branchaud <marcnarc@xiplink.com> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Nov 30, 2014 at 15:24 UTC
529fef20cf94dbd5c16f7a239ffc2b06f3cf8bb7
6 files changed
+212
-4
Documentation/git-checkout.txt
+46
@@ -225,6 +225,13 @@ This means that you can use `git checkout -p` to selectively discard
225
edits from your current working tree. See the ``Interactive Mode''
226
section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
227
228
+--to=<path>::
229
+ Check out a branch in a separate working directory at
230
+ `<path>`. A new working directory is linked to the current
231
+ repository, sharing everything except working directory
232
+ specific files such as HEAD, index... See "MULTIPLE WORKING
233
+ TREES" section for more information.
234
+
235
<branch>::
236
Branch to checkout; if it refers to a branch (i.e., a name that,
237
when prepended with "refs/heads/", is a valid ref), then that
@@ -388,6 +395,45 @@ $ git reflog -2 HEAD # or
395
$ git log -g -2 HEAD
396
------------
397
398
+MULTIPLE WORKING TREES
399
+----------------------
400
+
401
+A git repository can support multiple working trees, allowing you to check
402
+out more than one branch at a time. With `git checkout --to` a new working
403
+tree is associated with the repository. This new working tree is called a
404
+"linked working tree" as opposed to the "main working tree" prepared by "git
405
+init" or "git clone". A repository has one main working tree (if it's not a
406
+bare repository) and zero or more linked working trees.
407
+
408
+Each linked working tree has a private sub-directory in the repository's
409
+$GIT_DIR/worktrees directory. The private sub-directory's name is usually
410
+the base name of the linked working tree's path, possibly appended with a
411
+number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
412
+command `git checkout --to /path/other/test-next next` creates the linked
413
+working tree in `/path/other/test-next` and also creates a
414
+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
415
+if `test-next` is already taken).
416
+
417
+Within a linked working tree, $GIT_DIR is set to point to this private
418
+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
419
+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
420
+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
421
+the top directory of the linked working tree.
422
+
423
+Path resolution via `git rev-parse --git-path` uses either
424
+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
425
+linked working tree `git rev-parse --git-path HEAD` returns
426
+`/path/main/.git/worktrees/test-next/HEAD` (not
427
+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
428
+rev-parse --git-path refs/heads/master` uses
429
+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
430
+since refs are shared across all working trees.
431
+
432
+See linkgit:gitrepository-layout[5] for more information. The rule of
433
+thumb is do not make any assumption about whether a path belongs to
434
+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
435
+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
436
+
437
EXAMPLES
438
--------
439
Documentation/git.txt
+2
-1
@@ -813,7 +813,8 @@ Git so take care if using Cogito etc.
813
If this variable is set to a path, non-worktree files that are
814
normally in $GIT_DIR will be taken from this path
815
instead. Worktree-specific files such as HEAD or index are
816
- taken from $GIT_DIR. See linkgit:gitrepository-layout[5] for
816
+ taken from $GIT_DIR. See linkgit:gitrepository-layout[5] and
817
+ the section 'MULTIPLE CHECKOUT MODE' in linkgit:checkout[1]
818
details. This variable has lower precedence than other path
819
variables such as GIT_INDEX_FILE, GIT_OBJECT_DIRECTORY...
820
Documentation/gitrepository-layout.txt
+7
@@ -252,6 +252,13 @@ modules::
252
directory is ignored if $GIT_COMMON_DIR is set and
253
"$GIT_COMMON_DIR/modules" will be used instead.
254
255
+worktrees::
256
+ Contains worktree specific information of linked
257
+ checkouts. Each subdirectory contains the worktree-related
258
+ part of a linked checkout. This directory is ignored if
259
+ $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/worktrees" will be
260
+ used instead.
261
+
262
SEE ALSO
263
--------
264
linkgit:git-init[1],
builtin/checkout.c
+93
-2
@@ -48,6 +48,10 @@ struct checkout_opts {
48
const char *prefix;
49
struct pathspec pathspec;
50
struct tree *source_tree;
51
+
52
+ const char *new_worktree;
53
+ const char **saved_argv;
54
+ int new_worktree_mode;
55
};
56
57
static int post_checkout_hook(struct commit *old, struct commit *new,
@@ -249,6 +253,9 @@ static int checkout_paths(const struct checkout_opts *opts,
253
die(_("Cannot update paths and switch to branch '%s' at the same time."),
254
opts->new_branch);
255
256
+ if (opts->new_worktree)
257
+ die(_("'%s' cannot be used with updating paths"), "--to");
258
+
259
if (opts->patch_mode)
260
return run_add_interactive(revision, "--patch=checkout",
261
&opts->pathspec);
@@ -484,7 +491,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
491
topts.dir->flags |= DIR_SHOW_IGNORED;
492
setup_standard_excludes(topts.dir);
493
}
487
- tree = parse_tree_indirect(old->commit ?
494
+ tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ?
495
old->commit->object.sha1 :
496
EMPTY_TREE_SHA1_BIN);
497
init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -800,7 +807,8 @@ static int switch_branches(const struct checkout_opts *opts,
807
return ret;
808
}
809
803
- if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
810
+ if (!opts->quiet && !old.path && old.commit &&
811
+ new->commit != old.commit && !opts->new_worktree_mode)
812
orphaned_commit_warning(old.commit, new->commit);
813
814
update_refs_for_switch(opts, &old, new);
@@ -810,6 +818,76 @@ static int switch_branches(const struct checkout_opts *opts,
818
return ret || writeout_error;
819
}
820
821
+static int prepare_linked_checkout(const struct checkout_opts *opts,
822
+ struct branch_info *new)
823
+{
824
+ struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
825
+ struct strbuf sb = STRBUF_INIT;
826
+ const char *path = opts->new_worktree, *name;
827
+ struct stat st;
828
+ struct child_process cp;
829
+ int counter = 0, len;
830
+
831
+ if (!new->commit)
832
+ die(_("no branch specified"));
833
+ if (file_exists(path))
834
+ die(_("'%s' already exists"), path);
835
+
836
+ len = strlen(path);
837
+ while (len && is_dir_sep(path[len - 1]))
838
+ len--;
839
+
840
+ for (name = path + len - 1; name > path; name--)
841
+ if (is_dir_sep(*name)) {
842
+ name++;
843
+ break;
844
+ }
845
+ strbuf_addstr(&sb_repo,
846
+ git_path("worktrees/%.*s", (int)(path + len - name), name));
847
+ len = sb_repo.len;
848
+ if (safe_create_leading_directories_const(sb_repo.buf))
849
+ die_errno(_("could not create leading directories of '%s'"),
850
+ sb_repo.buf);
851
+ while (!stat(sb_repo.buf, &st)) {
852
+ counter++;
853
+ strbuf_setlen(&sb_repo, len);
854
+ strbuf_addf(&sb_repo, "%d", counter);
855
+ }
856
+ name = strrchr(sb_repo.buf, '/') + 1;
857
+ if (mkdir(sb_repo.buf, 0777))
858
+ die_errno(_("could not create directory of '%s'"), sb_repo.buf);
859
+
860
+ strbuf_addf(&sb_git, "%s/.git", path);
861
+ if (safe_create_leading_directories_const(sb_git.buf))
862
+ die_errno(_("could not create leading directories of '%s'"),
863
+ sb_git.buf);
864
+
865
+ write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n",
866
+ real_path(get_git_common_dir()), name);
867
+ /*
868
+ * This is to keep resolve_ref() happy. We need a valid HEAD
869
+ * or is_git_directory() will reject the directory. Any valid
870
+ * value would do because this value will be ignored and
871
+ * replaced at the next (real) checkout.
872
+ */
873
+ strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
874
+ write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1));
875
+ strbuf_reset(&sb);
876
+ strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
877
+ write_file(sb.buf, 1, "../..\n");
878
+
879
+ if (!opts->quiet)
880
+ fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
881
+
882
+ setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
883
+ setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1);
884
+ setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1);
885
+ memset(&cp, 0, sizeof(cp));
886
+ cp.git_cmd = 1;
887
+ cp.argv = opts->saved_argv;
888
+ return run_command(&cp);
889
+}
890
+
891
static int git_checkout_config(const char *var, const char *value, void *cb)
892
{
893
if (!strcmp(var, "diff.ignoresubmodules")) {
@@ -1071,6 +1149,9 @@ static int checkout_branch(struct checkout_opts *opts,
1149
die(_("Cannot switch branch to a non-commit '%s'"),
1150
new->name);
1151
1152
+ if (opts->new_worktree)
1153
+ return prepare_linked_checkout(opts, new);
1154
+
1155
if (!new->commit && opts->new_branch) {
1156
unsigned char rev[20];
1157
int flag;
@@ -1113,6 +1194,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
1194
N_("do not limit pathspecs to sparse entries only")),
1195
OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
1196
N_("second guess 'git checkout no-such-branch'")),
1197
+ OPT_FILENAME(0, "to", &opts.new_worktree,
1198
+ N_("check a branch out in a separate working directory")),
1199
OPT_END(),
1200
};
1201
@@ -1121,6 +1204,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
1204
opts.overwrite_ignore = 1;
1205
opts.prefix = prefix;
1206
1207
+ opts.saved_argv = xmalloc(sizeof(const char *) * (argc + 2));
1208
+ memcpy(opts.saved_argv, argv, sizeof(const char *) * (argc + 1));
1209
+
1210
gitmodules_config();
1211
git_config(git_checkout_config, &opts);
1212
@@ -1129,6 +1215,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
1215
argc = parse_options(argc, argv, prefix, options, checkout_usage,
1216
PARSE_OPT_KEEP_DASHDASH);
1217
1218
+ /* recursive execution from checkout_new_worktree() */
1219
+ opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL;
1220
+ if (opts.new_worktree_mode)
1221
+ opts.new_worktree = NULL;
1222
+
1223
if (conflict_style) {
1224
opts.merge = 1; /* implied */
1225
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
path.c
+1
-1
@@ -92,7 +92,7 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
92
93
static const char *common_list[] = {
94
"/branches", "/hooks", "/info", "/logs", "/lost-found", "/modules",
95
- "/objects", "/refs", "/remotes", "/rr-cache", "/svn",
95
+ "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
96
"config", "gc.pid", "packed-refs", "shallow",
97
NULL
98
};
t/t2025-checkout-to.sh
new
+63
@@ -0,0 +1,63 @@
1
+#!/bin/sh
2
+
3
+test_description='test git checkout --to'
4
+
5
+. ./test-lib.sh
6
+
7
+test_expect_success 'setup' '
8
+ test_commit init
9
+'
10
+
11
+test_expect_success 'checkout --to not updating paths' '
12
+ test_must_fail git checkout --to -- init.t
13
+'
14
+
15
+test_expect_success 'checkout --to an existing worktree' '
16
+ mkdir existing &&
17
+ test_must_fail git checkout --detach --to existing master
18
+'
19
+
20
+test_expect_success 'checkout --to a new worktree' '
21
+ git checkout --to here master &&
22
+ (
23
+ cd here &&
24
+ test_cmp ../init.t init.t &&
25
+ git symbolic-ref HEAD >actual &&
26
+ echo refs/heads/master >expect &&
27
+ test_cmp expect actual &&
28
+ git fsck
29
+ )
30
+'
31
+
32
+test_expect_success 'checkout --to a new worktree from a subdir' '
33
+ (
34
+ mkdir sub &&
35
+ cd sub &&
36
+ git checkout --detach --to here master &&
37
+ cd here &&
38
+ test_cmp ../../init.t init.t
39
+ )
40
+'
41
+
42
+test_expect_success 'checkout --to from a linked checkout' '
43
+ (
44
+ cd here &&
45
+ git checkout --to nested-here master &&
46
+ cd nested-here &&
47
+ git fsck
48
+ )
49
+'
50
+
51
+test_expect_success 'checkout --to a new worktree creating new branch' '
52
+ git checkout --to there -b newmaster master &&
53
+ (
54
+ cd there &&
55
+ test_cmp ../init.t init.t &&
56
+ git symbolic-ref HEAD >actual &&
57
+ echo refs/heads/newmaster >expect &&
58
+ test_cmp expect actual &&
59
+ git fsck
60
+ )
61
+'
62
+
63
+test_done