diff options
Diffstat (limited to 'builtin')
-rw-r--r-- | builtin/add.c | 1 | ||||
-rw-r--r-- | builtin/apply.c | 3 | ||||
-rw-r--r-- | builtin/blame.c | 10 | ||||
-rw-r--r-- | builtin/checkout.c | 174 | ||||
-rw-r--r-- | builtin/clone.c | 36 | ||||
-rw-r--r-- | builtin/for-each-ref.c | 1108 | ||||
-rw-r--r-- | builtin/fsck.c | 34 | ||||
-rw-r--r-- | builtin/gc.c | 2 | ||||
-rw-r--r-- | builtin/index-pack.c | 26 | ||||
-rw-r--r-- | builtin/log.c | 12 | ||||
-rw-r--r-- | builtin/pack-objects.c | 25 | ||||
-rw-r--r-- | builtin/prune.c | 99 | ||||
-rw-r--r-- | builtin/replace.c | 6 | ||||
-rw-r--r-- | builtin/rev-list.c | 3 | ||||
-rw-r--r-- | builtin/verify-commit.c | 25 | ||||
-rw-r--r-- | builtin/verify-tag.c | 30 | ||||
-rw-r--r-- | builtin/worktree.c | 332 |
17 files changed, 469 insertions, 1457 deletions
diff --git a/builtin/add.c b/builtin/add.c index df5135bf62..4bd98b799e 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -63,7 +63,6 @@ static void update_callback(struct diff_queue_struct *q, switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); - case DIFF_STATUS_ADDED: case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: if (add_file_to_index(&the_index, path, data->flags)) { diff --git a/builtin/apply.c b/builtin/apply.c index 146be97a1a..54aba4e351 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1638,6 +1638,9 @@ static int parse_fragment(const char *line, unsigned long size, } if (oldlines || newlines) return -1; + if (!deleted && !added) + return -1; + fragment->leading = leading; fragment->trailing = trailing; diff --git a/builtin/blame.c b/builtin/blame.c index b3e948e757..a22ac17407 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2176,6 +2176,14 @@ static int git_blame_config(const char *var, const char *value, void *cb) blank_boundary = git_config_bool(var, value); return 0; } + if (!strcmp(var, "blame.showemail")) { + int *output_option = cb; + if (git_config_bool(var, value)) + *output_option |= OUTPUT_SHOW_EMAIL; + else + *output_option &= ~OUTPUT_SHOW_EMAIL; + return 0; + } if (!strcmp(var, "blame.date")) { if (!value) return config_error_nonbool(var); @@ -2520,7 +2528,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) unsigned int range_i; long anchor; - git_config(git_blame_config, NULL); + git_config(git_blame_config, &output_option); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); 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 b878252bc2..a72ff7e009 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -51,15 +51,6 @@ static struct string_list option_config; static struct string_list option_reference; static int option_dissociate; -static int opt_parse_reference(const struct option *opt, const char *arg, int unset) -{ - struct string_list *option_reference = opt->value; - if (!arg) - return -1; - string_list_append(option_reference, arg); - return 0; -} - static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), OPT_BOOL(0, "progress", &option_progress, @@ -83,8 +74,10 @@ static struct option builtin_clone_options[] = { N_("initialize submodules in the clone")), OPT_STRING(0, "template", &option_template, N_("template-directory"), N_("directory from which templates will be used")), - OPT_CALLBACK(0 , "reference", &option_reference, N_("repo"), - N_("reference repository"), &opt_parse_reference), + OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), + N_("reference repository")), + OPT_BOOL(0, "dissociate", &option_dissociate, + N_("use --reference only while cloning")), OPT_STRING('o', "origin", &option_origin, N_("name"), N_("use <name> instead of 'origin' to track upstream")), OPT_STRING('b', "branch", &option_branch, N_("branch"), @@ -95,8 +88,6 @@ static struct option builtin_clone_options[] = { N_("create a shallow clone of that depth")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), - OPT_BOOL(0, "dissociate", &option_dissociate, - N_("use --reference only while cloning")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), @@ -156,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; /* @@ -182,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/for-each-ref.c b/builtin/for-each-ref.c index f7e51a7fad..7919206187 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -2,1077 +2,8 @@ #include "cache.h" #include "refs.h" #include "object.h" -#include "tag.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "quote.h" #include "parse-options.h" -#include "remote.h" -#include "color.h" - -/* Quoting styles */ -#define QUOTE_NONE 0 -#define QUOTE_SHELL 1 -#define QUOTE_PERL 2 -#define QUOTE_PYTHON 4 -#define QUOTE_TCL 8 - -typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; - -struct atom_value { - const char *s; - unsigned long ul; /* used for sorting when not FIELD_STR */ -}; - -struct ref_sort { - struct ref_sort *next; - int atom; /* index into used_atom array */ - unsigned reverse : 1; -}; - -struct refinfo { - char *refname; - unsigned char objectname[20]; - int flag; - const char *symref; - struct atom_value *value; -}; - -static struct { - const char *name; - cmp_type cmp_type; -} valid_atom[] = { - { "refname" }, - { "objecttype" }, - { "objectsize", FIELD_ULONG }, - { "objectname" }, - { "tree" }, - { "parent" }, - { "numparent", FIELD_ULONG }, - { "object" }, - { "type" }, - { "tag" }, - { "author" }, - { "authorname" }, - { "authoremail" }, - { "authordate", FIELD_TIME }, - { "committer" }, - { "committername" }, - { "committeremail" }, - { "committerdate", FIELD_TIME }, - { "tagger" }, - { "taggername" }, - { "taggeremail" }, - { "taggerdate", FIELD_TIME }, - { "creator" }, - { "creatordate", FIELD_TIME }, - { "subject" }, - { "body" }, - { "contents" }, - { "contents:subject" }, - { "contents:body" }, - { "contents:signature" }, - { "upstream" }, - { "push" }, - { "symref" }, - { "flag" }, - { "HEAD" }, - { "color" }, -}; - -/* - * An atom is a valid field atom listed above, possibly prefixed with - * a "*" to denote deref_tag(). - * - * We parse given format string and sort specifiers, and make a list - * of properties that we need to extract out of objects. refinfo - * structure will hold an array of values extracted that can be - * indexed with the "atom number", which is an index into this - * array. - */ -static const char **used_atom; -static cmp_type *used_atom_type; -static int used_atom_cnt, need_tagged, need_symref; -static int need_color_reset_at_eol; - -/* - * Used to parse format string and sort specifiers - */ -static int parse_atom(const char *atom, const char *ep) -{ - const char *sp; - int i, at; - - sp = atom; - if (*sp == '*' && sp < ep) - sp++; /* deref */ - if (ep <= sp) - die("malformed field name: %.*s", (int)(ep-atom), atom); - - /* Do we have the atom already used elsewhere? */ - for (i = 0; i < used_atom_cnt; i++) { - int len = strlen(used_atom[i]); - if (len == ep - atom && !memcmp(used_atom[i], atom, len)) - return i; - } - - /* Is the atom a valid one? */ - for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { - int len = strlen(valid_atom[i].name); - /* - * If the atom name has a colon, strip it and everything after - * it off - it specifies the format for this entry, and - * shouldn't be used for checking against the valid_atom - * table. - */ - const char *formatp = strchr(sp, ':'); - if (!formatp || ep < formatp) - formatp = ep; - if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) - break; - } - - if (ARRAY_SIZE(valid_atom) <= i) - die("unknown field name: %.*s", (int)(ep-atom), atom); - - /* Add it in, including the deref prefix */ - at = used_atom_cnt; - used_atom_cnt++; - REALLOC_ARRAY(used_atom, used_atom_cnt); - REALLOC_ARRAY(used_atom_type, used_atom_cnt); - used_atom[at] = xmemdupz(atom, ep - atom); - used_atom_type[at] = valid_atom[i].cmp_type; - if (*atom == '*') - need_tagged = 1; - if (!strcmp(used_atom[at], "symref")) - need_symref = 1; - return at; -} - -/* - * In a format string, find the next occurrence of %(atom). - */ -static const char *find_next(const char *cp) -{ - while (*cp) { - if (*cp == '%') { - /* - * %( is the start of an atom; - * %% is a quoted per-cent. - */ - if (cp[1] == '(') - return cp; - else if (cp[1] == '%') - cp++; /* skip over two % */ - /* otherwise this is a singleton, literal % */ - } - cp++; - } - return NULL; -} - -/* - * Make sure the format string is well formed, and parse out - * the used atoms. - */ -static int verify_format(const char *format) -{ - const char *cp, *sp; - - need_color_reset_at_eol = 0; - for (cp = format; *cp && (sp = find_next(cp)); ) { - const char *color, *ep = strchr(sp, ')'); - int at; - - if (!ep) - return error("malformed format string %s", sp); - /* sp points at "%(" and ep points at the closing ")" */ - at = parse_atom(sp + 2, ep); - cp = ep + 1; - - if (skip_prefix(used_atom[at], "color:", &color)) - need_color_reset_at_eol = !!strcmp(color, "reset"); - } - return 0; -} - -/* - * Given an object name, read the object data and size, and return a - * "struct object". If the object data we are returning is also borrowed - * by the "struct object" representation, set *eaten as well---it is a - * signal from parse_object_buffer to us not to free the buffer. - */ -static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) -{ - enum object_type type; - void *buf = read_sha1_file(sha1, &type, sz); - - if (buf) - *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); - else - *obj = NULL; - return buf; -} - -static int grab_objectname(const char *name, const unsigned char *sha1, - struct atom_value *v) -{ - if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(sha1)); - v->s = s; - return 1; - } - if (!strcmp(name, "objectname:short")) { - v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); - return 1; - } - return 0; -} - -/* See grab_values */ -static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "objecttype")) - v->s = typename(obj->type); - else if (!strcmp(name, "objectsize")) { - char *s = xmalloc(40); - sprintf(s, "%lu", sz); - v->ul = sz; - v->s = s; - } - else if (deref) - grab_objectname(name, obj->sha1, v); - } -} - -/* See grab_values */ -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct tag *tag = (struct tag *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tag")) - v->s = tag->tag; - else if (!strcmp(name, "type") && tag->tagged) - v->s = typename(tag->tagged->type); - else if (!strcmp(name, "object") && tag->tagged) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(tag->tagged->sha1)); - v->s = s; - } - } -} - -/* See grab_values */ -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct commit *commit = (struct commit *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tree")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(commit->tree->object.sha1)); - v->s = s; - } - if (!strcmp(name, "numparent")) { - char *s = xmalloc(40); - v->ul = commit_list_count(commit->parents); - sprintf(s, "%lu", v->ul); - v->s = s; - } - else if (!strcmp(name, "parent")) { - int num = commit_list_count(commit->parents); - int i; - struct commit_list *parents; - char *s = xmalloc(41 * num + 1); - v->s = s; - for (i = 0, parents = commit->parents; - parents; - parents = parents->next, i = i + 41) { - struct commit *parent = parents->item; - strcpy(s+i, sha1_to_hex(parent->object.sha1)); - if (parents->next) - s[i+40] = ' '; - } - if (!i) - *s = '\0'; - } - } -} - -static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) -{ - const char *eol; - while (*buf) { - if (!strncmp(buf, who, wholen) && - buf[wholen] == ' ') - return buf + wholen + 1; - eol = strchr(buf, '\n'); - if (!eol) - return ""; - eol++; - if (*eol == '\n') - return ""; /* end of header */ - buf = eol; - } - return ""; -} - -static const char *copy_line(const char *buf) -{ - const char *eol = strchrnul(buf, '\n'); - return xmemdupz(buf, eol - buf); -} - -static const char *copy_name(const char *buf) -{ - const char *cp; - for (cp = buf; *cp && *cp != '\n'; cp++) { - if (!strncmp(cp, " <", 2)) - return xmemdupz(buf, cp - buf); - } - return ""; -} - -static const char *copy_email(const char *buf) -{ - const char *email = strchr(buf, '<'); - const char *eoemail; - if (!email) - return ""; - eoemail = strchr(email, '>'); - if (!eoemail) - return ""; - return xmemdupz(email, eoemail + 1 - email); -} - -static char *copy_subject(const char *buf, unsigned long len) -{ - char *r = xmemdupz(buf, len); - int i; - - for (i = 0; i < len; i++) - if (r[i] == '\n') - r[i] = ' '; - - return r; -} - -static void grab_date(const char *buf, struct atom_value *v, const char *atomname) -{ - const char *eoemail = strstr(buf, "> "); - char *zone; - unsigned long timestamp; - long tz; - enum date_mode date_mode = DATE_NORMAL; - const char *formatp; - - /* - * We got here because atomname ends in "date" or "date<something>"; - * it's not possible that <something> is not ":<format>" because - * parse_atom() wouldn't have allowed it, so we can assume that no - * ":" means no format is specified, and use the default. - */ - formatp = strchr(atomname, ':'); - if (formatp != NULL) { - formatp++; - date_mode = parse_date_format(formatp); - } - - if (!eoemail) - goto bad; - timestamp = strtoul(eoemail + 2, &zone, 10); - if (timestamp == ULONG_MAX) - goto bad; - tz = strtol(zone, NULL, 10); - if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) - goto bad; - v->s = xstrdup(show_date(timestamp, tz, date_mode)); - v->ul = timestamp; - return; - bad: - v->s = ""; - v->ul = 0; -} - -/* See grab_values */ -static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - int wholen = strlen(who); - const char *wholine = NULL; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strncmp(who, name, wholen)) - continue; - if (name[wholen] != 0 && - strcmp(name + wholen, "name") && - strcmp(name + wholen, "email") && - !starts_with(name + wholen, "date")) - continue; - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; /* no point looking for it */ - if (name[wholen] == 0) - v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) - v->s = copy_name(wholine); - else if (!strcmp(name + wholen, "email")) - v->s = copy_email(wholine); - else if (starts_with(name + wholen, "date")) - grab_date(wholine, v, name); - } - - /* - * For a tag or a commit object, if "creator" or "creatordate" is - * requested, do something special. - */ - if (strcmp(who, "tagger") && strcmp(who, "committer")) - return; /* "author" for commit object is not wanted */ - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - - if (starts_with(name, "creatordate")) - grab_date(wholine, v, name); - else if (!strcmp(name, "creator")) - v->s = copy_line(wholine); - } -} - -static void find_subpos(const char *buf, unsigned long sz, - const char **sub, unsigned long *sublen, - const char **body, unsigned long *bodylen, - unsigned long *nonsiglen, - const char **sig, unsigned long *siglen) -{ - const char *eol; - /* skip past header until we hit empty line */ - while (*buf && *buf != '\n') { - eol = strchrnul(buf, '\n'); - if (*eol) - eol++; - buf = eol; - } - /* skip any empty lines */ - while (*buf == '\n') - buf++; - - /* parse signature first; we might not even have a subject line */ - *sig = buf + parse_signature(buf, strlen(buf)); - *siglen = strlen(*sig); - - /* subject is first non-empty line */ - *sub = buf; - /* subject goes to first empty line */ - while (buf < *sig && *buf && *buf != '\n') { - eol = strchrnul(buf, '\n'); - if (*eol) - eol++; - buf = eol; - } - *sublen = buf - *sub; - /* drop trailing newline, if present */ - if (*sublen && (*sub)[*sublen - 1] == '\n') - *sublen -= 1; - - /* skip any empty lines */ - while (*buf == '\n') - buf++; - *body = buf; - *bodylen = strlen(buf); - *nonsiglen = *sig - buf; -} - -/* See grab_values */ -static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; - unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strcmp(name, "subject") && - strcmp(name, "body") && - strcmp(name, "contents") && - strcmp(name, "contents:subject") && - strcmp(name, "contents:body") && - strcmp(name, "contents:signature")) - continue; - if (!subpos) - find_subpos(buf, sz, - &subpos, &sublen, - &bodypos, &bodylen, &nonsiglen, - &sigpos, &siglen); - - if (!strcmp(name, "subject")) - v->s = copy_subject(subpos, sublen); - else if (!strcmp(name, "contents:subject")) - v->s = copy_subject(subpos, sublen); - else if (!strcmp(name, "body")) - v->s = xmemdupz(bodypos, bodylen); - else if (!strcmp(name, "contents:body")) - v->s = xmemdupz(bodypos, nonsiglen); - else if (!strcmp(name, "contents:signature")) - v->s = xmemdupz(sigpos, siglen); - else if (!strcmp(name, "contents")) - v->s = xstrdup(subpos); - } -} - -/* - * We want to have empty print-string for field requests - * that do not apply (e.g. "authordate" for a tag object) - */ -static void fill_missing_values(struct atom_value *val) -{ - int i; - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &val[i]; - if (v->s == NULL) - v->s = ""; - } -} - -/* - * val is a list of atom_value to hold returned values. Extract - * the values for atoms in used_atom array out of (obj, buf, sz). - * when deref is false, (obj, buf, sz) is the object that is - * pointed at by the ref itself; otherwise it is the object the - * ref (which is a tag) refers to. - */ -static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - grab_common_values(val, deref, obj, buf, sz); - switch (obj->type) { - case OBJ_TAG: - grab_tag_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("tagger", val, deref, obj, buf, sz); - break; - case OBJ_COMMIT: - grab_commit_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("author", val, deref, obj, buf, sz); - grab_person("committer", val, deref, obj, buf, sz); - break; - case OBJ_TREE: - /* grab_tree_values(val, deref, obj, buf, sz); */ - break; - case OBJ_BLOB: - /* grab_blob_values(val, deref, obj, buf, sz); */ - break; - default: - die("Eh? Object of type %d?", obj->type); - } -} - -static inline char *copy_advance(char *dst, const char *src) -{ - while (*src) - *dst++ = *src++; - return dst; -} - -/* - * Parse the object referred by ref, and grab needed value. - */ -static void populate_value(struct refinfo *ref) -{ - void *buf; - struct object *obj; - int eaten, i; - unsigned long size; - const unsigned char *tagged; - - ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); - - if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { - unsigned char unused1[20]; - ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, - unused1, NULL); - if (!ref->symref) - ref->symref = ""; - } - - /* Fill in specials first */ - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &ref->value[i]; - int deref = 0; - const char *refname; - const char *formatp; - struct branch *branch = NULL; - - if (*name == '*') { - deref = 1; - name++; - } - - if (starts_with(name, "refname")) - refname = ref->refname; - else if (starts_with(name, "symref")) - refname = ref->symref ? ref->symref : ""; - else if (starts_with(name, "upstream")) { - const char *branch_name; - /* only local branches may have an upstream */ - if (!skip_prefix(ref->refname, "refs/heads/", - &branch_name)) - continue; - branch = branch_get(branch_name); - - refname = branch_get_upstream(branch, NULL); - if (!refname) - continue; - } else if (starts_with(name, "push")) { - const char *branch_name; - if (!skip_prefix(ref->refname, "refs/heads/", - &branch_name)) - continue; - branch = branch_get(branch_name); - - refname = branch_get_push(branch, NULL); - if (!refname) - continue; - } else if (starts_with(name, "color:")) { - char color[COLOR_MAXLEN] = ""; - - if (color_parse(name + 6, color) < 0) - die(_("unable to parse format")); - v->s = xstrdup(color); - continue; - } else if (!strcmp(name, "flag")) { - char buf[256], *cp = buf; - if (ref->flag & REF_ISSYMREF) - cp = copy_advance(cp, ",symref"); - if (ref->flag & REF_ISPACKED) - cp = copy_advance(cp, ",packed"); - if (cp == buf) - v->s = ""; - else { - *cp = '\0'; - v->s = xstrdup(buf + 1); - } - continue; - } else if (!deref && grab_objectname(name, ref->objectname, v)) { - continue; - } else if (!strcmp(name, "HEAD")) { - const char *head; - unsigned char sha1[20]; - - head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - sha1, NULL); - if (!strcmp(ref->refname, head)) - v->s = "*"; - else - v->s = " "; - continue; - } else - continue; - - formatp = strchr(name, ':'); - if (formatp) { - int num_ours, num_theirs; - - formatp++; - if (!strcmp(formatp, "short")) - refname = shorten_unambiguous_ref(refname, - warn_ambiguous_refs); - else if (!strcmp(formatp, "track") && - (starts_with(name, "upstream") || - starts_with(name, "push"))) { - char buf[40]; - - if (stat_tracking_info(branch, &num_ours, - &num_theirs, NULL)) - continue; - - if (!num_ours && !num_theirs) - v->s = ""; - else if (!num_ours) { - sprintf(buf, "[behind %d]", num_theirs); - v->s = xstrdup(buf); - } else if (!num_theirs) { - sprintf(buf, "[ahead %d]", num_ours); - v->s = xstrdup(buf); - } else { - sprintf(buf, "[ahead %d, behind %d]", - num_ours, num_theirs); - v->s = xstrdup(buf); - } - continue; - } else if (!strcmp(formatp, "trackshort") && - (starts_with(name, "upstream") || - starts_with(name, "push"))) { - assert(branch); - - if (stat_tracking_info(branch, &num_ours, - &num_theirs, NULL)) - continue; - - if (!num_ours && !num_theirs) - v->s = "="; - else if (!num_ours) - v->s = "<"; - else if (!num_theirs) - v->s = ">"; - else - v->s = "<>"; - continue; - } else - die("unknown %.*s format %s", - (int)(formatp - name), name, formatp); - } - - if (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } - } - - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &ref->value[i]; - if (v->s == NULL) - goto need_obj; - } - return; - - need_obj: - buf = get_obj(ref->objectname, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - - grab_values(ref->value, 0, obj, buf, size); - if (!eaten) - free(buf); - - /* - * If there is no atom that wants to know about tagged - * object, we are done. - */ - if (!need_tagged || (obj->type != OBJ_TAG)) - return; - - /* - * If it is a tag object, see if we use a value that derefs - * the object, and if we do grab the object it refers to. - */ - tagged = ((struct tag *)obj)->tagged->sha1; - - /* - * NEEDSWORK: This derefs tag only once, which - * is good to deal with chains of trust, but - * is not consistent with what deref_tag() does - * which peels the onion to the core. - */ - buf = get_obj(tagged, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(tagged), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(tagged), ref->refname); - grab_values(ref->value, 1, obj, buf, size); - if (!eaten) - free(buf); -} - -/* - * Given a ref, return the value for the atom. This lazily gets value - * out of the object by calling populate value. - */ -static void get_value(struct refinfo *ref, int atom, struct atom_value **v) -{ - if (!ref->value) { - populate_value(ref); - fill_missing_values(ref->value); - } - *v = &ref->value[atom]; -} - -struct grab_ref_cbdata { - struct refinfo **grab_array; - const char **grab_pattern; - int grab_cnt; -}; - -/* - * A call-back given to for_each_ref(). Filter refs and keep them for - * later object processing. - */ -static int grab_single_ref(const char *refname, const struct object_id *oid, - int flag, void *cb_data) -{ - struct grab_ref_cbdata *cb = cb_data; - struct refinfo *ref; - int cnt; - - if (flag & REF_BAD_NAME) { - warning("ignoring ref with broken name %s", refname); - return 0; - } - - if (*cb->grab_pattern) { - const char **pattern; - int namelen = strlen(refname); - for (pattern = cb->grab_pattern; *pattern; pattern++) { - const char *p = *pattern; - int plen = strlen(p); - - if ((plen <= namelen) && - !strncmp(refname, p, plen) && - (refname[plen] == '\0' || - refname[plen] == '/' || - p[plen-1] == '/')) - break; - if (!wildmatch(p, refname, WM_PATHNAME, NULL)) - break; - } - if (!*pattern) - return 0; - } - - /* - * We do not open the object yet; sort may only need refname - * to do its job and the resulting list may yet to be pruned - * by maxcount logic. - */ - ref = xcalloc(1, sizeof(*ref)); - ref->refname = xstrdup(refname); - hashcpy(ref->objectname, oid->hash); - ref->flag = flag; - - cnt = cb->grab_cnt; - REALLOC_ARRAY(cb->grab_array, cnt + 1); - cb->grab_array[cnt++] = ref; - cb->grab_cnt = cnt; - return 0; -} - -static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) -{ - struct atom_value *va, *vb; - int cmp; - cmp_type cmp_type = used_atom_type[s->atom]; - - get_value(a, s->atom, &va); - get_value(b, s->atom, &vb); - switch (cmp_type) { - case FIELD_STR: - cmp = strcmp(va->s, vb->s); - break; - default: - if (va->ul < vb->ul) - cmp = -1; - else if (va->ul == vb->ul) - cmp = 0; - else - cmp = 1; - break; - } - return (s->reverse) ? -cmp : cmp; -} - -static struct ref_sort *ref_sort; -static int compare_refs(const void *a_, const void *b_) -{ - struct refinfo *a = *((struct refinfo **)a_); - struct refinfo *b = *((struct refinfo **)b_); - struct ref_sort *s; - - for (s = ref_sort; s; s = s->next) { - int cmp = cmp_ref_sort(s, a, b); - if (cmp) - return cmp; - } - return 0; -} - -static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs) -{ - ref_sort = sort; - qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); -} - -static void print_value(struct atom_value *v, int quote_style) -{ - struct strbuf sb = STRBUF_INIT; - switch (quote_style) { - case QUOTE_NONE: - fputs(v->s, stdout); - break; - case QUOTE_SHELL: - sq_quote_buf(&sb, v->s); - break; - case QUOTE_PERL: - perl_quote_buf(&sb, v->s); - break; - case QUOTE_PYTHON: - python_quote_buf(&sb, v->s); - break; - case QUOTE_TCL: - tcl_quote_buf(&sb, v->s); - break; - } - if (quote_style != QUOTE_NONE) { - fputs(sb.buf, stdout); - strbuf_release(&sb); - } -} - -static int hex1(char ch) -{ - if ('0' <= ch && ch <= '9') - return ch - '0'; - else if ('a' <= ch && ch <= 'f') - return ch - 'a' + 10; - else if ('A' <= ch && ch <= 'F') - return ch - 'A' + 10; - return -1; -} -static int hex2(const char *cp) -{ - if (cp[0] && cp[1]) - return (hex1(cp[0]) << 4) | hex1(cp[1]); - else - return -1; -} - -static void emit(const char *cp, const char *ep) -{ - while (*cp && (!ep || cp < ep)) { - if (*cp == '%') { - if (cp[1] == '%') - cp++; - else { - int ch = hex2(cp + 1); - if (0 <= ch) { - putchar(ch); - cp += 3; - continue; - } - } - } - putchar(*cp); - cp++; - } -} - -static void show_ref(struct refinfo *info, const char *format, int quote_style) -{ - const char *cp, *sp, *ep; - - for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { - struct atom_value *atomv; - - ep = strchr(sp, ')'); - if (cp < sp) - emit(cp, sp); - get_value(info, parse_atom(sp + 2, ep), &atomv); - print_value(atomv, quote_style); - } - if (*cp) { - sp = cp + strlen(cp); - emit(cp, sp); - } - if (need_color_reset_at_eol) { - struct atom_value resetv; - char color[COLOR_MAXLEN] = ""; - - if (color_parse("reset", color) < 0) - die("BUG: couldn't parse 'reset' as a color"); - resetv.s = color; - print_value(&resetv, quote_style); - } - putchar('\n'); -} - -static struct ref_sort *default_sort(void) -{ - static const char cstr_name[] = "refname"; - - struct ref_sort *sort = xcalloc(1, sizeof(*sort)); - - sort->next = NULL; - sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name)); - return sort; -} - -static int opt_parse_sort(const struct option *opt, const char *arg, int unset) -{ - struct ref_sort **sort_tail = opt->value; - struct ref_sort *s; - int len; - - if (!arg) /* should --no-sort void the list ? */ - return -1; - - s = xcalloc(1, sizeof(*s)); - s->next = *sort_tail; - *sort_tail = s; - - if (*arg == '-') { - s->reverse = 1; - arg++; - } - len = strlen(arg); - s->atom = parse_atom(arg, arg+len); - return 0; -} +#include "ref-filter.h" static char const * const for_each_ref_usage[] = { N_("git for-each-ref [<options>] [<pattern>]"), @@ -1081,12 +12,12 @@ static char const * const for_each_ref_usage[] = { int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { - int i, num_refs; + int i; const char *format = "%(objectname) %(objecttype)\t%(refname)"; - struct ref_sort *sort = NULL, **sort_tail = &sort; + struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; int maxcount = 0, quote_style = 0; - struct refinfo **refs; - struct grab_ref_cbdata cbdata; + struct ref_array array; + struct ref_filter filter; struct option opts[] = { OPT_BIT('s', "shell", "e_style, @@ -1101,8 +32,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_GROUP(""), OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), - OPT_CALLBACK(0 , "sort", sort_tail, N_("key"), - N_("field name to sort on"), &opt_parse_sort), + OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), + N_("field name to sort on"), &parse_opt_ref_sorting), OPT_END(), }; @@ -1115,26 +46,25 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) error("more than one quoting style?"); usage_with_options(for_each_ref_usage, opts); } - if (verify_format(format)) + if (verify_ref_format(format)) usage_with_options(for_each_ref_usage, opts); - if (!sort) - sort = default_sort(); + if (!sorting) + sorting = ref_default_sorting(); /* for warn_ambiguous_refs */ git_config(git_default_config, NULL); - memset(&cbdata, 0, sizeof(cbdata)); - cbdata.grab_pattern = argv; - for_each_rawref(grab_single_ref, &cbdata); - refs = cbdata.grab_array; - num_refs = cbdata.grab_cnt; - - sort_refs(sort, refs, num_refs); + memset(&array, 0, sizeof(array)); + memset(&filter, 0, sizeof(filter)); + filter.name_patterns = argv; + filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); + ref_array_sort(sorting, &array); - if (!maxcount || num_refs < maxcount) - maxcount = num_refs; + if (!maxcount || array.nr < maxcount) + maxcount = array.nr; for (i = 0; i < maxcount; i++) - show_ref(refs[i], format, quote_style); + show_ref_array_item(array.items[i], format, quote_style); + ref_array_clear(&array); return 0; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 4e8e2ee5b7..2679793049 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -451,35 +451,41 @@ static void fsck_dir(int i, char *path) static int default_refs; +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1) +{ + struct object *obj; + + if (!is_null_sha1(sha1)) { + obj = lookup_object(sha1); + if (obj) { + obj->used = 1; + mark_object_reachable(obj); + } else { + error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1)); + errors_found |= ERROR_REACHABLE; + } + } +} + static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct object *obj; + const char *refname = cb_data; if (verbose) fprintf(stderr, "Checking reflog %s->%s\n", sha1_to_hex(osha1), sha1_to_hex(nsha1)); - if (!is_null_sha1(osha1)) { - obj = lookup_object(osha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - } - obj = lookup_object(nsha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } + fsck_handle_reflog_sha1(refname, osha1); + fsck_handle_reflog_sha1(refname, nsha1); return 0; } static int fsck_handle_reflog(const char *logname, const struct object_id *oid, int flag, void *cb_data) { - for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); + for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname); return 0; } 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 7ea2020d82..f07bc66ed6 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -616,7 +616,9 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2, int cmp = type1 - type2; if (cmp) return cmp; - return offset1 - offset2; + return offset1 < offset2 ? -1 : + offset1 > offset2 ? 1 : + 0; } static int find_ofs_delta(const off_t offset, enum object_type type) @@ -784,7 +786,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, assert(data || obj_entry); read_lock(); - collision_test_needed = has_sha1_file(sha1); + collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK); read_unlock(); if (collision_test_needed && !data) { @@ -1051,7 +1053,9 @@ static int compare_ofs_delta_entry(const void *a, const void *b) const struct ofs_delta_entry *delta_a = a; const struct ofs_delta_entry *delta_b = b; - return delta_a->offset - delta_b->offset; + return delta_a->offset < delta_b->offset ? -1 : + delta_a->offset > delta_b->offset ? 1 : + 0; } static int compare_ref_delta_entry(const void *a, const void *b) @@ -1223,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) { @@ -1245,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); @@ -1327,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 @@ -1342,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/log.c b/builtin/log.c index e67671e37a..878104943f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -795,7 +795,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) { struct rev_info check_rev; - struct commit *commit; + struct commit *commit, *c1, *c2; struct object *o1, *o2; unsigned flags1, flags2; @@ -803,9 +803,11 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; - flags1 = o1->flags; o2 = rev->pending.objects[1].item; + flags1 = o1->flags; flags2 = o2->flags; + c1 = lookup_commit_reference(o1->sha1); + c2 = lookup_commit_reference(o2->sha1); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) die(_("Not a range.")); @@ -827,10 +829,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) } /* reset for next revision walk */ - clear_commit_marks((struct commit *)o1, - SEEN | UNINTERESTING | SHOWN | ADDED); - clear_commit_marks((struct commit *)o2, - SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED); o1->flags = flags1; o2->flags = flags2; } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 80fe8c7dc1..62cc16ddc2 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2588,23 +2588,6 @@ static int option_parse_unpack_unreachable(const struct option *opt, return 0; } -static int option_parse_ulong(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - die(_("option %s does not accept negative form"), - opt->long_name); - - if (!git_parse_ulong(arg, opt->value)) - die(_("unable to parse value '%s' for option %s"), - arg, opt->long_name); - return 0; -} - -#define OPT_ULONG(s, l, v, h) \ - { OPTION_CALLBACK, (s), (l), (v), "n", (h), \ - PARSE_OPT_NONEG, option_parse_ulong } - int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; @@ -2627,16 +2610,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"), N_("write the pack index file in the specified idx format version"), 0, option_parse_index_version }, - OPT_ULONG(0, "max-pack-size", &pack_size_limit, - N_("maximum size of each output pack file")), + OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, + N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, N_("ignore borrowed objects from alternate object store")), OPT_BOOL(0, "incremental", &incremental, N_("ignore packed objects")), OPT_INTEGER(0, "window", &window, N_("limit pack window by objects")), - OPT_ULONG(0, "window-memory", &window_memory_limit, - N_("limit pack window by memory in addition to object limit")), + OPT_MAGNITUDE(0, "window-memory", &window_memory_limit, + N_("limit pack window by memory in addition to object limit")), OPT_INTEGER(0, "depth", &depth, N_("maximum length of delta chain allowed in the resulting pack")), OPT_BOOL(0, "reuse-delta", &reuse_delta, 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/replace.c b/builtin/replace.c index 0d52e7fa1d..6b3c469a33 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -104,9 +104,9 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) continue; } full_hex = sha1_to_hex(sha1); - snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex); + snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex); /* read_ref() may reuse the buffer */ - full_hex = ref + strlen("refs/replace/"); + full_hex = ref + strlen(git_replace_ref_base); if (read_ref(ref, sha1)) { error("replace ref '%s' not found.", full_hex); had_error = 1; @@ -134,7 +134,7 @@ static void check_ref_valid(unsigned char object[20], int force) { if (snprintf(ref, ref_size, - "refs/replace/%s", + "%s%s", git_replace_ref_base, sha1_to_hex(object)) > ref_size - 1) die("replace ref name too long: %.*s...", 50, ref); if (check_refname_format(ref, 0)) 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/verify-commit.c b/builtin/verify-commit.c index ec0c4e3d83..38bedf8f9f 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -18,25 +18,21 @@ static const char * const verify_commit_usage[] = { NULL }; -static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose) +static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, unsigned flags) { struct signature_check signature_check; + int ret; memset(&signature_check, 0, sizeof(signature_check)); - check_commit_signature(lookup_commit(sha1), &signature_check); - - if (verbose && signature_check.payload) - fputs(signature_check.payload, stdout); - - if (signature_check.gpg_output) - fputs(signature_check.gpg_output, stderr); + ret = check_commit_signature(lookup_commit(sha1), &signature_check); + print_signature_buffer(&signature_check, flags); signature_check_clear(&signature_check); - return signature_check.result != 'G'; + return ret; } -static int verify_commit(const char *name, int verbose) +static int verify_commit(const char *name, unsigned flags) { enum object_type type; unsigned char sha1[20]; @@ -54,7 +50,7 @@ static int verify_commit(const char *name, int verbose) return error("%s: cannot verify a non-commit object of type %s.", name, typename(type)); - ret = run_gpg_verify(sha1, buf, size, verbose); + ret = run_gpg_verify(sha1, buf, size, flags); free(buf); return ret; @@ -71,8 +67,10 @@ static int git_verify_commit_config(const char *var, const char *value, void *cb int cmd_verify_commit(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + unsigned flags = 0; const struct option verify_commit_options[] = { OPT__VERBOSE(&verbose, N_("print commit contents")), + OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), OPT_END() }; @@ -83,11 +81,14 @@ int cmd_verify_commit(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_commit_usage, verify_commit_options); + if (verbose) + flags |= GPG_VERIFY_VERBOSE; + /* sometimes the program was terminated because this signal * was received in the process of writing the gpg input: */ signal(SIGPIPE, SIG_IGN); while (i < argc) - if (verify_commit(argv[i++], verbose)) + if (verify_commit(argv[i++], flags)) had_error = 1; return had_error; } diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 53c68fce3a..00663f6a30 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -18,21 +18,30 @@ static const char * const verify_tag_usage[] = { NULL }; -static int run_gpg_verify(const char *buf, unsigned long size, int verbose) +static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) { + struct signature_check sigc; int len; + int ret; + + memset(&sigc, 0, sizeof(sigc)); len = parse_signature(buf, size); - if (verbose) - write_in_full(1, buf, len); - if (size == len) + if (size == len) { + if (flags & GPG_VERIFY_VERBOSE) + write_in_full(1, buf, len); return error("no signature found"); + } + + ret = check_signature(buf, len, buf + len, size - len, &sigc); + print_signature_buffer(&sigc, flags); - return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL); + signature_check_clear(&sigc); + return ret; } -static int verify_tag(const char *name, int verbose) +static int verify_tag(const char *name, unsigned flags) { enum object_type type; unsigned char sha1[20]; @@ -52,7 +61,7 @@ static int verify_tag(const char *name, int verbose) if (!buf) return error("%s: unable to read file.", name); - ret = run_gpg_verify(buf, size, verbose); + ret = run_gpg_verify(buf, size, flags); free(buf); return ret; @@ -69,8 +78,10 @@ static int git_verify_tag_config(const char *var, const char *value, void *cb) int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + unsigned flags = 0; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), + OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), OPT_END() }; @@ -81,11 +92,14 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (argc <= i) usage_with_options(verify_tag_usage, verify_tag_options); + if (verbose) + flags |= GPG_VERIFY_VERBOSE; + /* sometimes the program was terminated because this signal * was received in the process of writing the gpg input: */ signal(SIGPIPE, SIG_IGN); while (i < argc) - if (verify_tag(argv[i++], verbose)) + if (verify_tag(argv[i++], flags)) had_error = 1; return had_error; } 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); +} |