diff options
-rw-r--r-- | Documentation/git-checkout.txt | 7 | ||||
-rw-r--r-- | builtin/checkout.c | 161 | ||||
-rw-r--r-- | builtin/worktree.c | 144 |
3 files changed, 139 insertions, 173 deletions
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 77b7141f02..efe6a026f1 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -225,13 +225,6 @@ 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 branch in a separate working directory at - `<path>`. A new working directory is linked to the current - repository, sharing everything except working directory - specific files such as HEAD, index, etc. See - linkgit:git-worktree[1] for a description of linked worktrees. - --ignore-other-worktrees:: `git checkout` refuses when the wanted ref is already checked out by another worktree. This option makes it check the ref diff --git a/builtin/checkout.c b/builtin/checkout.c index e4064a84c9..b1e68b3263 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] <branch>"), @@ -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 d6d0eee3ca..04e6d0f927 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 [<options>] <path> <branch>"), @@ -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) |