summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/checkout.c174
-rw-r--r--builtin/clone.c19
-rw-r--r--builtin/gc.c2
-rw-r--r--builtin/index-pack.c16
-rw-r--r--builtin/prune.c99
-rw-r--r--builtin/receive-pack.c2
-rw-r--r--builtin/rev-list.c3
-rw-r--r--builtin/rev-parse.c57
-rw-r--r--builtin/send-pack.c3
-rw-r--r--builtin/worktree.c332
10 files changed, 393 insertions, 314 deletions
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 9b49f0e413..f71844a23a 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")) {
@@ -1110,7 +971,6 @@ static int parse_branchname_arg(int argc, const char **argv,
{
struct tree **source_tree = &opts->source_tree;
const char **new_branch = &opts->new_branch;
- int force_detach = opts->force_detach;
int argcount = 0;
unsigned char branch_rev[20];
const char *arg;
@@ -1231,17 +1091,6 @@ static int parse_branchname_arg(int argc, const char **argv,
else
new->path = NULL; /* not an existing branch */
- if (new->path && !force_detach && !*new_branch) {
- unsigned char sha1[20];
- int flag;
- char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
- if (head_ref &&
- (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
- !opts->ignore_other_worktrees)
- check_linked_checkouts(new);
- free(head_ref);
- }
-
new->commit = lookup_commit_reference_gently(rev, 1);
if (!new->commit) {
/* not a commit */
@@ -1321,8 +1170,16 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("Cannot switch branch to a non-commit '%s'"),
new->name);
- if (opts->new_worktree)
- return prepare_linked_checkout(opts, new);
+ if (new->path && !opts->force_detach && !opts->new_branch) {
+ unsigned char sha1[20];
+ int flag;
+ char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
+ if (head_ref &&
+ (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) &&
+ !opts->ignore_other_worktrees)
+ check_linked_checkouts(new);
+ free(head_ref);
+ }
if (!new->commit && opts->new_branch) {
unsigned char rev[20];
@@ -1366,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(),
@@ -1378,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);
@@ -1389,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/clone.c b/builtin/clone.c
index 00535d0178..a72ff7e009 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -147,6 +147,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
{
const char *end = repo + strlen(repo), *start;
+ size_t len;
char *dir;
/*
@@ -173,20 +174,12 @@ static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
/*
* Strip .{bundle,git}.
*/
- if (is_bundle) {
- if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
- end -= 7;
- } else {
- if (end - start > 4 && !strncmp(end - 4, ".git", 4))
- end -= 4;
- }
+ strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len);
- if (is_bare) {
- struct strbuf result = STRBUF_INIT;
- strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
- dir = strbuf_detach(&result, NULL);
- } else
- dir = xstrndup(start, end - start);
+ if (is_bare)
+ dir = xstrfmt("%.*s.git", (int)len, start);
+ else
+ dir = xstrndup(start, len);
/*
* Replace sequences of 'control' characters and whitespace
* with one ascii space, remove leading and trailing spaces.
diff --git a/builtin/gc.c b/builtin/gc.c
index 36fe33300f..4957c39032 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -293,7 +293,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
argv_array_pushl(&prune, "prune", "--expire", NULL);
- argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
+ argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
argv_array_pushl(&rerere, "rerere", "gc", NULL);
gc_config();
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 48fa4724aa..f07bc66ed6 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1227,7 +1227,7 @@ static void resolve_deltas(void)
* - append objects to convert thin pack to full pack if required
* - write the final 20-byte SHA-1
*/
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
+static void fix_unresolved_deltas(struct sha1file *f);
static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
{
if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) {
@@ -1249,7 +1249,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
memset(objects + nr_objects + 1, 0,
nr_unresolved * sizeof(*objects));
f = sha1fd(output_fd, curr_pack);
- fix_unresolved_deltas(f, nr_unresolved);
+ fix_unresolved_deltas(f);
strbuf_addf(&msg, _("completed with %d local objects"),
nr_objects - nr_objects_initial);
stop_progress_msg(&progress, msg.buf);
@@ -1331,10 +1331,10 @@ static int delta_pos_compare(const void *_a, const void *_b)
return a->obj_no - b->obj_no;
}
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
+static void fix_unresolved_deltas(struct sha1file *f)
{
struct ref_delta_entry **sorted_by_pos;
- int i, n = 0;
+ int i;
/*
* Since many unresolved deltas may well be themselves base objects
@@ -1346,12 +1346,12 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
* before deltas depending on them, a good heuristic is to start
* resolving deltas in the same order as their position in the pack.
*/
- sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
+ sorted_by_pos = xmalloc(nr_ref_deltas * sizeof(*sorted_by_pos));
for (i = 0; i < nr_ref_deltas; i++)
- sorted_by_pos[n++] = &ref_deltas[i];
- qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+ sorted_by_pos[i] = &ref_deltas[i];
+ qsort(sorted_by_pos, nr_ref_deltas, sizeof(*sorted_by_pos), delta_pos_compare);
- for (i = 0; i < n; i++) {
+ for (i = 0; i < nr_ref_deltas; i++) {
struct ref_delta_entry *d = sorted_by_pos[i];
enum object_type type;
struct base_data *base_obj = alloc_base_data();
diff --git a/builtin/prune.c b/builtin/prune.c
index 0c73246c72..10b03d3e4c 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -6,7 +6,6 @@
#include "reachable.h"
#include "parse-options.h"
#include "progress.h"
-#include "dir.h"
static const char * const prune_usage[] = {
N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
@@ -76,95 +75,6 @@ static int prune_subdir(int nr, const char *path, void *data)
return 0;
}
-static int prune_worktree(const char *id, struct strbuf *reason)
-{
- struct stat st;
- char *path;
- int fd, len;
-
- if (!is_directory(git_path("worktrees/%s", id))) {
- strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
- return 1;
- }
- if (file_exists(git_path("worktrees/%s/locked", id)))
- return 0;
- if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
- strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
- return 1;
- }
- fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
- if (fd < 0) {
- strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
- id, strerror(errno));
- return 1;
- }
- len = st.st_size;
- path = xmalloc(len + 1);
- read_in_full(fd, path, len);
- close(fd);
- while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
- len--;
- if (!len) {
- strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
- free(path);
- return 1;
- }
- path[len] = '\0';
- if (!file_exists(path)) {
- struct stat st_link;
- free(path);
- /*
- * the repo is moved manually and has not been
- * accessed since?
- */
- if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
- st_link.st_nlink > 1)
- return 0;
- if (st.st_mtime <= expire) {
- strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
- return 1;
- } else {
- return 0;
- }
- }
- free(path);
- return 0;
-}
-
-static void prune_worktrees(void)
-{
- struct strbuf reason = STRBUF_INIT;
- struct strbuf path = STRBUF_INIT;
- DIR *dir = opendir(git_path("worktrees"));
- struct dirent *d;
- int ret;
- if (!dir)
- return;
- while ((d = readdir(dir)) != NULL) {
- if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
- continue;
- strbuf_reset(&reason);
- if (!prune_worktree(d->d_name, &reason))
- continue;
- if (show_only || verbose)
- printf("%s\n", reason.buf);
- if (show_only)
- continue;
- strbuf_reset(&path);
- strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
- ret = remove_dir_recursively(&path, 0);
- if (ret < 0 && errno == ENOTDIR)
- ret = unlink(path.buf);
- if (ret)
- error(_("failed to remove: %s"), strerror(errno));
- }
- closedir(dir);
- if (!show_only)
- rmdir(git_path("worktrees"));
- strbuf_release(&reason);
- strbuf_release(&path);
-}
-
/*
* Write errors (particularly out of space) can result in
* failed temporary packs (and more rarely indexes and other
@@ -191,12 +101,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct progress *progress = NULL;
- int do_prune_worktrees = 0;
const struct option options[] = {
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
- OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("expire objects older than <time>")),
OPT_END()
@@ -211,13 +119,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
- if (do_prune_worktrees) {
- if (argc)
- die(_("--worktrees does not take extra arguments"));
- prune_worktrees();
- return 0;
- }
-
while (argc--) {
unsigned char sha1[20];
const char *name = *argv++;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 94d0571776..04cb5a1a06 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -911,7 +911,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
return "deletion prohibited";
}
- if (!strcmp(namespaced_name, head_name)) {
+ if (head_name && !strcmp(namespaced_name, head_name)) {
switch (deny_delete_current) {
case DENY_IGNORE:
break;
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index ff84a825ff..c0b4b53652 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -42,6 +42,7 @@ static const char rev_list_usage[] =
" --abbrev=<n> | --no-abbrev\n"
" --abbrev-commit\n"
" --left-right\n"
+" --count\n"
" special purpose:\n"
" --bisect\n"
" --bisect-vars\n"
@@ -355,7 +356,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (bisect_list)
revs.limited = 1;
- if (use_bitmap_index) {
+ if (use_bitmap_index && !revs.prune) {
if (revs.count && !revs.left_right && !revs.cherry_mark) {
uint32_t commit_count;
if (!prepare_bitmap_walk(&revs)) {
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index b6232390a6..02d747dcb1 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -371,6 +371,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
N_("output in stuck long form")),
OPT_END(),
};
+ static const char * const flag_chars = "*=?!";
struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
const char **usage = NULL;
@@ -400,7 +401,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
/* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
while (strbuf_getline(&sb, stdin, '\n') != EOF) {
const char *s;
- const char *end;
+ const char *help;
struct option *o;
if (!sb.len)
@@ -410,54 +411,56 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
memset(opts + onb, 0, sizeof(opts[onb]));
o = &opts[onb++];
- s = strchr(sb.buf, ' ');
- if (!s || *sb.buf == ' ') {
+ help = strchr(sb.buf, ' ');
+ if (!help || *sb.buf == ' ') {
o->type = OPTION_GROUP;
o->help = xstrdup(skipspaces(sb.buf));
continue;
}
o->type = OPTION_CALLBACK;
- o->help = xstrdup(skipspaces(s));
+ o->help = xstrdup(skipspaces(help));
o->value = &parsed;
o->flags = PARSE_OPT_NOARG;
o->callback = &parseopt_dump;
- /* Possible argument name hint */
- end = s;
- while (s > sb.buf && strchr("*=?!", s[-1]) == NULL)
- --s;
- if (s != sb.buf && s != end)
- o->argh = xmemdupz(s, end - s);
- if (s == sb.buf)
- s = end;
-
- while (s > sb.buf && strchr("*=?!", s[-1])) {
- switch (*--s) {
+ /* name(s) */
+ s = strpbrk(sb.buf, flag_chars);
+ if (s == NULL)
+ s = help;
+
+ if (s - sb.buf == 1) /* short option only */
+ o->short_name = *sb.buf;
+ else if (sb.buf[1] != ',') /* long option only */
+ o->long_name = xmemdupz(sb.buf, s - sb.buf);
+ else {
+ o->short_name = *sb.buf;
+ o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+ }
+
+ /* flags */
+ while (s < help) {
+ switch (*s++) {
case '=':
o->flags &= ~PARSE_OPT_NOARG;
- break;
+ continue;
case '?':
o->flags &= ~PARSE_OPT_NOARG;
o->flags |= PARSE_OPT_OPTARG;
- break;
+ continue;
case '!':
o->flags |= PARSE_OPT_NONEG;
- break;
+ continue;
case '*':
o->flags |= PARSE_OPT_HIDDEN;
- break;
+ continue;
}
+ s--;
+ break;
}
- if (s - sb.buf == 1) /* short option only */
- o->short_name = *sb.buf;
- else if (sb.buf[1] != ',') /* long option only */
- o->long_name = xmemdupz(sb.buf, s - sb.buf);
- else {
- o->short_name = *sb.buf;
- o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
- }
+ if (s < help)
+ o->argh = xmemdupz(s, help - s);
}
strbuf_release(&sb);
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index b961e5ae78..23b2962cb0 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -11,6 +11,7 @@
#include "transport.h"
#include "version.h"
#include "sha1-array.h"
+#include "gpg-interface.h"
static const char send_pack_usage[] =
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n"
@@ -113,6 +114,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
int from_stdin = 0;
struct push_cas_option cas = {0};
+ git_config(git_gpg_config, NULL);
+
argv++;
for (i = 1; i < argc; i++, argv++) {
const char *arg = *argv;
diff --git a/builtin/worktree.c b/builtin/worktree.c
new file mode 100644
index 0000000000..6a264ee749
--- /dev/null
+++ b/builtin/worktree.c
@@ -0,0 +1,332 @@
+#include "cache.h"
+#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
+};
+
+static int show_only;
+static int verbose;
+static unsigned long expire;
+
+static int prune_worktree(const char *id, struct strbuf *reason)
+{
+ struct stat st;
+ char *path;
+ int fd, len;
+
+ if (!is_directory(git_path("worktrees/%s", id))) {
+ strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+ return 1;
+ }
+ if (file_exists(git_path("worktrees/%s/locked", id)))
+ return 0;
+ if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
+ return 1;
+ }
+ fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
+ if (fd < 0) {
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
+ id, strerror(errno));
+ return 1;
+ }
+ len = st.st_size;
+ path = xmalloc(len + 1);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
+ len--;
+ if (!len) {
+ strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+ free(path);
+ return 1;
+ }
+ path[len] = '\0';
+ if (!file_exists(path)) {
+ struct stat st_link;
+ free(path);
+ /*
+ * the repo is moved manually and has not been
+ * accessed since?
+ */
+ if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
+ st_link.st_nlink > 1)
+ return 0;
+ if (st.st_mtime <= expire) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ free(path);
+ return 0;
+}
+
+static void prune_worktrees(void)
+{
+ struct strbuf reason = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir = opendir(git_path("worktrees"));
+ struct dirent *d;
+ int ret;
+ if (!dir)
+ return;
+ while ((d = readdir(dir)) != NULL) {
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+ strbuf_reset(&reason);
+ if (!prune_worktree(d->d_name, &reason))
+ continue;
+ if (show_only || verbose)
+ printf("%s\n", reason.buf);
+ if (show_only)
+ continue;
+ strbuf_reset(&path);
+ strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
+ ret = remove_dir_recursively(&path, 0);
+ if (ret < 0 && errno == ENOTDIR)
+ ret = unlink(path.buf);
+ if (ret)
+ error(_("failed to remove: %s"), strerror(errno));
+ }
+ closedir(dir);
+ if (!show_only)
+ rmdir(git_path("worktrees"));
+ strbuf_release(&reason);
+ strbuf_release(&path);
+}
+
+static int prune(int ac, const char **av, const char *prefix)
+{
+ struct option options[] = {
+ OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
+ OPT__VERBOSE(&verbose, N_("report pruned objects")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire objects older than <time>")),
+ OPT_END()
+ };
+
+ expire = ULONG_MAX;
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac)
+ usage_with_options(worktree_usage, options);
+ prune_worktrees();
+ 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[] = {
+ OPT_END()
+ };
+
+ 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);
+}