From c990a4c11dd7bb671da1b30e14568ad986621488 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:45 -0400 Subject: checkout: fix bug with --to and relative HEAD Given "git checkout --to HEAD~1", the new worktree's HEAD should begin life at the current branch's HEAD~1, however, it actually ends up at HEAD~2. This happens because: 1. git-checkout resolves HEAD~1 2. to satisfy is_git_directory(), prepare_linked_worktree() creates a HEAD for the new worktree with the value of the resolved HEAD~1 3. git-checkout re-invokes itself with the same arguments within the new worktree to populate the worktree 4. the sub git-checkout resolves HEAD~1 relative to its own HEAD, which is the resolved HEAD~1 from the original invocation, resulting unexpectedly and incorrectly in HEAD~2 (relative to the original) Fix this by unconditionally assigning the current worktree's HEAD as the value of the new worktree's HEAD. As a side-effect, this change also eliminates a dependence within prepare_linked_checkout() upon 'struct branch_info'. The plan is to eventually relocate "git checkout --to" functionality to "git worktree add", and worktree.c won't have knowledge of 'struct branch_info', so removal of this dependency is a step toward that goal. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 2079aa417..5ada22a39 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -863,6 +863,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, struct stat st; struct child_process cp; int counter = 0, len, ret; + unsigned char rev[20]; if (!new->commit) die(_("no branch specified")); @@ -920,13 +921,20 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, real_path(get_git_common_dir()), name); /* * This is to keep resolve_ref() happy. We need a valid HEAD - * or is_git_directory() will reject the directory. Any valid - * value would do because this value will be ignored and - * replaced at the next (real) checkout. + * or is_git_directory() will reject the directory. Moreover, HEAD + * in the new worktree must resolve to the same value as HEAD in + * the current tree since the command invoked to populate the new + * worktree will be handed the branch/ref specified by the user. + * For instance, if the user asks for the new worktree to be based + * at HEAD~5, then the resolved HEAD~5 in the new worktree must + * match the resolved HEAD~5 in the current tree in order to match + * the user's expectation. */ + if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) + die(_("unable to resolve HEAD")); strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, 1, "%s\n", sha1_to_hex(new->commit->object.sha1)); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev)); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, 1, "../..\n"); -- cgit v1.2.1 From 9559ce8368b533862584b2717532e3c25232f1c9 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:46 -0400 Subject: checkout: relocate --to's "no branch specified" check The plan is to relocate "git checkout --to" functionality to "git worktree add", however, this check expects a 'struct branch_info' which git-worktree won't have at hand. It will, however, have access to its own command-line from which it can pick up the branch name. Therefore, as a preparatory step, rather than having prepare_linked_checkout() perform this check, make it the caller's responsibility. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 5ada22a39..162c82226 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -865,8 +865,6 @@ static int prepare_linked_checkout(const struct checkout_opts *opts, int counter = 0, len, ret; unsigned char rev[20]; - if (!new->commit) - die(_("no branch specified")); if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); @@ -1303,8 +1301,11 @@ static int checkout_branch(struct checkout_opts *opts, free(head_ref); } - if (opts->new_worktree) + if (opts->new_worktree) { + if (!new->commit) + die(_("no branch specified")); return prepare_linked_checkout(opts, new); + } if (!new->commit && opts->new_branch) { unsigned char rev[20]; -- cgit v1.2.1 From 3c3e7f5b57f94b8f5fcba5042cf2412625c24d4a Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:47 -0400 Subject: checkout: prepare_linked_checkout: drop now-unused 'new' argument The only references to 'new' were folded out by the last two patches. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 162c82226..134b6d65f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -854,8 +854,7 @@ static void remove_junk_on_signal(int signo) raise(signo); } -static int prepare_linked_checkout(const struct checkout_opts *opts, - struct branch_info *new) +static int prepare_linked_checkout(const struct checkout_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; @@ -1304,7 +1303,7 @@ static int checkout_branch(struct checkout_opts *opts, if (opts->new_worktree) { if (!new->commit) die(_("no branch specified")); - return prepare_linked_checkout(opts, new); + return prepare_linked_checkout(opts); } if (!new->commit && opts->new_branch) { -- cgit v1.2.1 From 338dfd0da4b7fd3ccf0772788d64cbcdf089f486 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:48 -0400 Subject: checkout: make --to unconditionally verbose prepare_linked_checkout() respects git-checkout's --quiet flag, however, the plan is to relocate "git checkout --to" functionality to "git worktree add", and git-worktree does not (yet) have a --quiet flag. Consequently, make prepare_linked_checkout() unconditionally verbose to ease eventual code movement to worktree.c. (A --quiet flag can be added to git-worktree later if there is demand for it.) Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 134b6d65f..90bb3cdf2 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -936,8 +936,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts) strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, 1, "../..\n"); - if (!opts->quiet) - fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); -- cgit v1.2.1 From bdf0f375b9cf1c272233583a03eddef1772da99e Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:49 -0400 Subject: checkout: drop 'checkout_opts' dependency from prepare_linked_checkout The plan is to relocate "git checkout --to" functionality to "git worktree add", however, worktree.c won't have access to the 'struct checkout_opts' passed to prepare_linked_worktree(), which it consults for the pathname of the new worktree and the argv[] of the command it should run to populate the new worktree. Facilitate relocation of prepare_linked_worktree() by instead having it accept the pathname and argv[] directly, thus eliminating the final references to 'struct checkout_opts'. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 90bb3cdf2..e4064a84c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -854,11 +854,11 @@ static void remove_junk_on_signal(int signo) raise(signo); } -static int prepare_linked_checkout(const struct checkout_opts *opts) +static int prepare_linked_checkout(const char *path, const char **child_argv) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; - const char *path = opts->new_worktree, *name; + const char *name; struct stat st; struct child_process cp; int counter = 0, len, ret; @@ -943,7 +943,7 @@ static int prepare_linked_checkout(const struct checkout_opts *opts) setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); memset(&cp, 0, sizeof(cp)); cp.git_cmd = 1; - cp.argv = opts->saved_argv; + cp.argv = child_argv; ret = run_command(&cp); if (!ret) { is_junk = 0; @@ -1302,7 +1302,8 @@ static int checkout_branch(struct checkout_opts *opts, if (opts->new_worktree) { if (!new->commit) die(_("no branch specified")); - return prepare_linked_checkout(opts); + return prepare_linked_checkout(opts->new_worktree, + opts->saved_argv); } if (!new->commit && opts->new_branch) { -- cgit v1.2.1 From fc56361f58a20f1259e1ba2364424ec27825c37f Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:50 -0400 Subject: worktree: introduce "add" command The plan is to relocate "git checkout --to" functionality to "git worktree add". As a first step, introduce a bare-bones git-worktree "add" command along with documentation. At this stage, "git worktree add" merely invokes "git checkout --to" behind the scenes, but an upcoming patch will move the actual functionality (checkout.c:prepare_linked_checkout() and its helpers) to worktree.c. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 2a729c661..e0749c099 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -2,8 +2,11 @@ #include "builtin.h" #include "dir.h" #include "parse-options.h" +#include "argv-array.h" +#include "run-command.h" static const char * const worktree_usage[] = { + N_("git worktree add "), N_("git worktree prune []"), NULL }; @@ -119,6 +122,32 @@ static int prune(int ac, const char **av, const char *prefix) return 0; } +static int add(int ac, const char **av, const char *prefix) +{ + struct child_process c; + const char *path, *branch; + struct argv_array cmd = ARGV_ARRAY_INIT; + struct option options[] = { + OPT_END() + }; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 2) + usage_with_options(worktree_usage, options); + + path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; + branch = av[1]; + + argv_array_push(&cmd, "checkout"); + argv_array_pushl(&cmd, "--to", path, NULL); + argv_array_push(&cmd, branch); + + memset(&c, 0, sizeof(c)); + c.git_cmd = 1; + c.argv = cmd.argv; + return run_command(&c); +} + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -127,6 +156,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix) if (ac < 2) usage_with_options(worktree_usage, options); + if (!strcmp(av[1], "add")) + return add(ac - 1, av + 1, prefix); if (!strcmp(av[1], "prune")) return prune(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); -- cgit v1.2.1 From f43254440d56da1fd52216cbebd5c285b20985e6 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:51 -0400 Subject: worktree: add --force option By default, "git worktree add" refuses to create a new worktree when the requested branch is already checked out elsewhere. Add a --force option to override this safeguard. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index e0749c099..8c35023a4 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -6,7 +6,7 @@ #include "run-command.h" static const char * const worktree_usage[] = { - N_("git worktree add "), + N_("git worktree add [] "), N_("git worktree prune []"), NULL }; @@ -125,9 +125,11 @@ static int prune(int ac, const char **av, const char *prefix) static int add(int ac, const char **av, const char *prefix) { struct child_process c; + int force = 0; const char *path, *branch; struct argv_array cmd = ARGV_ARRAY_INIT; struct option options[] = { + OPT__FORCE(&force, N_("checkout even if already checked out in other worktree")), OPT_END() }; @@ -140,6 +142,8 @@ static int add(int ac, const char **av, const char *prefix) argv_array_push(&cmd, "checkout"); argv_array_pushl(&cmd, "--to", path, NULL); + if (force) + argv_array_push(&cmd, "--ignore-other-worktrees"); argv_array_push(&cmd, branch); memset(&c, 0, sizeof(c)); -- cgit v1.2.1 From 39ecb27436296361371d3caadcc9bc4a4e0b556d Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:52 -0400 Subject: worktree: add --detach option One of git-worktree's roles is to populate the new worktree, much like git-checkout, and thus, for convenience, ought to support several of the same shortcuts. Toward this goal, add a --detach option to detach HEAD in the new worktree. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 8c35023a4..9dc92b07a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -125,11 +125,12 @@ static int prune(int ac, const char **av, const char *prefix) static int add(int ac, const char **av, const char *prefix) { struct child_process c; - int force = 0; + int force = 0, detach = 0; const char *path, *branch; struct argv_array cmd = ARGV_ARRAY_INIT; struct option options[] = { OPT__FORCE(&force, N_("checkout even if already checked out in other worktree")), + OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")), OPT_END() }; @@ -144,6 +145,8 @@ static int add(int ac, const char **av, const char *prefix) argv_array_pushl(&cmd, "--to", path, NULL); if (force) argv_array_push(&cmd, "--ignore-other-worktrees"); + if (detach) + argv_array_push(&cmd, "--detach"); argv_array_push(&cmd, branch); memset(&c, 0, sizeof(c)); -- cgit v1.2.1 From cbdf60fa183e3a2330849046846b400ef4a99c3b Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:53 -0400 Subject: worktree: add -b/-B options One of git-worktree's roles is to populate the new worktree, much like git-checkout, and thus, for convenience, ought to support several of the same shortcuts. Toward this goal, add -b/-B options to create a new branch and check it out in the new worktree. (For brevity, only -b is mentioned in the synopsis; -B is omitted.) Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 9dc92b07a..d6d0eee3c 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -126,15 +126,22 @@ static int add(int ac, const char **av, const char *prefix) { struct child_process c; int force = 0, detach = 0; + const char *new_branch = NULL, *new_branch_force = NULL; const char *path, *branch; struct argv_array cmd = ARGV_ARRAY_INIT; struct option options[] = { OPT__FORCE(&force, N_("checkout even if already checked out in other worktree")), + OPT_STRING('b', NULL, &new_branch, N_("branch"), + N_("create a new branch")), + OPT_STRING('B', NULL, &new_branch_force, N_("branch"), + N_("create or reset a branch")), OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")), OPT_END() }; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (new_branch && new_branch_force) + die(_("-b and -B are mutually exclusive")); if (ac != 2) usage_with_options(worktree_usage, options); @@ -145,6 +152,10 @@ static int add(int ac, const char **av, const char *prefix) argv_array_pushl(&cmd, "--to", path, NULL); if (force) argv_array_push(&cmd, "--ignore-other-worktrees"); + if (new_branch) + argv_array_pushl(&cmd, "-b", new_branch, NULL); + if (new_branch_force) + argv_array_pushl(&cmd, "-B", new_branch_force, NULL); if (detach) argv_array_push(&cmd, "--detach"); argv_array_push(&cmd, branch); -- cgit v1.2.1 From b979d95027242455b10e6f566b0e96c5f30cc908 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:55 -0400 Subject: checkout: retire --to option Now that "git worktree add" has achieved user-facing feature-parity with "git checkout --to", retire the latter. Move the actual linked worktree creation functionality, prepare_linked_checkout() and its helpers, verbatim from checkout.c to worktree.c. This effectively reverts changes to checkout.c by 529fef2 (checkout: support checking out into a new working directory, 2014-11-30) with the exception of merge_working_tree() and switch_branches() which still require specialized knowledge that a the checkout is occurring in a newly-created linked worktree (signaled to them by the private GIT_CHECKOUT_NEW_WORKTREE environment variable). Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 161 +---------------------------------------------------- builtin/worktree.c | 144 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 166 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index e4064a84c..b1e68b326 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,8 +19,6 @@ #include "ll-merge.h" #include "resolve-undo.h" #include "submodule.h" -#include "argv-array.h" -#include "sigchain.h" static const char * const checkout_usage[] = { N_("git checkout [options] "), @@ -51,8 +49,6 @@ struct checkout_opts { struct pathspec pathspec; struct tree *source_tree; - const char *new_worktree; - const char **saved_argv; int new_worktree_mode; }; @@ -255,9 +251,6 @@ 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); @@ -825,142 +818,6 @@ static int switch_branches(const struct checkout_opts *opts, return ret || writeout_error; } -static char *junk_work_tree; -static char *junk_git_dir; -static int is_junk; -static pid_t junk_pid; - -static void remove_junk(void) -{ - struct strbuf sb = STRBUF_INIT; - if (!is_junk || getpid() != junk_pid) - return; - if (junk_git_dir) { - strbuf_addstr(&sb, junk_git_dir); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); - } - if (junk_work_tree) { - strbuf_addstr(&sb, junk_work_tree); - remove_dir_recursively(&sb, 0); - } - strbuf_release(&sb); -} - -static void remove_junk_on_signal(int signo) -{ - remove_junk(); - sigchain_pop(signo); - raise(signo); -} - -static int prepare_linked_checkout(const char *path, const char **child_argv) -{ - struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT; - const char *name; - struct stat st; - struct child_process cp; - int counter = 0, len, ret; - unsigned char rev[20]; - - if (file_exists(path) && !is_empty_dir(path)) - die(_("'%s' already exists"), path); - - len = strlen(path); - while (len && is_dir_sep(path[len - 1])) - len--; - - for (name = path + len - 1; name > path; name--) - if (is_dir_sep(*name)) { - name++; - break; - } - strbuf_addstr(&sb_repo, - git_path("worktrees/%.*s", (int)(path + len - name), 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 = strrchr(sb_repo.buf, '/') + 1; - - junk_pid = getpid(); - atexit(remove_junk); - sigchain_push_common(remove_junk_on_signal); - - if (mkdir(sb_repo.buf, 0777)) - die_errno(_("could not create directory of '%s'"), sb_repo.buf); - junk_git_dir = xstrdup(sb_repo.buf); - is_junk = 1; - - /* - * lock the incomplete repo so prune won't delete it, unlock - * after the preparation is over. - */ - strbuf_addf(&sb, "%s/locked", sb_repo.buf); - write_file(sb.buf, 1, "initializing\n"); - - 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); - junk_work_tree = xstrdup(path); - - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); - write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", - real_path(get_git_common_dir()), name); - /* - * This is to keep resolve_ref() happy. We need a valid HEAD - * or is_git_directory() will reject the directory. Moreover, HEAD - * in the new worktree must resolve to the same value as HEAD in - * the current tree since the command invoked to populate the new - * worktree will be handed the branch/ref specified by the user. - * For instance, if the user asks for the new worktree to be based - * at HEAD~5, then the resolved HEAD~5 in the new worktree must - * match the resolved HEAD~5 in the current tree in order to match - * the user's expectation. - */ - if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) - die(_("unable to resolve HEAD")); - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev)); - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/commondir", sb_repo.buf); - write_file(sb.buf, 1, "../..\n"); - - fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); - - 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 = child_argv; - ret = run_command(&cp); - if (!ret) { - is_junk = 0; - free(junk_work_tree); - free(junk_git_dir); - junk_work_tree = NULL; - junk_git_dir = NULL; - } - strbuf_reset(&sb); - strbuf_addf(&sb, "%s/locked", sb_repo.buf); - unlink_or_warn(sb.buf); - strbuf_release(&sb); - strbuf_release(&sb_repo); - strbuf_release(&sb_git); - return ret; -} - static int git_checkout_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.ignoresubmodules")) { @@ -1299,13 +1156,6 @@ static int checkout_branch(struct checkout_opts *opts, free(head_ref); } - if (opts->new_worktree) { - if (!new->commit) - die(_("no branch specified")); - return prepare_linked_checkout(opts->new_worktree, - opts->saved_argv); - } - if (!new->commit && opts->new_branch) { unsigned char rev[20]; int flag; @@ -1348,8 +1198,6 @@ 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_FILENAME(0, "to", &opts.new_worktree, - N_("check a branch out in a separate working directory")), OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, N_("do not check if another worktree is holding the given ref")), OPT_END(), @@ -1360,9 +1208,6 @@ 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); @@ -1371,13 +1216,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); - /* recursive execution from checkout_new_worktree() */ opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; - if (opts.new_worktree_mode) - opts.new_worktree = NULL; - if (!opts.new_worktree) - setup_work_tree(); + setup_work_tree(); if (conflict_style) { opts.merge = 1; /* implied */ diff --git a/builtin/worktree.c b/builtin/worktree.c index d6d0eee3c..04e6d0f92 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -4,6 +4,7 @@ #include "parse-options.h" #include "argv-array.h" #include "run-command.h" +#include "sigchain.h" static const char * const worktree_usage[] = { N_("git worktree add [] "), @@ -122,9 +123,144 @@ static int prune(int ac, const char **av, const char *prefix) return 0; } +static char *junk_work_tree; +static char *junk_git_dir; +static int is_junk; +static pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb = STRBUF_INIT; + if (!is_junk || getpid() != junk_pid) + return; + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + } + strbuf_release(&sb); +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + sigchain_pop(signo); + raise(signo); +} + +static int add_worktree(const char *path, const char **child_argv) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *name; + struct stat st; + struct child_process cp; + int counter = 0, len, ret; + unsigned char rev[20]; + + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + strbuf_addstr(&sb_repo, + git_path("worktrees/%.*s", (int)(path + len - name), 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 = strrchr(sb_repo.buf, '/') + 1; + + junk_pid = getpid(); + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + + if (mkdir(sb_repo.buf, 0777)) + die_errno(_("could not create directory of '%s'"), sb_repo.buf); + junk_git_dir = xstrdup(sb_repo.buf); + is_junk = 1; + + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, 1, "initializing\n"); + + 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); + junk_work_tree = xstrdup(path); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); + write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", + real_path(get_git_common_dir()), name); + /* + * This is to keep resolve_ref() happy. We need a valid HEAD + * or is_git_directory() will reject the directory. Moreover, HEAD + * in the new worktree must resolve to the same value as HEAD in + * the current tree since the command invoked to populate the new + * worktree will be handed the branch/ref specified by the user. + * For instance, if the user asks for the new worktree to be based + * at HEAD~5, then the resolved HEAD~5 in the new worktree must + * match the resolved HEAD~5 in the current tree in order to match + * the user's expectation. + */ + if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) + die(_("unable to resolve HEAD")); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, 1, "../..\n"); + + fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + + 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 = child_argv; + ret = run_command(&cp); + if (!ret) { + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + strbuf_release(&sb); + strbuf_release(&sb_repo); + strbuf_release(&sb_git); + return ret; +} + static int add(int ac, const char **av, const char *prefix) { - struct child_process c; int force = 0, detach = 0; const char *new_branch = NULL, *new_branch_force = NULL; const char *path, *branch; @@ -149,7 +285,6 @@ static int add(int ac, const char **av, const char *prefix) branch = av[1]; argv_array_push(&cmd, "checkout"); - argv_array_pushl(&cmd, "--to", path, NULL); if (force) argv_array_push(&cmd, "--ignore-other-worktrees"); if (new_branch) @@ -160,10 +295,7 @@ static int add(int ac, const char **av, const char *prefix) argv_array_push(&cmd, "--detach"); argv_array_push(&cmd, branch); - memset(&c, 0, sizeof(c)); - c.git_cmd = 1; - c.argv = cmd.argv; - return run_command(&c); + return add_worktree(path, cmd.argv); } int cmd_worktree(int ac, const char **av, const char *prefix) -- cgit v1.2.1 From 0ca560cb975aa081971d0fb1586e0206156fa22f Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:56 -0400 Subject: checkout: require worktree unconditionally In order to allow linked worktree creation via "git checkout --to" from a bare repository, 3473ad0 (checkout: don't require a work tree when checking out into a new one, 2014-11-30) dropped git-checkout's unconditional NEED_WORK_TREE requirement and instead performed worktree setup conditionally based upon presence or absence of the --to option. Now that --to has been retired and git-checkout is no longer responsible for linked worktree creation, the NEED_WORK_TREE requirement can be re-instated. This effectively reverts 3473ad0, except for the tests it added which now check bare repository behavior of "git worktree add" instead. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index b1e68b326..57545543e 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1218,8 +1218,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; - setup_work_tree(); - if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); -- cgit v1.2.1 From f5682b2a861cbde6e9aca324caa5e2d36c49569a Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:57 -0400 Subject: worktree: extract basename computation to new function A subsequent patch will also need to compute the basename of the new worktree, so factor out this logic into a new function. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 04e6d0f92..25fe25b45 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -152,6 +152,25 @@ static void remove_junk_on_signal(int signo) raise(signo); } +static const char *worktree_basename(const char *path, int *olen) +{ + const char *name; + int len; + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + + *olen = len; + return name; +} + static int add_worktree(const char *path, const char **child_argv) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; @@ -165,15 +184,7 @@ static int add_worktree(const char *path, const char **child_argv) if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); - len = strlen(path); - while (len && is_dir_sep(path[len - 1])) - len--; - - for (name = path + len - 1; name > path; name--) - if (is_dir_sep(*name)) { - name++; - break; - } + name = worktree_basename(path, &len); strbuf_addstr(&sb_repo, git_path("worktrees/%.*s", (int)(path + len - name), name)); len = sb_repo.len; -- cgit v1.2.1 From 0f4af3b9ea1fc62e445271bb2e7fbb8e1ac230b7 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:58 -0400 Subject: worktree: add: make -b/-B default to HEAD when is omitted As a convenience, like "git branch" and "git checkout -b", make "git worktree add -b " default to HEAD when is omitted. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 25fe25b45..323d444b3 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -289,11 +289,13 @@ static int add(int ac, const char **av, const char *prefix) ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (new_branch && new_branch_force) die(_("-b and -B are mutually exclusive")); - if (ac != 2) + if (ac < 1 || ac > 2) + usage_with_options(worktree_usage, options); + if (ac < 2 && !new_branch && !new_branch_force) usage_with_options(worktree_usage, options); path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; - branch = av[1]; + branch = ac < 2 ? "HEAD" : av[1]; argv_array_push(&cmd, "checkout"); if (force) -- cgit v1.2.1 From 1eb07d829f3f0992c93c6b44fdcc4e95ebab12f3 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:30:59 -0400 Subject: worktree: add: auto-vivify new branch when is omitted As a convenience, when is omitted from "git worktree " and neither -b nor -B is used, automatically create a new branch named after , as if "-b $(basename )" was specified. Thus, "git worktree add ../hotfix" creates a new branch named "hotfix" and associates it with new worktree "../hotfix". Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/worktree.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'builtin') diff --git a/builtin/worktree.c b/builtin/worktree.c index 323d444b3..69248ba0a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -291,12 +291,16 @@ static int add(int ac, const char **av, const char *prefix) die(_("-b and -B are mutually exclusive")); if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); - if (ac < 2 && !new_branch && !new_branch_force) - usage_with_options(worktree_usage, options); path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; branch = ac < 2 ? "HEAD" : av[1]; + if (ac < 2 && !new_branch && !new_branch_force) { + int n; + const char *s = worktree_basename(path, &n); + new_branch = xstrndup(s, n); + } + argv_array_push(&cmd, "checkout"); if (force) argv_array_push(&cmd, "--ignore-other-worktrees"); -- cgit v1.2.1 From 0d1a1517835a10818f2d40d8780a268dbb5e20ce Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 6 Jul 2015 13:31:00 -0400 Subject: checkout: retire --ignore-other-worktrees in favor of --force As a safeguard, checking out a branch already checked out by a different worktree is disallowed. This behavior can be overridden with --ignore-other-worktrees, however, this option is neither obvious nor particularly discoverable. As a common safeguard override, --force is more likely to come to mind. Therefore, overload it to also suppress the check for a branch already checked out elsewhere. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- builtin/checkout.c | 8 +++----- builtin/worktree.c | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 57545543e..01c7c30d5 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -35,7 +35,6 @@ struct checkout_opts { int writeout_stage; int overwrite_ignore; int ignore_skipworktree; - int ignore_other_worktrees; const char *new_branch; const char *new_branch_force; @@ -903,7 +902,8 @@ static void check_linked_checkout(struct branch_info *new, const char *id) strbuf_rtrim(&gitdir); } else strbuf_addstr(&gitdir, get_git_common_dir()); - die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); + die(_("'%s' is already checked out at '%s'; use --force to override"), + new->name, gitdir.buf); done: strbuf_release(&path); strbuf_release(&sb); @@ -1151,7 +1151,7 @@ static int checkout_branch(struct checkout_opts *opts, char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); if (head_ref && (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && - !opts->ignore_other_worktrees) + !opts->force) check_linked_checkouts(new); free(head_ref); } @@ -1198,8 +1198,6 @@ 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_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, - N_("do not check if another worktree is holding the given ref")), OPT_END(), }; diff --git a/builtin/worktree.c b/builtin/worktree.c index 69248ba0a..050b443dc 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -303,7 +303,7 @@ static int add(int ac, const char **av, const char *prefix) argv_array_push(&cmd, "checkout"); if (force) - argv_array_push(&cmd, "--ignore-other-worktrees"); + argv_array_push(&cmd, "--force"); if (new_branch) argv_array_pushl(&cmd, "-b", new_branch, NULL); if (new_branch_force) -- cgit v1.2.1 From c925fe23684455735c3bb1903803643a24a58d8f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 12 Jul 2015 09:38:21 -0700 Subject: Revert "checkout: retire --ignore-other-worktrees in favor of --force" This reverts commit 0d1a1517835a10818f2d40d8780a268dbb5e20ce. When trying to switch to a different branch, that happens to be checked out in another working tree, the user shouldn't have to give up the other safety measures (like protecting the local changes that overlap the difference between the branches) while defeating the "no two checkouts of the same branch" safety. Signed-off-by: Junio C Hamano --- builtin/checkout.c | 8 +++++--- builtin/worktree.c | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'builtin') diff --git a/builtin/checkout.c b/builtin/checkout.c index 01c7c30d5..57545543e 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -35,6 +35,7 @@ struct checkout_opts { int writeout_stage; int overwrite_ignore; int ignore_skipworktree; + int ignore_other_worktrees; const char *new_branch; const char *new_branch_force; @@ -902,8 +903,7 @@ static void check_linked_checkout(struct branch_info *new, const char *id) strbuf_rtrim(&gitdir); } else strbuf_addstr(&gitdir, get_git_common_dir()); - die(_("'%s' is already checked out at '%s'; use --force to override"), - new->name, gitdir.buf); + die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); done: strbuf_release(&path); strbuf_release(&sb); @@ -1151,7 +1151,7 @@ static int checkout_branch(struct checkout_opts *opts, char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); if (head_ref && (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && - !opts->force) + !opts->ignore_other_worktrees) check_linked_checkouts(new); free(head_ref); } @@ -1198,6 +1198,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_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, + N_("do not check if another worktree is holding the given ref")), OPT_END(), }; diff --git a/builtin/worktree.c b/builtin/worktree.c index 050b443dc..69248ba0a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -303,7 +303,7 @@ static int add(int ac, const char **av, const char *prefix) argv_array_push(&cmd, "checkout"); if (force) - argv_array_push(&cmd, "--force"); + argv_array_push(&cmd, "--ignore-other-worktrees"); if (new_branch) argv_array_pushl(&cmd, "-b", new_branch, NULL); if (new_branch_force) -- cgit v1.2.1