log: rename "tweak" helpers
[gitweb.git] / builtin / worktree.c
index 7bd6f1793e3e89701b9371acd3925a6c869ceaaf..430b51e7a74bb28ac1532ac836e9770121977096 100644 (file)
@@ -3,8 +3,11 @@
 #include "dir.h"
 #include "parse-options.h"
 #include "argv-array.h"
+#include "branch.h"
+#include "refs.h"
 #include "run-command.h"
 #include "sigchain.h"
+#include "refs.h"
 
 static const char * const worktree_usage[] = {
        N_("git worktree add [<options>] <path> <branch>"),
@@ -178,7 +181,7 @@ static const char *worktree_basename(const char *path, int *olen)
        return name;
 }
 
-static int add_worktree(const char *path, const char **child_argv,
+static int add_worktree(const char *path, const char *refname,
                        const struct add_opts *opts)
 {
        struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
@@ -188,11 +191,25 @@ static int add_worktree(const char *path, const char **child_argv,
        struct child_process cp;
        struct argv_array child_env = ARGV_ARRAY_INIT;
        int counter = 0, len, ret;
-       unsigned char rev[20];
+       struct strbuf symref = STRBUF_INIT;
+       struct commit *commit = NULL;
 
        if (file_exists(path) && !is_empty_dir(path))
                die(_("'%s' already exists"), path);
 
+       /* is 'refname' a branch or commit? */
+       if (opts->force_new_branch) /* definitely a branch */
+               ;
+       else if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
+                ref_exists(symref.buf)) { /* it's a branch */
+               if (!opts->force)
+                       die_if_checked_out(symref.buf);
+       } else { /* must be a commit */
+               commit = lookup_commit_reference_by_name(refname);
+               if (!commit)
+                       die(_("invalid reference: %s"), refname);
+       }
+
        name = worktree_basename(path, &len);
        strbuf_addstr(&sb_repo,
                      git_path("worktrees/%.*s", (int)(path + len - name), name));
@@ -236,32 +253,39 @@ static int add_worktree(const char *path, const char **child_argv,
                   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.
+        * or is_git_directory() will reject the directory. Any value which
+        * looks like an object ID will do since it will be immediately
+        * replaced by the symbolic-ref or update-ref invocation in the new
+        * worktree.
         */
-       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));
+       write_file(sb.buf, 1, "0000000000000000000000000000000000000000\n");
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
        write_file(sb.buf, 1, "../..\n");
 
        fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
 
-       setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1);
        argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
        argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
        memset(&cp, 0, sizeof(cp));
        cp.git_cmd = 1;
-       cp.argv = child_argv;
+
+       if (commit)
+               argv_array_pushl(&cp.args, "update-ref", "HEAD",
+                                sha1_to_hex(commit->object.sha1), NULL);
+       else
+               argv_array_pushl(&cp.args, "symbolic-ref", "HEAD",
+                                symref.buf, NULL);
+       cp.env = child_env.argv;
+       ret = run_command(&cp);
+       if (ret)
+               goto done;
+
+       cp.argv = NULL;
+       argv_array_clear(&cp.args);
+       argv_array_pushl(&cp.args, "reset", "--hard", NULL);
        cp.env = child_env.argv;
        ret = run_command(&cp);
        if (!ret) {
@@ -271,11 +295,13 @@ static int add_worktree(const char *path, const char **child_argv,
                junk_work_tree = NULL;
                junk_git_dir = NULL;
        }
+done:
        strbuf_reset(&sb);
        strbuf_addf(&sb, "%s/locked", sb_repo.buf);
        unlink_or_warn(sb.buf);
        argv_array_clear(&child_env);
        strbuf_release(&sb);
+       strbuf_release(&symref);
        strbuf_release(&sb_repo);
        strbuf_release(&sb_git);
        return ret;
@@ -286,7 +312,6 @@ static int add(int ac, const char **av, const char *prefix)
        struct add_opts opts;
        const char *new_branch_force = NULL;
        const char *path, *branch;
-       struct argv_array cmd = ARGV_ARRAY_INIT;
        struct option options[] = {
                OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
                OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
@@ -331,14 +356,7 @@ static int add(int ac, const char **av, const char *prefix)
                branch = opts.new_branch;
        }
 
-       argv_array_push(&cmd, "checkout");
-       if (opts.force)
-               argv_array_push(&cmd, "--ignore-other-worktrees");
-       if (opts.detach)
-               argv_array_push(&cmd, "--detach");
-       argv_array_push(&cmd, branch);
-
-       return add_worktree(path, cmd.argv, &opts);
+       return add_worktree(path, branch, &opts);
 }
 
 int cmd_worktree(int ac, const char **av, const char *prefix)