aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2015-07-13 14:02:18 -0700
committerJunio C Hamano <gitster@pobox.com>2015-07-13 14:02:19 -0700
commit799767cc98b2f8e6f82d0de4bef9b5e8fcc16e97 (patch)
tree6b4292f1c30feec0958156c66c1a724dc067be9e
parent7783eb2e59684492e75068443e1f77f64fe37cc9 (diff)
parentc925fe23684455735c3bb1903803643a24a58d8f (diff)
downloadgit-799767cc98b2f8e6f82d0de4bef9b5e8fcc16e97.tar.gz
git-799767cc98b2f8e6f82d0de4bef9b5e8fcc16e97.tar.xz
Merge branch 'es/worktree-add'
Update to the "linked checkout" in 2.5.0-rc1. Instead of "checkout --to" that does not do what "checkout" normally does, move the functionality to "git worktree add". * es/worktree-add: (24 commits) Revert "checkout: retire --ignore-other-worktrees in favor of --force" checkout: retire --ignore-other-worktrees in favor of --force worktree: add: auto-vivify new branch when <branch> is omitted worktree: add: make -b/-B default to HEAD when <branch> is omitted worktree: extract basename computation to new function checkout: require worktree unconditionally checkout: retire --to option tests: worktree: retrofit "checkout --to" tests for "worktree add" worktree: add -b/-B options worktree: add --detach option worktree: add --force option worktree: introduce "add" command checkout: drop 'checkout_opts' dependency from prepare_linked_checkout checkout: make --to unconditionally verbose checkout: prepare_linked_checkout: drop now-unused 'new' argument checkout: relocate --to's "no branch specified" check checkout: fix bug with --to and relative HEAD Documentation/git-worktree: add EXAMPLES section Documentation/git-worktree: add high-level 'lock' overview Documentation/git-worktree: split technical info from general description ...
-rw-r--r--Documentation/git-checkout.txt72
-rw-r--r--Documentation/git-worktree.txt141
-rw-r--r--builtin/checkout.c153
-rw-r--r--builtin/worktree.c199
-rw-r--r--git.c2
-rwxr-xr-xt/t2025-worktree-add.sh (renamed from t/t2025-checkout-to.sh)73
-rwxr-xr-xt/t2026-prune-linked-checkouts.sh2
-rwxr-xr-xt/t7410-submodule-checkout-to.sh4
8 files changed, 386 insertions, 260 deletions
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 2df6d0d4b..63b739c55 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -229,13 +229,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... See "MULTIPLE WORKING
- TREES" section for more information.
-
--ignore-other-worktrees::
`git checkout` refuses when the wanted ref is already checked
out by another worktree. This option makes it check the ref
@@ -405,71 +398,6 @@ $ git reflog -2 HEAD # or
$ git log -g -2 HEAD
------------
-MULTIPLE WORKING TREES
-----------------------
-
-A git repository can support multiple working trees, allowing you to check
-out more than one branch at a time. With `git checkout --to` a new working
-tree is associated with the repository. This new working tree is called a
-"linked working tree" as opposed to the "main working tree" prepared by "git
-init" or "git clone". A repository has one main working tree (if it's not a
-bare repository) and zero or more linked working trees.
-
-Each linked working tree has a private sub-directory in the repository's
-$GIT_DIR/worktrees directory. The private sub-directory's name is usually
-the base name of the linked working tree's path, possibly appended with a
-number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
-command `git checkout --to /path/other/test-next next` creates the linked
-working tree in `/path/other/test-next` and also creates a
-`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
-if `test-next` is already taken).
-
-Within a linked working tree, $GIT_DIR is set to point to this private
-directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
-$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
-(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
-the top directory of the linked working tree.
-
-Path resolution via `git rev-parse --git-path` uses either
-$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
-linked working tree `git rev-parse --git-path HEAD` returns
-`/path/main/.git/worktrees/test-next/HEAD` (not
-`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
-rev-parse --git-path refs/heads/master` uses
-$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
-since refs are shared across all working trees.
-
-See linkgit:gitrepository-layout[5] for more information. The rule of
-thumb is do not make any assumption about whether a path belongs to
-$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
-inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
-
-When you are done with a linked working tree you can simply delete it.
-The working tree's entry in the repository's $GIT_DIR/worktrees
-directory will eventually be removed automatically (see
-`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
-`git prune --worktrees` in the main or any linked working tree to
-clean up any stale entries in $GIT_DIR/worktrees.
-
-If you move a linked working directory to another file system, or
-within a file system that does not support hard links, you need to run
-at least one git command inside the linked working directory
-(e.g. `git status`) in order to update its entry in $GIT_DIR/worktrees
-so that it does not get automatically removed.
-
-To prevent a $GIT_DIR/worktrees entry from from being pruned (which
-can be useful in some situations, such as when the
-entry's working tree is stored on a portable device), add a file named
-'locked' to the entry's directory. The file contains the reason in
-plain text. For example, if a linked working tree's `.git` file points
-to `/path/main/.git/worktrees/test-next` then a file named
-`/path/main/.git/worktrees/test-next/locked` will prevent the
-`test-next` entry from being pruned. See
-linkgit:gitrepository-layout[5] for details.
-
-Multiple checkout support for submodules is incomplete. It is NOT
-recommended to make multiple checkouts of a superproject.
-
EXAMPLES
--------
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 41103e548..da71f508b 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,16 +9,52 @@ git-worktree - Manage multiple worktrees
SYNOPSIS
--------
[verse]
+'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
'git worktree prune' [-n] [-v] [--expire <expire>]
DESCRIPTION
-----------
-Manage multiple worktrees attached to the same repository. These are
-created by the command `git checkout --to`.
+Manage multiple worktrees attached to the same repository.
+
+A git repository can support multiple working trees, allowing you to check
+out more than one branch at a time. With `git checkout --to` a new working
+tree is associated with the repository. This new working tree is called a
+"linked working tree" as opposed to the "main working tree" prepared by "git
+init" or "git clone". A repository has one main working tree (if it's not a
+bare repository) and zero or more linked working trees.
+
+When you are done with a linked working tree you can simply delete it.
+The working tree's administrative files in the repository (see
+"DETAILS" below) will eventually be removed automatically (see
+`gc.pruneworktreesexpire` in linkgit::git-config[1]), or you can run
+`git worktree prune` in the main or any linked working tree to
+clean up any stale administrative files.
+
+If you move a linked working directory to another file system, or
+within a file system that does not support hard links, you need to run
+at least one git command inside the linked working directory
+(e.g. `git status`) in order to update its administrative files in the
+repository so that they do not get automatically pruned.
+
+If a linked working tree is stored on a portable device or network share
+which is not always mounted, you can prevent its administrative files from
+being pruned by creating a file named 'lock' alongside the other
+administrative files, optionally containing a plain text reason that
+pruning should be suppressed. See section "DETAILS" for more information.
COMMANDS
--------
+add <path> [<branch>]::
+
+Create `<path>` and checkout `<branch>` into it. The new working directory
+is linked to the current repository, sharing everything except working
+directory specific files such as HEAD, index, etc.
++
+If `<branch>` is omitted and neither `-b` nor `-B` is used, then, as a
+convenience, a new branch based at HEAD is created automatically, as if
+`-b $(basename <path>)` was specified.
+
prune::
Prune working tree information in $GIT_DIR/worktrees.
@@ -26,22 +62,113 @@ Prune working tree information in $GIT_DIR/worktrees.
OPTIONS
-------
+-f::
+--force::
+ By default, `add` refuses to create a new worktree when `<branch>`
+ is already checked out by another worktree. This option overrides
+ that safeguard.
+
+-b <new-branch>::
+-B <new-branch>::
+ With `add`, create a new branch named `<new-branch>` starting at
+ `<branch>`, and check out `<new-branch>` into the new worktree.
+ If `<branch>` is omitted, it defaults to HEAD.
+ By default, `-b` refuses to create a new branch if it already
+ exists. `-B` overrides this safeguard, resetting `<new-branch>` to
+ `<branch>`.
+
+--detach::
+ With `add`, detach HEAD in the new worktree. See "DETACHED HEAD" in
+ linkgit:git-checkout[1].
+
-n::
--dry-run::
- Do not remove anything; just report what it would
+ With `prune`, do not remove anything; just report what it would
remove.
-v::
--verbose::
- Report all removals.
+ With `prune`, report all removals.
--expire <time>::
- Only expire unused worktrees older than <time>.
+ With `prune`, only expire unused worktrees older than <time>.
-SEE ALSO
+DETAILS
+-------
+Each linked working tree has a private sub-directory in the repository's
+$GIT_DIR/worktrees directory. The private sub-directory's name is usually
+the base name of the linked working tree's path, possibly appended with a
+number to make it unique. For example, when `$GIT_DIR=/path/main/.git` the
+command `git checkout --to /path/other/test-next next` creates the linked
+working tree in `/path/other/test-next` and also creates a
+`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
+if `test-next` is already taken).
+
+Within a linked working tree, $GIT_DIR is set to point to this private
+directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
+$GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR
+(e.g. `/path/main/.git`). These settings are made in a `.git` file located at
+the top directory of the linked working tree.
+
+Path resolution via `git rev-parse --git-path` uses either
+$GIT_DIR or $GIT_COMMON_DIR depending on the path. For example, in the
+linked working tree `git rev-parse --git-path HEAD` returns
+`/path/main/.git/worktrees/test-next/HEAD` (not
+`/path/other/test-next/.git/HEAD` or `/path/main/.git/HEAD`) while `git
+rev-parse --git-path refs/heads/master` uses
+$GIT_COMMON_DIR and returns `/path/main/.git/refs/heads/master`,
+since refs are shared across all working trees.
+
+See linkgit:gitrepository-layout[5] for more information. The rule of
+thumb is do not make any assumption about whether a path belongs to
+$GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
+inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
+
+To prevent a $GIT_DIR/worktrees entry from from being pruned (which
+can be useful in some situations, such as when the
+entry's working tree is stored on a portable device), add a file named
+'locked' to the entry's directory. The file contains the reason in
+plain text. For example, if a linked working tree's `.git` file points
+to `/path/main/.git/worktrees/test-next` then a file named
+`/path/main/.git/worktrees/test-next/locked` will prevent the
+`test-next` entry from being pruned. See
+linkgit:gitrepository-layout[5] for details.
+
+EXAMPLES
--------
+You are in the middle of a refactoring session and your boss comes in and
+demands that you fix something immediately. You might typically use
+linkgit:git-stash[1] to store your changes away temporarily, however, your
+worktree is in such a state of disarray (with new, moved, and removed files,
+and other bits and pieces strewn around) that you don't want to risk
+disturbing any of it. Instead, you create a temporary linked worktree to
+make the emergency fix, remove it when done, and then resume your earlier
+refactoring session.
+
+------------
+$ git worktree add -b emergency-fix ../temp master
+$ pushd ../temp
+# ... hack hack hack ...
+$ git commit -a -m 'emergency fix for boss'
+$ popd
+$ rm -rf ../temp
+$ git worktree prune
+------------
+
+BUGS
+----
+Multiple checkout support for submodules is incomplete. It is NOT
+recommended to make multiple checkouts of a superproject.
+
+git-worktree could provide more automation for tasks currently
+performed manually, such as:
-linkgit:git-checkout[1]
+- `remove` to remove a linked worktree and its administrative files (and
+ warn if the worktree is dirty)
+- `mv` to move or rename a worktree and update its administrative files
+- `list` to list linked worktrees
+- `lock` to prevent automatic pruning of administrative files (for instance,
+ for a worktree on a portable device)
GIT
---
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e227f6499..f71844a23 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;
};
@@ -273,9 +269,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);
@@ -850,138 +843,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 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, *name;
- struct stat st;
- struct child_process cp;
- int counter = 0, len, ret;
-
- if (!new->commit)
- die(_("no branch specified"));
- 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. Any valid
- * value would do because this value will be ignored and
- * replaced at the next (real) checkout.
- */
- 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));
- strbuf_reset(&sb);
- 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);
-
- 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;
- 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")) {
@@ -1320,9 +1181,6 @@ static int checkout_branch(struct checkout_opts *opts,
free(head_ref);
}
- if (opts->new_worktree)
- return prepare_linked_checkout(opts, new);
-
if (!new->commit && opts->new_branch) {
unsigned char rev[20];
int flag;
@@ -1365,8 +1223,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(),
@@ -1377,9 +1233,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);
@@ -1388,13 +1241,7 @@ 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();
if (conflict_style) {
opts.merge = 1; /* implied */
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 2a729c661..6a264ee74 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -2,8 +2,13 @@
#include "builtin.h"
#include "dir.h"
#include "parse-options.h"
+#include "argv-array.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "refs.h"
static const char * const worktree_usage[] = {
+ N_("git worktree add [<options>] <path> <branch>"),
N_("git worktree prune [<options>]"),
NULL
};
@@ -119,6 +124,198 @@ 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 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;
+ 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);
+
+ name = worktree_basename(path, &len);
+ 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)
+{
+ 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 <branch> 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 < 1 || ac > 2)
+ 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");
+ 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);
+
+ return add_worktree(path, cmd.argv);
+}
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
@@ -127,6 +324,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);
diff --git a/git.c b/git.c
index fa77bc92b..fe94066ae 100644
--- a/git.c
+++ b/git.c
@@ -382,7 +382,7 @@ static struct cmd_struct commands[] = {
{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
{ "check-mailmap", cmd_check_mailmap, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
- { "checkout", cmd_checkout, RUN_SETUP },
+ { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "cherry", cmd_cherry, RUN_SETUP },
diff --git a/t/t2025-checkout-to.sh b/t/t2025-worktree-add.sh
index a8d93366f..ead8aa2a9 100755
--- a/t/t2025-checkout-to.sh
+++ b/t/t2025-worktree-add.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-test_description='test git checkout --to'
+test_description='test git worktree add'
. ./test-lib.sh
@@ -8,22 +8,18 @@ test_expect_success 'setup' '
test_commit init
'
-test_expect_success 'checkout --to not updating paths' '
- test_must_fail git checkout --to -- init.t
-'
-
-test_expect_success 'checkout --to an existing worktree' '
+test_expect_success '"add" an existing worktree' '
mkdir -p existing/subtree &&
- test_must_fail git checkout --detach --to existing master
+ test_must_fail git worktree add --detach existing master
'
-test_expect_success 'checkout --to an existing empty worktree' '
+test_expect_success '"add" an existing empty worktree' '
mkdir existing_empty &&
- git checkout --detach --to existing_empty master
+ git worktree add --detach existing_empty master
'
-test_expect_success 'checkout --to refuses to checkout locked branch' '
- test_must_fail git checkout --to zere master &&
+test_expect_success '"add" refuses to checkout locked branch' '
+ test_must_fail git worktree add zere master &&
! test -d zere &&
! test -d .git/worktrees/zere
'
@@ -36,9 +32,9 @@ test_expect_success 'checking out paths not complaining about linked checkouts'
)
'
-test_expect_success 'checkout --to a new worktree' '
+test_expect_success '"add" worktree' '
git rev-parse HEAD >expect &&
- git checkout --detach --to here master &&
+ git worktree add --detach here master &&
(
cd here &&
test_cmp ../init.t init.t &&
@@ -49,27 +45,27 @@ test_expect_success 'checkout --to a new worktree' '
)
'
-test_expect_success 'checkout --to a new worktree from a subdir' '
+test_expect_success '"add" worktree from a subdir' '
(
mkdir sub &&
cd sub &&
- git checkout --detach --to here master &&
+ git worktree add --detach here master &&
cd here &&
test_cmp ../../init.t init.t
)
'
-test_expect_success 'checkout --to from a linked checkout' '
+test_expect_success '"add" from a linked checkout' '
(
cd here &&
- git checkout --detach --to nested-here master &&
+ git worktree add --detach nested-here master &&
cd nested-here &&
git fsck
)
'
-test_expect_success 'checkout --to a new worktree creating new branch' '
- git checkout --to there -b newmaster master &&
+test_expect_success '"add" worktree creating new branch' '
+ git worktree add -b newmaster there master &&
(
cd there &&
test_cmp ../init.t init.t &&
@@ -90,7 +86,7 @@ test_expect_success 'die the same branch is already checked out' '
test_expect_success 'not die the same branch is already checked out' '
(
cd here &&
- git checkout --ignore-other-worktrees --to anothernewmaster newmaster
+ git worktree add --force anothernewmaster newmaster
)
'
@@ -101,15 +97,15 @@ test_expect_success 'not die on re-checking out current branch' '
)
'
-test_expect_success 'checkout --to from a bare repo' '
+test_expect_success '"add" from a bare repo' '
(
git clone --bare . bare &&
cd bare &&
- git checkout --to ../there2 -b bare-master master
+ git worktree add -b bare-master ../there2 master
)
'
-test_expect_success 'checkout from a bare repo without --to' '
+test_expect_success 'checkout from a bare repo without "add"' '
(
cd bare &&
test_must_fail git checkout master
@@ -129,9 +125,38 @@ test_expect_success 'checkout with grafts' '
EOF
git log --format=%s -2 >actual &&
test_cmp expected actual &&
- git checkout --detach --to grafted master &&
+ git worktree add --detach grafted master &&
git --git-dir=grafted/.git log --format=%s -2 >actual &&
test_cmp expected actual
'
+test_expect_success '"add" from relative HEAD' '
+ test_commit a &&
+ test_commit b &&
+ test_commit c &&
+ git rev-parse HEAD~1 >expected &&
+ git worktree add relhead HEAD~1 &&
+ git -C relhead rev-parse HEAD >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '"add -b" with <branch> omitted' '
+ git worktree add -b burble flornk &&
+ test_cmp_rev HEAD burble
+'
+
+test_expect_success '"add" with <branch> omitted' '
+ git worktree add wiffle/bat &&
+ test_cmp_rev HEAD bat
+'
+
+test_expect_success '"add" auto-vivify does not clobber existing branch' '
+ test_commit c1 &&
+ test_commit c2 &&
+ git branch precious HEAD~1 &&
+ test_must_fail git worktree add precious &&
+ test_cmp_rev HEAD~1 precious &&
+ test_path_is_missing precious
+'
+
test_done
diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-prune-linked-checkouts.sh
index e872f02da..a0f1e3bb8 100755
--- a/t/t2026-prune-linked-checkouts.sh
+++ b/t/t2026-prune-linked-checkouts.sh
@@ -88,7 +88,7 @@ test_expect_success 'not prune recent checkouts' '
test_expect_success 'not prune proper checkouts' '
test_when_finished rm -r .git/worktrees &&
- git checkout "--to=$PWD/nop" --detach master &&
+ git worktree add --detach "$PWD/nop" master &&
git worktree prune &&
test -d .git/worktrees/nop
'
diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh
index 8f30aed6c..3f609e890 100755
--- a/t/t7410-submodule-checkout-to.sh
+++ b/t/t7410-submodule-checkout-to.sh
@@ -33,7 +33,7 @@ rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1
test_expect_success 'checkout main' \
'mkdir default_checkout &&
(cd clone/main &&
- git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")'
+ git worktree add "$base_path/default_checkout/main" "$rev1_hash_main")'
test_expect_failure 'can see submodule diffs just after checkout' \
'(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")'
@@ -41,7 +41,7 @@ test_expect_failure 'can see submodule diffs just after checkout' \
test_expect_success 'checkout main and initialize independed clones' \
'mkdir fully_cloned_submodule &&
(cd clone/main &&
- git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
+ git worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") &&
(cd fully_cloned_submodule/main && git submodule update)'
test_expect_success 'can see submodule diffs after independed cloning' \