diff options
Diffstat (limited to 'builtin')
40 files changed, 1935 insertions, 704 deletions
diff --git a/builtin/add.c b/builtin/add.c index f45d9d4865..8266a9cb70 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -343,6 +343,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; + rev.diffopt.use_color = 0; DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) diff --git a/builtin/apply.c b/builtin/apply.c index a88c372de5..50912c928f 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -2999,7 +2999,7 @@ static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsig return 0; } -static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) +static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf) { if (!ce) return 0; @@ -3117,7 +3117,7 @@ static struct patch *previous_patch(struct patch *patch, int *gone) return previous; } -static int verify_index_match(struct cache_entry *ce, struct stat *st) +static int verify_index_match(const struct cache_entry *ce, struct stat *st) { if (S_ISGITLINK(ce->ce_mode)) { if (!S_ISDIR(st->st_mode)) @@ -3130,7 +3130,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st) #define SUBMODULE_PATCH_WITHOUT_INDEX 1 static int load_patch_target(struct strbuf *buf, - struct cache_entry *ce, + const struct cache_entry *ce, struct stat *st, const char *name, unsigned expected_mode) @@ -3160,7 +3160,8 @@ static int load_patch_target(struct strbuf *buf, * we read from the result of a previous diff. */ static int load_preimage(struct image *image, - struct patch *patch, struct stat *st, struct cache_entry *ce) + struct patch *patch, struct stat *st, + const struct cache_entry *ce) { struct strbuf buf = STRBUF_INIT; size_t len; @@ -3273,7 +3274,7 @@ static int load_current(struct image *image, struct patch *patch) } static int try_threeway(struct image *image, struct patch *patch, - struct stat *st, struct cache_entry *ce) + struct stat *st, const struct cache_entry *ce) { unsigned char pre_sha1[20], post_sha1[20], our_sha1[20]; struct strbuf buf = STRBUF_INIT; @@ -3343,7 +3344,7 @@ static int try_threeway(struct image *image, struct patch *patch, return 0; } -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) +static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce) { struct image image; @@ -3847,7 +3848,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned const char *s = buf; if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) - die(_("corrupt patch for subproject %s"), path); + die(_("corrupt patch for submodule %s"), path); } else { if (!cached) { if (lstat(path, &st) < 0) diff --git a/builtin/blame.c b/builtin/blame.c index 57a487e052..079dcd3407 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -21,6 +21,7 @@ #include "parse-options.h" #include "utf8.h" #include "userdiff.h" +#include "line-range.h" static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); @@ -566,11 +567,16 @@ static void dup_entry(struct blame_entry *dst, struct blame_entry *src) dst->score = 0; } -static const char *nth_line(struct scoreboard *sb, int lno) +static const char *nth_line(struct scoreboard *sb, long lno) { return sb->final_buf + sb->lineno[lno]; } +static const char *nth_line_cb(void *data, long lno) +{ + return nth_line((struct scoreboard *)data, lno); +} + /* * It is known that lines between tlno to same came from parent, and e * has an overlap with that range. it also is known that parent's @@ -1932,83 +1938,6 @@ static const char *add_prefix(const char *prefix, const char *path) } /* - * Parsing of (comma separated) one item in the -L option - */ -static const char *parse_loc(const char *spec, - struct scoreboard *sb, long lno, - long begin, long *ret) -{ - char *term; - const char *line; - long num; - int reg_error; - regex_t regexp; - regmatch_t match[1]; - - /* Allow "-L <something>,+20" to mean starting at <something> - * for 20 lines, or "-L <something>,-5" for 5 lines ending at - * <something>. - */ - if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { - num = strtol(spec + 1, &term, 10); - if (term != spec + 1) { - if (spec[0] == '-') - num = 0 - num; - if (0 < num) - *ret = begin + num - 2; - else if (!num) - *ret = begin; - else - *ret = begin + num; - return term; - } - return spec; - } - num = strtol(spec, &term, 10); - if (term != spec) { - *ret = num; - return term; - } - if (spec[0] != '/') - return spec; - - /* it could be a regexp of form /.../ */ - for (term = (char *) spec + 1; *term && *term != '/'; term++) { - if (*term == '\\') - term++; - } - if (*term != '/') - return spec; - - /* try [spec+1 .. term-1] as regexp */ - *term = 0; - begin--; /* input is in human terms */ - line = nth_line(sb, begin); - - if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && - !(reg_error = regexec(®exp, line, 1, match, 0))) { - const char *cp = line + match[0].rm_so; - const char *nline; - - while (begin++ < lno) { - nline = nth_line(sb, begin); - if (line <= cp && cp < nline) - break; - line = nline; - } - *ret = begin; - regfree(®exp); - *term++ = '/'; - return term; - } - else { - char errbuf[1024]; - regerror(reg_error, ®exp, errbuf, 1024); - die("-L parameter '%s': %s", spec + 1, errbuf); - } -} - -/* * Parsing of -L option */ static void prepare_blame_range(struct scoreboard *sb, @@ -2016,15 +1945,7 @@ static void prepare_blame_range(struct scoreboard *sb, long lno, long *bottom, long *top) { - const char *term; - - term = parse_loc(bottomtop, sb, lno, 1, bottom); - if (*term == ',') { - term = parse_loc(term + 1, sb, lno, *bottom + 1, top); - if (*term) - usage(blame_usage); - } - if (*term) + if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path)) usage(blame_usage); } @@ -2574,10 +2495,6 @@ parse_done: bottom = top = 0; if (bottomtop) prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom && top && top < bottom) { - long tmp; - tmp = top; top = bottom; bottom = tmp; - } if (bottom < 1) bottom = 1; if (top < 1) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 045cee7bce..425346048b 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -13,9 +13,6 @@ #include "userdiff.h" #include "streaming.h" -#define BATCH 1 -#define BATCH_CHECK 2 - static int cat_one_file(int opt, const char *exp_type, const char *obj_name) { unsigned char sha1[20]; @@ -117,54 +114,166 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) return 0; } -static int batch_one_object(const char *obj_name, int print_contents) -{ +struct expand_data { unsigned char sha1[20]; - enum object_type type = 0; + enum object_type type; unsigned long size; - void *contents = NULL; + unsigned long disk_size; + + /* + * If mark_query is true, we do not expand anything, but rather + * just mark the object_info with items we wish to query. + */ + int mark_query; + + /* + * After a mark_query run, this object_info is set up to be + * passed to sha1_object_info_extended. It will point to the data + * elements above, so you can retrieve the response from there. + */ + struct object_info info; +}; + +static int is_atom(const char *atom, const char *s, int slen) +{ + int alen = strlen(atom); + return alen == slen && !memcmp(atom, s, alen); +} + +static void expand_atom(struct strbuf *sb, const char *atom, int len, + void *vdata) +{ + struct expand_data *data = vdata; + + if (is_atom("objectname", atom, len)) { + if (!data->mark_query) + strbuf_addstr(sb, sha1_to_hex(data->sha1)); + } else if (is_atom("objecttype", atom, len)) { + if (data->mark_query) + data->info.typep = &data->type; + else + strbuf_addstr(sb, typename(data->type)); + } else if (is_atom("objectsize", atom, len)) { + if (data->mark_query) + data->info.sizep = &data->size; + else + strbuf_addf(sb, "%lu", data->size); + } else if (is_atom("objectsize:disk", atom, len)) { + if (data->mark_query) + data->info.disk_sizep = &data->disk_size; + else + strbuf_addf(sb, "%lu", data->disk_size); + } else + die("unknown format element: %.*s", len, atom); +} + +static size_t expand_format(struct strbuf *sb, const char *start, void *data) +{ + const char *end; + + if (*start != '(') + return 0; + end = strchr(start + 1, ')'); + if (!end) + die("format element '%s' does not end in ')'", start); + + expand_atom(sb, start + 1, end - start - 1, data); + + return end - start + 1; +} + +static void print_object_or_die(int fd, const unsigned char *sha1, + enum object_type type, unsigned long size) +{ + if (type == OBJ_BLOB) { + if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0) + die("unable to stream %s to stdout", sha1_to_hex(sha1)); + } + else { + enum object_type rtype; + unsigned long rsize; + void *contents; + + contents = read_sha1_file(sha1, &rtype, &rsize); + if (!contents) + die("object %s disappeared", sha1_to_hex(sha1)); + if (rtype != type) + die("object %s changed type!?", sha1_to_hex(sha1)); + if (rsize != size) + die("object %s change size!?", sha1_to_hex(sha1)); + + write_or_die(fd, contents, size); + free(contents); + } +} + +struct batch_options { + int enabled; + int print_contents; + const char *format; +}; + +static int batch_one_object(const char *obj_name, struct batch_options *opt, + struct expand_data *data) +{ + struct strbuf buf = STRBUF_INIT; if (!obj_name) return 1; - if (get_sha1(obj_name, sha1)) { + if (get_sha1(obj_name, data->sha1)) { printf("%s missing\n", obj_name); fflush(stdout); return 0; } - if (print_contents == BATCH) - contents = read_sha1_file(sha1, &type, &size); - else - type = sha1_object_info(sha1, &size); - - if (type <= 0) { + if (sha1_object_info_extended(data->sha1, &data->info) < 0) { printf("%s missing\n", obj_name); fflush(stdout); - if (print_contents == BATCH) - free(contents); return 0; } - printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size); - fflush(stdout); + strbuf_expand(&buf, opt->format, expand_format, data); + strbuf_addch(&buf, '\n'); + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); - if (print_contents == BATCH) { - write_or_die(1, contents, size); - printf("\n"); - fflush(stdout); - free(contents); + if (opt->print_contents) { + print_object_or_die(1, data->sha1, data->type, data->size); + write_or_die(1, "\n", 1); } - return 0; } -static int batch_objects(int print_contents) +static int batch_objects(struct batch_options *opt) { struct strbuf buf = STRBUF_INIT; + struct expand_data data; + + if (!opt->format) + opt->format = "%(objectname) %(objecttype) %(objectsize)"; + + /* + * Expand once with our special mark_query flag, which will prime the + * object_info to be handed to sha1_object_info_extended for each + * object. + */ + memset(&data, 0, sizeof(data)); + data.mark_query = 1; + strbuf_expand(&buf, opt->format, expand_format, &data); + data.mark_query = 0; + + /* + * We are going to call get_sha1 on a potentially very large number of + * objects. In most large cases, these will be actual object sha1s. The + * cost to double-check that each one is not also a ref (just so we can + * warn) ends up dwarfing the actual cost of the object lookups + * themselves. We can work around it by just turning off the warning. + */ + warn_on_object_refname_ambiguity = 0; while (strbuf_getline(&buf, stdin, '\n') != EOF) { - int error = batch_one_object(buf.buf, print_contents); + int error = batch_one_object(buf.buf, opt, &data); if (error) return error; } @@ -186,10 +295,29 @@ static int git_cat_file_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int batch_option_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct batch_options *bo = opt->value; + + if (unset) { + memset(bo, 0, sizeof(*bo)); + return 0; + } + + bo->enabled = 1; + bo->print_contents = !strcmp(opt->long_name, "batch"); + bo->format = arg; + + return 0; +} + int cmd_cat_file(int argc, const char **argv, const char *prefix) { - int opt = 0, batch = 0; + int opt = 0; const char *exp_type = NULL, *obj_name = NULL; + struct batch_options batch = {0}; const struct option options[] = { OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), @@ -200,12 +328,12 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), OPT_SET_INT(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), - OPT_SET_INT(0, "batch", &batch, - N_("show info and content of objects fed from the standard input"), - BATCH), - OPT_SET_INT(0, "batch-check", &batch, - N_("show info about objects fed from the standard input"), - BATCH_CHECK), + { OPTION_CALLBACK, 0, "batch", &batch, "format", + N_("show info and content of objects fed from the standard input"), + PARSE_OPT_OPTARG, batch_option_callback }, + { OPTION_CALLBACK, 0, "batch-check", &batch, "format", + N_("show info about objects fed from the standard input"), + PARSE_OPT_OPTARG, batch_option_callback }, OPT_END() }; @@ -222,19 +350,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) else usage_with_options(cat_file_usage, options); } - if (!opt && !batch) { + if (!opt && !batch.enabled) { if (argc == 2) { exp_type = argv[0]; obj_name = argv[1]; } else usage_with_options(cat_file_usage, options); } - if (batch && (opt || argc)) { + if (batch.enabled && (opt || argc)) { usage_with_options(cat_file_usage, options); } - if (batch) - return batch_objects(batch); + if (batch.enabled) + return batch_objects(&batch); return cat_one_file(opt, exp_type, obj_name); } diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 854a88a056..4a8fc707c7 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -5,7 +5,7 @@ #include "pathspec.h" #include "parse-options.h" -static int quiet, verbose, stdin_paths; +static int quiet, verbose, stdin_paths, show_non_matching; static const char * const check_ignore_usage[] = { "git check-ignore [options] pathname...", "git check-ignore [options] --stdin < <list-of-paths>", @@ -22,21 +22,28 @@ static const struct option check_ignore_options[] = { N_("read file names from stdin")), OPT_BOOLEAN('z', NULL, &null_term_line, N_("input paths are terminated by a null character")), + OPT_BOOLEAN('n', "non-matching", &show_non_matching, + N_("show non-matching input paths")), OPT_END() }; static void output_exclude(const char *path, struct exclude *exclude) { - char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : ""; - char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : ""; + char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : ""; + char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : ""; if (!null_term_line) { if (!verbose) { write_name_quoted(path, stdout, '\n'); } else { - quote_c_style(exclude->el->src, NULL, stdout, 0); - printf(":%d:%s%s%s\t", - exclude->srcpos, - bang, exclude->pattern, slash); + if (exclude) { + quote_c_style(exclude->el->src, NULL, stdout, 0); + printf(":%d:%s%s%s\t", + exclude->srcpos, + bang, exclude->pattern, slash); + } + else { + printf("::\t"); + } quote_c_style(path, NULL, stdout, 0); fputc('\n', stdout); } @@ -44,30 +51,26 @@ static void output_exclude(const char *path, struct exclude *exclude) if (!verbose) { printf("%s%c", path, '\0'); } else { - printf("%s%c%d%c%s%s%s%c%s%c", - exclude->el->src, '\0', - exclude->srcpos, '\0', - bang, exclude->pattern, slash, '\0', - path, '\0'); + if (exclude) + printf("%s%c%d%c%s%s%s%c%s%c", + exclude->el->src, '\0', + exclude->srcpos, '\0', + bang, exclude->pattern, slash, '\0', + path, '\0'); + else + printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0'); } } } -static int check_ignore(const char *prefix, const char **pathspec) +static int check_ignore(struct dir_struct *dir, + const char *prefix, const char **pathspec) { - struct dir_struct dir; const char *path, *full_path; char *seen; int num_ignored = 0, dtype = DT_UNKNOWN, i; struct exclude *exclude; - /* read_cache() is only necessary so we can watch out for submodules. */ - if (read_cache() < 0) - die(_("index file corrupt")); - - memset(&dir, 0, sizeof(dir)); - setup_standard_excludes(&dir); - if (!pathspec || !*pathspec) { if (!quiet) fprintf(stderr, "no pathspec given.\n"); @@ -86,28 +89,26 @@ static int check_ignore(const char *prefix, const char **pathspec) ? strlen(prefix) : 0, path); full_path = check_path_for_gitlink(full_path); die_if_path_beyond_symlink(full_path, prefix); + exclude = NULL; if (!seen[i]) { - exclude = last_exclude_matching(&dir, full_path, &dtype); - if (exclude) { - if (!quiet) - output_exclude(path, exclude); - num_ignored++; - } + exclude = last_exclude_matching(dir, full_path, &dtype); } + if (!quiet && (exclude || show_non_matching)) + output_exclude(path, exclude); + if (exclude) + num_ignored++; } free(seen); - clear_directory(&dir); return num_ignored; } -static int check_ignore_stdin_paths(const char *prefix) +static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix) { struct strbuf buf, nbuf; - char **pathspec = NULL; - size_t nr = 0, alloc = 0; + char *pathspec[2] = { NULL, NULL }; int line_termination = null_term_line ? 0 : '\n'; - int num_ignored; + int num_ignored = 0; strbuf_init(&buf, 0); strbuf_init(&nbuf, 0); @@ -118,23 +119,19 @@ static int check_ignore_stdin_paths(const char *prefix) die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - ALLOC_GROW(pathspec, nr + 1, alloc); - pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf)); - strcpy(pathspec[nr++], buf.buf); + pathspec[0] = buf.buf; + num_ignored += check_ignore(dir, prefix, (const char **)pathspec); + maybe_flush_or_die(stdout, "check-ignore to stdout"); } - ALLOC_GROW(pathspec, nr + 1, alloc); - pathspec[nr] = NULL; - num_ignored = check_ignore(prefix, (const char **)pathspec); - maybe_flush_or_die(stdout, "attribute to stdout"); strbuf_release(&buf); strbuf_release(&nbuf); - free(pathspec); return num_ignored; } int cmd_check_ignore(int argc, const char **argv, const char *prefix) { int num_ignored; + struct dir_struct dir; git_config(git_default_config, NULL); @@ -156,13 +153,24 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (verbose) die(_("cannot have both --quiet and --verbose")); } + if (show_non_matching && !verbose) + die(_("--non-matching is only valid with --verbose")); + + /* read_cache() is only necessary so we can watch out for submodules. */ + if (read_cache() < 0) + die(_("index file corrupt")); + + memset(&dir, 0, sizeof(dir)); + setup_standard_excludes(&dir); if (stdin_paths) { - num_ignored = check_ignore_stdin_paths(prefix); + num_ignored = check_ignore_stdin_paths(&dir, prefix); } else { - num_ignored = check_ignore(prefix, argv); + num_ignored = check_ignore(&dir, prefix, argv); maybe_flush_or_die(stdout, "ignore to stdout"); } + clear_directory(&dir); + return !num_ignored; } diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c new file mode 100644 index 0000000000..8f4d809bd8 --- /dev/null +++ b/builtin/check-mailmap.c @@ -0,0 +1,66 @@ +#include "builtin.h" +#include "mailmap.h" +#include "parse-options.h" +#include "string-list.h" + +static int use_stdin; +static const char * const check_mailmap_usage[] = { +N_("git check-mailmap [options] <contact>..."), +NULL +}; + +static const struct option check_mailmap_options[] = { + OPT_BOOL(0, "stdin", &use_stdin, N_("also read contacts from stdin")), + OPT_END() +}; + +static void check_mailmap(struct string_list *mailmap, const char *contact) +{ + const char *name, *mail; + size_t namelen, maillen; + struct ident_split ident; + + if (split_ident_line(&ident, contact, strlen(contact))) + die(_("unable to parse contact: %s"), contact); + + name = ident.name_begin; + namelen = ident.name_end - ident.name_begin; + mail = ident.mail_begin; + maillen = ident.mail_end - ident.mail_begin; + + map_user(mailmap, &mail, &maillen, &name, &namelen); + + if (namelen) + printf("%.*s ", (int)namelen, name); + printf("<%.*s>\n", (int)maillen, mail); +} + +int cmd_check_mailmap(int argc, const char **argv, const char *prefix) +{ + int i; + struct string_list mailmap = STRING_LIST_INIT_NODUP; + + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, check_mailmap_options, + check_mailmap_usage, 0); + if (argc == 0 && !use_stdin) + die(_("no contacts specified")); + + read_mailmap(&mailmap, NULL); + + for (i = 0; i < argc; ++i) + check_mailmap(&mailmap, argv[i]); + maybe_flush_or_die(stdout, "stdout"); + + if (use_stdin) { + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin, '\n') != EOF) { + check_mailmap(&mailmap, buf.buf); + maybe_flush_or_die(stdout, "stdout"); + } + strbuf_release(&buf); + } + + clear_mailmap(&mailmap); + return 0; +} diff --git a/builtin/checkout.c b/builtin/checkout.c index e5103b9c95..22cc7fcb59 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -97,7 +97,7 @@ static int read_tree_some(struct tree *tree, const char **pathspec) return 0; } -static int skip_same_name(struct cache_entry *ce, int pos) +static int skip_same_name(const struct cache_entry *ce, int pos) { while (++pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) @@ -105,7 +105,7 @@ static int skip_same_name(struct cache_entry *ce, int pos) return pos; } -static int check_stage(int stage, struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -119,7 +119,7 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) return error(_("path '%s' does not have their version"), ce->name); } -static int check_stages(unsigned stages, struct cache_entry *ce, int pos) +static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) { unsigned seen = 0; const char *name = ce->name; @@ -321,7 +321,7 @@ static int checkout_paths(const struct checkout_opts *opts, /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; @@ -587,7 +587,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, struct branch_info *new) { struct strbuf msg = STRBUF_INIT; - const char *old_desc; + const char *old_desc, *reflog_msg; if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { @@ -620,8 +620,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts, old_desc = old->name; if (!old_desc && old->commit) old_desc = sha1_to_hex(old->commit->object.sha1); - strbuf_addf(&msg, "checkout: moving from %s to %s", - old_desc ? old_desc : "(invalid)", new->name); + + reflog_msg = getenv("GIT_REFLOG_ACTION"); + if (!reflog_msg) + strbuf_addf(&msg, "checkout: moving from %s to %s", + old_desc ? old_desc : "(invalid)", new->name); + else + strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { /* Nothing to do. */ @@ -838,13 +843,16 @@ static int check_tracking_name(struct remote *remote, void *cb_data) memset(&query, 0, sizeof(struct refspec)); query.src = cb->src_ref; if (remote_find_tracking(remote, &query) || - get_sha1(query.dst, cb->dst_sha1)) + get_sha1(query.dst, cb->dst_sha1)) { + free(query.dst); return 0; + } if (cb->dst_ref) { + free(query.dst); cb->unique = 0; return 0; } - cb->dst_ref = xstrdup(query.dst); + cb->dst_ref = query.dst; return 0; } diff --git a/builtin/clean.c b/builtin/clean.c index 04e396b17a..3c85e152e1 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -13,11 +13,16 @@ #include "refs.h" #include "string-list.h" #include "quote.h" +#include "column.h" +#include "color.h" static int force = -1; /* unset */ +static int interactive; +static struct string_list del_list = STRING_LIST_INIT_DUP; +static unsigned int colopts; static const char *const builtin_clean_usage[] = { - N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."), + N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."), NULL }; @@ -27,11 +32,112 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); static const char *msg_warn_remove_failed = N_("failed to remove %s"); +static int clean_use_color = -1; +static char clean_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RESET, + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_BOLD_BLUE, /* PROMPT */ + GIT_COLOR_BOLD, /* HEADER */ + GIT_COLOR_BOLD_RED, /* HELP */ + GIT_COLOR_BOLD_RED, /* ERROR */ +}; +enum color_clean { + CLEAN_COLOR_RESET = 0, + CLEAN_COLOR_PLAIN = 1, + CLEAN_COLOR_PROMPT = 2, + CLEAN_COLOR_HEADER = 3, + CLEAN_COLOR_HELP = 4, + CLEAN_COLOR_ERROR = 5, +}; + +#define MENU_OPTS_SINGLETON 01 +#define MENU_OPTS_IMMEDIATE 02 +#define MENU_OPTS_LIST_ONLY 04 + +struct menu_opts { + const char *header; + const char *prompt; + int flags; +}; + +#define MENU_RETURN_NO_LOOP 10 + +struct menu_item { + char hotkey; + const char *title; + int selected; + int (*fn)(); +}; + +enum menu_stuff_type { + MENU_STUFF_TYPE_STRING_LIST = 1, + MENU_STUFF_TYPE_MENU_ITEM +}; + +struct menu_stuff { + enum menu_stuff_type type; + int nr; + void *stuff; +}; + +static int parse_clean_color_slot(const char *var) +{ + if (!strcasecmp(var, "reset")) + return CLEAN_COLOR_RESET; + if (!strcasecmp(var, "plain")) + return CLEAN_COLOR_PLAIN; + if (!strcasecmp(var, "prompt")) + return CLEAN_COLOR_PROMPT; + if (!strcasecmp(var, "header")) + return CLEAN_COLOR_HEADER; + if (!strcasecmp(var, "help")) + return CLEAN_COLOR_HELP; + if (!strcasecmp(var, "error")) + return CLEAN_COLOR_ERROR; + return -1; +} + static int git_clean_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "clean.requireforce")) + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "clean", &colopts); + + /* honors the color.interactive* config variables which also + applied in git-add--interactive and git-stash */ + if (!strcmp(var, "color.interactive")) { + clean_use_color = git_config_colorbool(var, value); + return 0; + } + if (!prefixcmp(var, "color.interactive.")) { + int slot = parse_clean_color_slot(var + + strlen("color.interactive.")); + if (slot < 0) + return 0; + if (!value) + return config_error_nonbool(var); + color_parse(value, var, clean_colors[slot]); + return 0; + } + + if (!strcmp(var, "clean.requireforce")) { force = !git_config_bool(var, value); - return git_default_config(var, value, cb); + return 0; + } + + /* inspect the color.ui config variable and others */ + return git_color_default_config(var, value, cb); +} + +static const char *clean_get_color(enum color_clean ix) +{ + if (want_color(clean_use_color)) + return clean_colors[ix]; + return ""; +} + +static void clean_print_color(enum color_clean ix) +{ + printf("%s", clean_get_color(ix)); } static int exclude_cb(const struct option *opt, const char *arg, int unset) @@ -56,7 +162,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { if (!quiet) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), quoted.buf); } @@ -70,7 +176,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, /* an empty dir could be removed even if it is unreadble */ res = dry_run ? 0 : rmdir(path->buf); if (res) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } @@ -94,7 +200,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) ret = 1; if (gone) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else *dir_gone = 0; @@ -102,10 +208,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } else { res = dry_run ? 0 : unlink(path->buf); if (!res) { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); string_list_append(&dels, quoted.buf); } else { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; @@ -127,7 +233,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (!res) *dir_gone = 1; else { - quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + quote_path_relative(path->buf, prefix, "ed); warning(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; ret = 1; @@ -142,24 +248,633 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, return ret; } +static void pretty_print_dels(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + const char *qname; + struct column_options copts; + + for_each_string_list_item(item, &del_list) { + qname = quote_path_relative(item->string, NULL, &buf); + string_list_append(&list, qname); + } + + /* + * always enable column display, we only consult column.* + * about layout strategy and stuff + */ + colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(&list, colopts, &copts); + strbuf_release(&buf); + string_list_clear(&list, 0); +} + +static void pretty_print_menus(struct string_list *menu_list) +{ + unsigned int local_colopts = 0; + struct column_options copts; + + local_colopts = COL_ENABLED | COL_ROW; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(menu_list, local_colopts, &copts); +} + +static void prompt_help_cmd(int singleton) +{ + clean_print_color(CLEAN_COLOR_HELP); + printf_ln(singleton ? + _("Prompt help:\n" + "1 - select a numbered item\n" + "foo - select item based on unique prefix\n" + " - (empty) select nothing") : + _("Prompt help:\n" + "1 - select a single item\n" + "3-5 - select a range of items\n" + "2-3,6-9 - select multiple ranges\n" + "foo - select item based on unique prefix\n" + "-... - unselect specified items\n" + "* - choose all items\n" + " - (empty) finish selecting")); + clean_print_color(CLEAN_COLOR_RESET); +} + +/* + * display menu stuff with number prefix and hotkey highlight + */ +static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) +{ + struct string_list menu_list = STRING_LIST_INIT_DUP; + struct strbuf menu = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct menu_item *menu_item; + struct string_list_item *string_list_item; + int i; + + switch (stuff->type) { + default: + die("Bad type of menu_staff when print menu"); + case MENU_STUFF_TYPE_MENU_ITEM: + menu_item = (struct menu_item *)stuff->stuff; + for (i = 0; i < stuff->nr; i++, menu_item++) { + const char *p; + int highlighted = 0; + + p = menu_item->title; + if ((*chosen)[i] < 0) + (*chosen)[i] = menu_item->selected ? 1 : 0; + strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1); + for (; *p; p++) { + if (!highlighted && *p == menu_item->hotkey) { + strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT)); + strbuf_addch(&menu, *p); + strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET)); + highlighted = 1; + } else { + strbuf_addch(&menu, *p); + } + } + string_list_append(&menu_list, menu.buf); + strbuf_reset(&menu); + } + break; + case MENU_STUFF_TYPE_STRING_LIST: + i = 0; + for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) { + if ((*chosen)[i] < 0) + (*chosen)[i] = 0; + strbuf_addf(&menu, "%s%2d: %s", + (*chosen)[i] ? "*" : " ", i+1, string_list_item->string); + string_list_append(&menu_list, menu.buf); + strbuf_reset(&menu); + i++; + } + break; + } + + pretty_print_menus(&menu_list); + + strbuf_release(&menu); + strbuf_release(&buf); + string_list_clear(&menu_list, 0); +} + +static int find_unique(const char *choice, struct menu_stuff *menu_stuff) +{ + struct menu_item *menu_item; + struct string_list_item *string_list_item; + int i, len, found = 0; + + len = strlen(choice); + switch (menu_stuff->type) { + default: + die("Bad type of menu_stuff when parse choice"); + case MENU_STUFF_TYPE_MENU_ITEM: + + menu_item = (struct menu_item *)menu_stuff->stuff; + for (i = 0; i < menu_stuff->nr; i++, menu_item++) { + if (len == 1 && *choice == menu_item->hotkey) { + found = i + 1; + break; + } + if (!strncasecmp(choice, menu_item->title, len)) { + if (found) { + if (len == 1) { + /* continue for hotkey matching */ + found = -1; + } else { + found = 0; + break; + } + } else { + found = i + 1; + } + } + } + break; + case MENU_STUFF_TYPE_STRING_LIST: + string_list_item = ((struct string_list *)menu_stuff->stuff)->items; + for (i = 0; i < menu_stuff->nr; i++, string_list_item++) { + if (!strncasecmp(choice, string_list_item->string, len)) { + if (found) { + found = 0; + break; + } + found = i + 1; + } + } + break; + } + return found; +} + + +/* + * Parse user input, and return choice(s) for menu (menu_stuff). + * + * Input + * (for single choice) + * 1 - select a numbered item + * foo - select item based on menu title + * - (empty) select nothing + * + * (for multiple choice) + * 1 - select a single item + * 3-5 - select a range of items + * 2-3,6-9 - select multiple ranges + * foo - select item based on menu title + * -... - unselect specified items + * * - choose all items + * - (empty) finish selecting + * + * The parse result will be saved in array **chosen, and + * return number of total selections. + */ +static int parse_choice(struct menu_stuff *menu_stuff, + int is_single, + struct strbuf input, + int **chosen) +{ + struct strbuf **choice_list, **ptr; + int nr = 0; + int i; + + if (is_single) { + choice_list = strbuf_split_max(&input, '\n', 0); + } else { + char *p = input.buf; + do { + if (*p == ',') + *p = ' '; + } while (*p++); + choice_list = strbuf_split_max(&input, ' ', 0); + } + + for (ptr = choice_list; *ptr; ptr++) { + char *p; + int choose = 1; + int bottom = 0, top = 0; + int is_range, is_number; + + strbuf_trim(*ptr); + if (!(*ptr)->len) + continue; + + /* Input that begins with '-'; unchoose */ + if (*(*ptr)->buf == '-') { + choose = 0; + strbuf_remove((*ptr), 0, 1); + } + + is_range = 0; + is_number = 1; + for (p = (*ptr)->buf; *p; p++) { + if ('-' == *p) { + if (!is_range) { + is_range = 1; + is_number = 0; + } else { + is_number = 0; + is_range = 0; + break; + } + } else if (!isdigit(*p)) { + is_number = 0; + is_range = 0; + break; + } + } + + if (is_number) { + bottom = atoi((*ptr)->buf); + top = bottom; + } else if (is_range) { + bottom = atoi((*ptr)->buf); + /* a range can be specified like 5-7 or 5- */ + if (!*(strchr((*ptr)->buf, '-') + 1)) + top = menu_stuff->nr; + else + top = atoi(strchr((*ptr)->buf, '-') + 1); + } else if (!strcmp((*ptr)->buf, "*")) { + bottom = 1; + top = menu_stuff->nr; + } else { + bottom = find_unique((*ptr)->buf, menu_stuff); + top = bottom; + } + + if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || + (is_single && bottom != top)) { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("Huh (%s)?"), (*ptr)->buf); + clean_print_color(CLEAN_COLOR_RESET); + continue; + } + + for (i = bottom; i <= top; i++) + (*chosen)[i-1] = choose; + } + + strbuf_list_free(choice_list); + + for (i = 0; i < menu_stuff->nr; i++) + nr += (*chosen)[i]; + return nr; +} + +/* + * Implement a git-add-interactive compatible UI, which is borrowed + * from git-add--interactive.perl. + * + * Return value: + * + * - Return an array of integers + * - , and it is up to you to free the allocated memory. + * - The array ends with EOF. + * - If user pressed CTRL-D (i.e. EOF), no selection returned. + */ +static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) +{ + struct strbuf choice = STRBUF_INIT; + int *chosen, *result; + int nr = 0; + int eof = 0; + int i; + + chosen = xmalloc(sizeof(int) * stuff->nr); + /* set chosen as uninitialized */ + for (i = 0; i < stuff->nr; i++) + chosen[i] = -1; + + for (;;) { + if (opts->header) { + printf_ln("%s%s%s", + clean_get_color(CLEAN_COLOR_HEADER), + _(opts->header), + clean_get_color(CLEAN_COLOR_RESET)); + } + + /* chosen will be initialized by print_highlight_menu_stuff */ + print_highlight_menu_stuff(stuff, &chosen); + + if (opts->flags & MENU_OPTS_LIST_ONLY) + break; + + if (opts->prompt) { + printf("%s%s%s%s", + clean_get_color(CLEAN_COLOR_PROMPT), + _(opts->prompt), + opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ", + clean_get_color(CLEAN_COLOR_RESET)); + } + + if (strbuf_getline(&choice, stdin, '\n') != EOF) { + strbuf_trim(&choice); + } else { + eof = 1; + break; + } + + /* help for prompt */ + if (!strcmp(choice.buf, "?")) { + prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON); + continue; + } + + /* for a multiple-choice menu, press ENTER (empty) will return back */ + if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len) + break; + + nr = parse_choice(stuff, + opts->flags & MENU_OPTS_SINGLETON, + choice, + &chosen); + + if (opts->flags & MENU_OPTS_SINGLETON) { + if (nr) + break; + } else if (opts->flags & MENU_OPTS_IMMEDIATE) { + break; + } + } + + if (eof) { + result = xmalloc(sizeof(int)); + *result = EOF; + } else { + int j = 0; + + /* + * recalculate nr, if return back from menu directly with + * default selections. + */ + if (!nr) { + for (i = 0; i < stuff->nr; i++) + nr += chosen[i]; + } + + result = xmalloc(sizeof(int) * (nr + 1)); + memset(result, 0, sizeof(int) * (nr + 1)); + for (i = 0; i < stuff->nr && j < nr; i++) { + if (chosen[i]) + result[j++] = i; + } + result[j] = EOF; + } + + free(chosen); + strbuf_release(&choice); + return result; +} + +static int clean_cmd(void) +{ + return MENU_RETURN_NO_LOOP; +} + +static int filter_by_patterns_cmd(void) +{ + struct dir_struct dir; + struct strbuf confirm = STRBUF_INIT; + struct strbuf **ignore_list; + struct string_list_item *item; + struct exclude_list *el; + int changed = -1, i; + + for (;;) { + if (!del_list.nr) + break; + + if (changed) + pretty_print_dels(); + + clean_print_color(CLEAN_COLOR_PROMPT); + printf(_("Input ignore patterns>> ")); + clean_print_color(CLEAN_COLOR_RESET); + if (strbuf_getline(&confirm, stdin, '\n') != EOF) + strbuf_trim(&confirm); + else + putchar('\n'); + + /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ + if (!confirm.len) + break; + + memset(&dir, 0, sizeof(dir)); + el = add_exclude_list(&dir, EXC_CMDL, "manual exclude"); + ignore_list = strbuf_split_max(&confirm, ' ', 0); + + for (i = 0; ignore_list[i]; i++) { + strbuf_trim(ignore_list[i]); + if (!ignore_list[i]->len) + continue; + + add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1)); + } + + changed = 0; + for_each_string_list_item(item, &del_list) { + int dtype = DT_UNKNOWN; + + if (is_excluded(&dir, item->string, &dtype)) { + *item->string = '\0'; + changed++; + } + } + + if (changed) { + string_list_remove_empty_items(&del_list, 0); + } else { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf); + clean_print_color(CLEAN_COLOR_RESET); + } + + strbuf_list_free(ignore_list); + clear_directory(&dir); + } + + strbuf_release(&confirm); + return 0; +} + +static int select_by_numbers_cmd(void) +{ + struct menu_opts menu_opts; + struct menu_stuff menu_stuff; + struct string_list_item *items; + int *chosen; + int i, j; + + menu_opts.header = NULL; + menu_opts.prompt = N_("Select items to delete"); + menu_opts.flags = 0; + + menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST; + menu_stuff.stuff = &del_list; + menu_stuff.nr = del_list.nr; + + chosen = list_and_choose(&menu_opts, &menu_stuff); + items = del_list.items; + for (i = 0, j = 0; i < del_list.nr; i++) { + if (i < chosen[j]) { + *(items[i].string) = '\0'; + } else if (i == chosen[j]) { + /* delete selected item */ + j++; + continue; + } else { + /* end of chosen (chosen[j] == EOF), won't delete */ + *(items[i].string) = '\0'; + } + } + + string_list_remove_empty_items(&del_list, 0); + + free(chosen); + return 0; +} + +static int ask_each_cmd(void) +{ + struct strbuf confirm = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct string_list_item *item; + const char *qname; + int changed = 0, eof = 0; + + for_each_string_list_item(item, &del_list) { + /* Ctrl-D should stop removing files */ + if (!eof) { + qname = quote_path_relative(item->string, NULL, &buf); + printf(_("remove %s? "), qname); + if (strbuf_getline(&confirm, stdin, '\n') != EOF) { + strbuf_trim(&confirm); + } else { + putchar('\n'); + eof = 1; + } + } + if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) { + *item->string = '\0'; + changed++; + } + } + + if (changed) + string_list_remove_empty_items(&del_list, 0); + + strbuf_release(&buf); + strbuf_release(&confirm); + return MENU_RETURN_NO_LOOP; +} + +static int quit_cmd(void) +{ + string_list_clear(&del_list, 0); + printf_ln(_("Bye.")); + return MENU_RETURN_NO_LOOP; +} + +static int help_cmd(void) +{ + clean_print_color(CLEAN_COLOR_HELP); + printf_ln(_( + "clean - start cleaning\n" + "filter by pattern - exclude items from deletion\n" + "select by numbers - select items to be deleted by numbers\n" + "ask each - confirm each deletion (like \"rm -i\")\n" + "quit - stop cleaning\n" + "help - this screen\n" + "? - help for prompt selection" + )); + clean_print_color(CLEAN_COLOR_RESET); + return 0; +} + +static void interactive_main_loop(void) +{ + while (del_list.nr) { + struct menu_opts menu_opts; + struct menu_stuff menu_stuff; + struct menu_item menus[] = { + {'c', "clean", 0, clean_cmd}, + {'f', "filter by pattern", 0, filter_by_patterns_cmd}, + {'s', "select by numbers", 0, select_by_numbers_cmd}, + {'a', "ask each", 0, ask_each_cmd}, + {'q', "quit", 0, quit_cmd}, + {'h', "help", 0, help_cmd}, + }; + int *chosen; + + menu_opts.header = N_("*** Commands ***"); + menu_opts.prompt = N_("What now"); + menu_opts.flags = MENU_OPTS_SINGLETON; + + menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM; + menu_stuff.stuff = menus; + menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item); + + clean_print_color(CLEAN_COLOR_HEADER); + printf_ln(Q_("Would remove the following item:", + "Would remove the following items:", + del_list.nr)); + clean_print_color(CLEAN_COLOR_RESET); + + pretty_print_dels(); + + chosen = list_and_choose(&menu_opts, &menu_stuff); + + if (*chosen != EOF) { + int ret; + ret = menus[*chosen].fn(); + if (ret != MENU_RETURN_NO_LOOP) { + free(chosen); + chosen = NULL; + if (!del_list.nr) { + clean_print_color(CLEAN_COLOR_ERROR); + printf_ln(_("No more files to clean, exiting.")); + clean_print_color(CLEAN_COLOR_RESET); + break; + } + continue; + } + } else { + quit_cmd(); + } + + free(chosen); + chosen = NULL; + break; + } +} + int cmd_clean(int argc, const char **argv, const char *prefix) { int i, res; int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; - struct strbuf directory = STRBUF_INIT; + struct strbuf abs_path = STRBUF_INIT; struct dir_struct dir; static const char **pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct exclude_list *el; + struct string_list_item *item; const char *qname; char *seen = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("do not print names of files removed")), OPT__DRY_RUN(&dry_run, N_("dry run")), OPT__FORCE(&force, N_("force")), + OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), OPT_BOOLEAN('d', NULL, &remove_directories, N_("remove whole directories")), { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"), @@ -186,12 +901,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (ignored && ignored_only) die(_("-x and -X cannot be used together")); - if (!dry_run && !force) { + if (!interactive && !dry_run && !force) { if (config_set) - die(_("clean.requireForce set to true and neither -n nor -f given; " + die(_("clean.requireForce set to true and neither -i, -n nor -f given; " "refusing to clean")); else - die(_("clean.requireForce defaults to true and neither -n nor -f given; " + die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; " "refusing to clean")); } @@ -221,8 +936,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct dir_entry *ent = dir.entries[i]; int len, pos; int matches = 0; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; + const char *rel; /* * Remove the '/' at the end that directory @@ -242,13 +958,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) continue; /* Yup, this one exists unmerged */ } - /* - * we might have removed this as part of earlier - * recursive directory removal, so lstat() here could - * fail with ENOENT. - */ if (lstat(ent->name, &st)) - continue; + die_errno("Cannot lstat '%s'", ent->name); if (pathspec) { memset(seen, 0, argc > 0 ? argc : 1); @@ -257,33 +968,62 @@ int cmd_clean(int argc, const char **argv, const char *prefix) } if (S_ISDIR(st.st_mode)) { - strbuf_addstr(&directory, ent->name); if (remove_directories || (matches == MATCHED_EXACTLY)) { - if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone)) - errors++; - if (gone && !quiet) { - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); - } + rel = relative_path(ent->name, prefix, &buf); + string_list_append(&del_list, rel); } - strbuf_reset(&directory); } else { if (pathspec && !matches) continue; - res = dry_run ? 0 : unlink(ent->name); + rel = relative_path(ent->name, prefix, &buf); + string_list_append(&del_list, rel); + } + } + + if (interactive && del_list.nr > 0) + interactive_main_loop(); + + for_each_string_list_item(item, &del_list) { + struct stat st; + + if (prefix) + strbuf_addstr(&abs_path, prefix); + + strbuf_addstr(&abs_path, item->string); + + /* + * we might have removed this as part of earlier + * recursive directory removal, so lstat() here could + * fail with ENOENT. + */ + if (lstat(abs_path.buf, &st)) + continue; + + if (S_ISDIR(st.st_mode)) { + if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) + errors++; + if (gone && !quiet) { + qname = quote_path_relative(item->string, NULL, &buf); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); + } + } else { + res = dry_run ? 0 : unlink(abs_path.buf); if (res) { - qname = quote_path_relative(ent->name, -1, &buf, prefix); + qname = quote_path_relative(item->string, NULL, &buf); warning(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { - qname = quote_path_relative(ent->name, -1, &buf, prefix); + qname = quote_path_relative(item->string, NULL, &buf); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } + strbuf_reset(&abs_path); } free(seen); - strbuf_release(&directory); + strbuf_release(&abs_path); + strbuf_release(&buf); + string_list_clear(&del_list, 0); string_list_clear(&exclude_list, 0); return (errors != 0); } diff --git a/builtin/clone.c b/builtin/clone.c index dad4265989..552f3409e3 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -18,7 +18,6 @@ #include "transport.h" #include "strbuf.h" #include "dir.h" -#include "pack-refs.h" #include "sigchain.h" #include "branch.h" #include "remote.h" @@ -381,7 +380,7 @@ static void clone_local(const char *src_repo, const char *dest_repo) } if (0 <= option_verbosity) - printf(_("done.\n")); + fprintf(stderr, _("done.\n")); } static const char *junk_work_tree; @@ -494,13 +493,16 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; + lock_packed_refs(LOCK_DIE_ON_ERROR); + for (r = local_refs; r; r = r->next) { if (!r->peer_ref) continue; add_packed_ref(r->peer_ref->name, r->old_sha1); } - pack_refs(PACK_REFS_ALL); + if (commit_packed_refs()) + die_errno("unable to overwrite old ref-pack file"); } static void write_followtags(const struct ref *refs, const char *msg) @@ -542,16 +544,21 @@ static void update_remote_refs(const struct ref *refs, const struct ref *mapped_refs, const struct ref *remote_head_points_at, const char *branch_top, - const char *msg) + const char *msg, + struct transport *transport, + int check_connectivity) { const struct ref *rm = mapped_refs; - if (0 <= option_verbosity) - printf(_("Checking connectivity... ")); - if (check_everything_connected(iterate_ref_map, 0, &rm)) - die(_("remote did not send all necessary objects")); - if (0 <= option_verbosity) - printf(_("done\n")); + if (check_connectivity) { + if (transport->progress) + fprintf(stderr, _("Checking connectivity... ")); + if (check_everything_connected_with_transport(iterate_ref_map, + 0, &rm, transport)) + die(_("remote did not send all necessary objects")); + if (transport->progress) + fprintf(stderr, _("done\n")); + } if (refs) { write_remote_refs(mapped_refs); @@ -697,7 +704,7 @@ static void write_refspec_config(const char* src_ref_prefix, /* * otherwise, the next "git fetch" will * simply fetch from HEAD without updating - * any remote tracking branch, which is what + * any remote-tracking branch, which is what * we want. */ } else { @@ -787,6 +794,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) is_local = option_local != 0 && path && !is_bundle; if (is_local && option_depth) warning(_("--depth is ignored in local clones; use file:// instead.")); + if (option_local > 0 && !is_local) + warning(_("--local is ignored")); if (argc == 2) dir = xstrdup(argv[1]); @@ -841,9 +850,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (0 <= option_verbosity) { if (option_bare) - printf(_("Cloning into bare repository '%s'...\n"), dir); + fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir); else - printf(_("Cloning into '%s'...\n"), dir); + fprintf(stderr, _("Cloning into '%s'...\n"), dir); } init_db(option_template, INIT_DB_QUIET); write_config(&option_config); @@ -876,24 +885,25 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); - if (!is_local) { - if (!transport->get_refs_list || !transport->fetch) - die(_("Don't know how to clone %s"), transport->url); + if (!transport->get_refs_list || (!is_local && !transport->fetch)) + die(_("Don't know how to clone %s"), transport->url); - transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + transport_set_option(transport, TRANS_OPT_KEEP, "yes"); - if (option_depth) - transport_set_option(transport, TRANS_OPT_DEPTH, - option_depth); - if (option_single_branch) - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); + if (option_depth) + transport_set_option(transport, TRANS_OPT_DEPTH, + option_depth); + if (option_single_branch) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); - transport_set_verbosity(transport, option_verbosity, option_progress); + transport_set_verbosity(transport, option_verbosity, option_progress); - if (option_upload_pack) - transport_set_option(transport, TRANS_OPT_UPLOADPACK, - option_upload_pack); - } + if (option_upload_pack) + transport_set_option(transport, TRANS_OPT_UPLOADPACK, + option_upload_pack); + + if (transport->smart_options && !option_depth) + transport->smart_options->check_self_contained_and_connected = 1; refs = transport_get_remote_refs(transport); @@ -934,6 +944,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) our_head_points_at = remote_head_points_at; } else { + if (option_branch) + die(_("Remote branch %s not found in upstream %s"), + option_branch, option_origin); + warning(_("You appear to have cloned an empty repository.")); mapped_refs = NULL; our_head_points_at = NULL; @@ -954,7 +968,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_fetch_refs(transport, mapped_refs); update_remote_refs(refs, mapped_refs, remote_head_points_at, - branch_top.buf, reflog_msg.buf); + branch_top.buf, reflog_msg.buf, transport, !is_local); update_head(our_head_points_at, remote_head, reflog_msg.buf); diff --git a/builtin/commit.c b/builtin/commit.c index 1621dfcd40..a48a7fe59c 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -29,6 +29,8 @@ #include "gpg-interface.h" #include "column.h" #include "sequencer.h" +#include "notes-utils.h" +#include "mailmap.h" static const char * const builtin_commit_usage[] = { N_("git commit [options] [--] <pathspec>..."), @@ -62,8 +64,18 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ "If you wish to commit it anyway, use:\n" "\n" " git commit --allow-empty\n" +"\n"); + +static const char empty_cherry_pick_advice_single[] = +N_("Otherwise, please use 'git reset'\n"); + +static const char empty_cherry_pick_advice_multi[] = +N_("If you wish to skip this commit, use:\n" +"\n" +" git reset\n" "\n" -"Otherwise, please use 'git reset'\n"); +"Then \"git cherry-pick --continue\" will resume cherry-picking\n" +"the remaining commits.\n"); static const char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; @@ -106,17 +118,20 @@ static enum { static const char *cleanup_arg; static enum commit_whence whence; +static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static enum { +static enum status_format { STATUS_FORMAT_NONE = 0, STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN -} status_format; + STATUS_FORMAT_PORCELAIN, + + STATUS_FORMAT_UNSPECIFIED +} status_format = STATUS_FORMAT_UNSPECIFIED; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -138,8 +153,11 @@ static void determine_whence(struct wt_status *s) { if (file_exists(git_path("MERGE_HEAD"))) whence = FROM_MERGE; - else if (file_exists(git_path("CHERRY_PICK_HEAD"))) + else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { whence = FROM_CHERRY_PICK; + if (file_exists(git_path("sequencer"))) + sequencer_in_use = 1; + } else whence = FROM_COMMIT; if (s) @@ -204,7 +222,7 @@ static int list_paths(struct string_list *list, const char *with_tree, } for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct string_list_item *item; if (ce->ce_flags & CE_UPDATE) @@ -459,6 +477,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s); break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: wt_status_print(s); @@ -528,7 +549,6 @@ static void determine_author_info(struct strbuf *author_ident) (lb - strlen(" ") - (a + strlen("\nauthor ")))); email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); - date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); len = eol - (rb + strlen("> ")); date = xmalloc(len + 2); *date = '@'; @@ -805,8 +825,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix, run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) + else if (whence == FROM_CHERRY_PICK) { fputs(_(empty_cherry_pick_advice), stderr); + if (!sequencer_in_use) + fputs(_(empty_cherry_pick_advice_single), stderr); + else + fputs(_(empty_cherry_pick_advice_multi), stderr); + } return 0; } @@ -911,6 +936,7 @@ static const char *find_author_by_nickname(const char *name) struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; + struct string_list mailmap = STRING_LIST_INIT_NODUP; const char *av[20]; int ac = 0; @@ -921,13 +947,17 @@ static const char *find_author_by_nickname(const char *name) av[++ac] = buf.buf; av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); + revs.mailmap = &mailmap; + read_mailmap(revs.mailmap, NULL); + prepare_revision_walk(&revs); commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; ctx.date_mode = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, &ctx); + format_commit_message(commit, "%aN <%aE>", &buf, &ctx); + clear_mailmap(&mailmap); return strbuf_detach(&buf, NULL); } die(_("No existing author found with '%s'"), name); @@ -960,6 +990,42 @@ static const char *read_commit_message(const char *name) return logmsg_reencode(commit, NULL, out_enc); } +/* + * Enumerate what needs to be propagated when --porcelain + * is not in effect here. + */ +static struct status_deferred_config { + enum status_format status_format; + int show_branch; +} status_deferred_config = { + STATUS_FORMAT_UNSPECIFIED, + -1 /* unspecified */ +}; + +static void finalize_deferred_config(struct wt_status *s) +{ + int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN && + !s->null_termination); + + if (s->null_termination) { + if (status_format == STATUS_FORMAT_NONE || + status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } + + if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = status_deferred_config.status_format; + if (status_format == STATUS_FORMAT_UNSPECIFIED) + status_format = STATUS_FORMAT_NONE; + + if (use_deferred_config && s->show_branch < 0) + s->show_branch = status_deferred_config.show_branch; + if (s->show_branch < 0) + s->show_branch = 0; +} + static int parse_and_validate_options(int argc, const char *argv[], const struct option *options, const char * const usage[], @@ -970,6 +1036,7 @@ static int parse_and_validate_options(int argc, const char *argv[], int f = 0; argc = parse_options(argc, argv, prefix, options, usage, 0); + finalize_deferred_config(s); if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); @@ -1054,12 +1121,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (all && argc > 0) die(_("Paths with -a does not make sense.")); - if (s->null_termination) { - if (status_format == STATUS_FORMAT_NONE) - status_format = STATUS_FORMAT_PORCELAIN; - else if (status_format == STATUS_FORMAT_LONG) - die(_("--long and -z are incompatible")); - } if (status_format != STATUS_FORMAT_NONE) dry_run = 1; @@ -1112,6 +1173,17 @@ static int git_status_config(const char *k, const char *v, void *cb) s->submodule_summary = -1; return 0; } + if (!strcmp(k, "status.short")) { + if (git_config_bool(k, v)) + status_deferred_config.status_format = STATUS_FORMAT_SHORT; + else + status_deferred_config.status_format = STATUS_FORMAT_NONE; + return 0; + } + if (!strcmp(k, "status.branch")) { + status_deferred_config.show_branch = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { s->use_color = git_config_colorbool(k, v); return 0; @@ -1154,8 +1226,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&verbose, N_("be verbose")), OPT_SET_INT('s', "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), - OPT_BOOLEAN('b', "branch", &s.show_branch, - N_("show branch information")), + OPT_BOOL('b', "branch", &s.show_branch, + N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), @@ -1188,13 +1260,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_options, builtin_status_usage, 0); finalize_colopts(&s.colopts, -1); - - if (s.null_termination) { - if (status_format == STATUS_FORMAT_NONE) - status_format = STATUS_FORMAT_PORCELAIN; - else if (status_format == STATUS_FORMAT_LONG) - die(_("--long and -z are incompatible")); - } + finalize_deferred_config(&s); handle_untracked_files_arg(&s); if (show_ignored_in_status) @@ -1223,6 +1289,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(&s); break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: s.verbose = verbose; @@ -1391,7 +1460,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")), OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), - OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")), + OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), OPT_SET_INT(0, "long", &status_format, @@ -1432,6 +1501,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) wt_status_prepare(&s); gitmodules_config(); git_config(git_commit_config, &s); + status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ determine_whence(&s); s.colopts = 0; @@ -1593,7 +1663,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (cfg) { /* we are amending, so current_head is not NULL */ copy_note_for_rewrite(cfg, current_head->object.sha1, sha1); - finish_copy_notes_for_rewrite(cfg); + finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); } run_rewrite_hook(current_head->object.sha1, sha1); } diff --git a/builtin/config.c b/builtin/config.c index 19ffcaf187..4010c4320a 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -21,6 +21,7 @@ static char term = '\n'; static int use_global_config, use_system_config, use_local_config; static const char *given_config_file; +static const char *given_config_blob; static int actions, types; static const char *get_color_slot, *get_colorbool_slot; static int end_null; @@ -53,6 +54,7 @@ static struct option builtin_config_options[] = { OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")), OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")), OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")), + OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), @@ -218,7 +220,8 @@ static int get_value(const char *key_, const char *regex_) } git_config_with_options(collect_config, &values, - given_config_file, respect_includes); + given_config_file, given_config_blob, + respect_includes); ret = !values.nr; @@ -302,7 +305,8 @@ static void get_color(const char *def_color) get_color_found = 0; parsed_color[0] = '\0'; git_config_with_options(git_get_color_config, NULL, - given_config_file, respect_includes); + given_config_file, given_config_blob, + respect_includes); if (!get_color_found && def_color) color_parse(def_color, "command line", parsed_color); @@ -329,8 +333,10 @@ static int get_colorbool(int print) { get_colorbool_found = -1; get_diff_color_found = -1; + get_color_ui_found = -1; git_config_with_options(git_get_colorbool_config, NULL, - given_config_file, respect_includes); + given_config_file, given_config_blob, + respect_includes); if (get_colorbool_found < 0) { if (!strcmp(get_colorbool_slot, "color.diff")) @@ -339,6 +345,10 @@ static int get_colorbool(int print) get_colorbool_found = get_color_ui_found; } + if (get_colorbool_found < 0) + /* default value if none found in config */ + get_colorbool_found = GIT_COLOR_AUTO; + get_colorbool_found = want_color(get_colorbool_found); if (print) { @@ -348,6 +358,12 @@ static int get_colorbool(int print) return get_colorbool_found ? 0 : 1; } +static void check_blob_write(void) +{ + if (given_config_blob) + die("writing config blobs is not supported"); +} + int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; @@ -359,7 +375,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) { + if (use_global_config + use_system_config + use_local_config + + !!given_config_file + !!given_config_blob > 1) { error("only one config file at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } @@ -438,6 +455,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 0, 0); if (git_config_with_options(show_all_config, NULL, given_config_file, + given_config_blob, respect_includes) < 0) { if (given_config_file) die_errno("unable to read config file '%s'", @@ -450,6 +468,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 0, 0); if (!given_config_file && nongit) die("not in a git directory"); + if (given_config_blob) + die("editing blobs is not supported"); git_config(git_default_config, NULL); launch_editor(given_config_file ? given_config_file : git_path("config"), @@ -457,6 +477,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else if (actions == ACTION_SET) { int ret; + check_blob_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); ret = git_config_set_in_file(given_config_file, argv[0], value); @@ -466,18 +487,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) return ret; } else if (actions == ACTION_SET_ALL) { + check_blob_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, argv[2], 0); } else if (actions == ACTION_ADD) { + check_blob_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, "^$", 0); } else if (actions == ACTION_REPLACE_ALL) { + check_blob_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, @@ -500,6 +524,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) return get_value(argv[0], argv[1]); } else if (actions == ACTION_UNSET) { + check_blob_write(); check_argc(argc, 1, 2); if (argc == 2) return git_config_set_multivar_in_file(given_config_file, @@ -509,12 +534,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) argv[0], NULL); } else if (actions == ACTION_UNSET_ALL) { + check_blob_write(); check_argc(argc, 1, 2); return git_config_set_multivar_in_file(given_config_file, argv[0], NULL, argv[1], 1); } else if (actions == ACTION_RENAME_SECTION) { int ret; + check_blob_write(); check_argc(argc, 2, 2); ret = git_config_rename_section_in_file(given_config_file, argv[0], argv[1]); @@ -525,6 +552,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else if (actions == ACTION_REMOVE_SECTION) { int ret; + check_blob_write(); check_argc(argc, 1, 1); ret = git_config_rename_section_in_file(given_config_file, argv[0], NULL); diff --git a/builtin/describe.c b/builtin/describe.c index 6636a68cd9..7d73722f59 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -7,6 +7,7 @@ #include "parse-options.h" #include "diff.h" #include "hash.h" +#include "argv-array.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) @@ -21,6 +22,7 @@ static int debug; /* Display lots of verbose info */ static int all; /* Any valid ref can be used */ static int tags; /* Allow lightweight tags */ static int longformat; +static int first_parent; static int abbrev = -1; /* unspecified */ static int max_candidates = 10; static struct hash_table names; @@ -42,7 +44,7 @@ struct commit_name { unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; unsigned char sha1[20]; - const char *path; + char *path; }; static const char *prio_names[] = { "head", "lightweight", "annotated", @@ -126,12 +128,14 @@ static void add_to_known_names(const char *path, } else { e->next = NULL; } + e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; hashcpy(e->sha1, sha1); - e->path = path; + free(e->path); + e->path = xstrdup(path); } } @@ -336,6 +340,9 @@ static void describe(const char *arg, int last_one) commit_list_insert_by_date(p, &list); p->object.flags |= c->object.flags; parents = parents->next; + + if (first_parent) + break; } } @@ -404,6 +411,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all", &all, N_("use any ref")), OPT_BOOLEAN(0, "tags", &tags, N_("use any tag, even unannotated")), OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")), + OPT_BOOLEAN(0, "first-parent", &first_parent, N_("only follow first parent")), OPT__ABBREV(&abbrev), OPT_SET_INT(0, "exact-match", &max_candidates, N_("only output exact matches"), 0), @@ -435,24 +443,24 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die(_("--long is incompatible with --abbrev=0")); if (contains) { - const char **args = xmalloc((7 + argc) * sizeof(char *)); - int i = 0; - args[i++] = "name-rev"; - args[i++] = "--name-only"; - args[i++] = "--no-undefined"; + struct argv_array args; + + argv_array_init(&args); + argv_array_pushl(&args, "name-rev", + "--peel-tag", "--name-only", "--no-undefined", + NULL); if (always) - args[i++] = "--always"; + argv_array_push(&args, "--always"); if (!all) { - args[i++] = "--tags"; - if (pattern) { - char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1); - sprintf(s, "--refs=refs/tags/%s", pattern); - args[i++] = s; - } + argv_array_push(&args, "--tags"); + if (pattern) + argv_array_pushf(&args, "--refs=refs/tags/%s", pattern); + } + while (*argv) { + argv_array_push(&args, *argv); + argv++; } - memcpy(args + i, argv, argc * sizeof(char *)); - args[i + argc] = NULL; - return cmd_name_rev(i + argc, args, prefix); + return cmd_name_rev(args.argc, args.argv, prefix); } init_hash(&names); diff --git a/builtin/diff.c b/builtin/diff.c index 8c2af6cb43..9fc273d8cd 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -153,7 +153,8 @@ static int builtin_diff_index(struct rev_info *revs, static int builtin_diff_tree(struct rev_info *revs, int argc, const char **argv, - struct object_array_entry *ent) + struct object_array_entry *ent0, + struct object_array_entry *ent1) { const unsigned char *(sha1[2]); int swap = 0; @@ -161,13 +162,14 @@ static int builtin_diff_tree(struct rev_info *revs, if (argc > 1) usage(builtin_diff_usage); - /* We saw two trees, ent[0] and ent[1]. - * if ent[1] is uninteresting, they are swapped + /* + * We saw two trees, ent0 and ent1. If ent1 is uninteresting, + * swap them. */ - if (ent[1].item->flags & UNINTERESTING) + if (ent1->item->flags & UNINTERESTING) swap = 1; - sha1[swap] = ent[0].item->sha1; - sha1[1-swap] = ent[1].item->sha1; + sha1[swap] = ent0->item->sha1; + sha1[1-swap] = ent1->item->sha1; diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; @@ -251,8 +253,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) { int i; struct rev_info rev; - struct object_array_entry ent[100]; - int ents = 0, blobs = 0, paths = 0; + struct object_array ent = OBJECT_ARRAY_INIT; + int blobs = 0, paths = 0; const char *path = NULL; struct blobinfo blob[2]; int nongit; @@ -337,9 +339,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } for (i = 0; i < rev.pending.nr; i++) { - struct object_array_entry *list = rev.pending.objects+i; - struct object *obj = list->item; - const char *name = list->name; + struct object_array_entry *entry = &rev.pending.objects[i]; + struct object *obj = entry->item; + const char *name = entry->name; int flags = (obj->flags & UNINTERESTING); if (!obj->parsed) obj = parse_object(obj->sha1); @@ -348,27 +350,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix) die(_("invalid object '%s' given."), name); if (obj->type == OBJ_COMMIT) obj = &((struct commit *)obj)->tree->object; + if (obj->type == OBJ_TREE) { - if (ARRAY_SIZE(ent) <= ents) - die(_("more than %d trees given: '%s'"), - (int) ARRAY_SIZE(ent), name); obj->flags |= flags; - ent[ents].item = obj; - ent[ents].name = name; - ents++; - continue; - } - if (obj->type == OBJ_BLOB) { + add_object_array(obj, name, &ent); + } else if (obj->type == OBJ_BLOB) { if (2 <= blobs) die(_("more than two blobs given: '%s'"), name); hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; - blob[blobs].mode = list->mode; + blob[blobs].mode = entry->mode; blobs++; - continue; + } else { + die(_("unhandled object '%s' given."), name); } - die(_("unhandled object '%s' given."), name); } if (rev.prune_data.nr) { if (!path) @@ -379,7 +375,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) /* * Now, do the arguments look reasonable? */ - if (!ents) { + if (!ent.nr) { switch (blobs) { case 0: result = builtin_diff_files(&rev, argc, argv); @@ -400,23 +396,26 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } else if (blobs) usage(builtin_diff_usage); - else if (ents == 1) + else if (ent.nr == 1) result = builtin_diff_index(&rev, argc, argv); - else if (ents == 2) - result = builtin_diff_tree(&rev, argc, argv, ent); - else if (ent[0].item->flags & UNINTERESTING) { + else if (ent.nr == 2) + result = builtin_diff_tree(&rev, argc, argv, + &ent.objects[0], &ent.objects[1]); + else if (ent.objects[0].item->flags & UNINTERESTING) { /* * diff A...B where there is at least one merge base - * between A and B. We have ent[0] == merge-base, - * ent[ents-2] == A, and ent[ents-1] == B. Show diff - * between the base and B. Note that we pick one - * merge base at random if there are more than one. + * between A and B. We have ent.objects[0] == + * merge-base, ent.objects[ents-2] == A, and + * ent.objects[ents-1] == B. Show diff between the + * base and B. Note that we pick one merge base at + * random if there are more than one. */ - ent[1] = ent[ents-1]; - result = builtin_diff_tree(&rev, argc, argv, ent); + result = builtin_diff_tree(&rev, argc, argv, + &ent.objects[0], + &ent.objects[ent.nr-1]); } else result = builtin_diff_combined(&rev, argc, argv, - ent, ents); + ent.objects, ent.nr); result = diff_result_code(&rev.diffopt, result); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index d60d675f6f..8e19058744 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -379,7 +379,7 @@ static void handle_tag(const char *name, struct tag *tag) int tagged_mark; struct commit *p; - /* Trees have no identifer in fast-export output, thus we have no way + /* Trees have no identifier in fast-export output, thus we have no way * to output tags of trees, tags of tags of trees, etc. Simply omit * such tags. */ @@ -613,6 +613,8 @@ static void import_marks(char *input_file) char *line_end, *mark_end; unsigned char sha1[20]; struct object *object; + struct commit *commit; + enum object_type type; line_end = strchr(line, '\n'); if (line[0] != ':' || !line_end) @@ -621,23 +623,29 @@ static void import_marks(char *input_file) mark = strtoumax(line + 1, &mark_end, 10); if (!mark || mark_end == line + 1 - || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) + || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1)) die("corrupt mark line: %s", line); if (last_idnum < mark) last_idnum = mark; - object = parse_object(sha1); - if (!object) + type = sha1_object_info(sha1, NULL); + if (type < 0) + die("object not found: %s", sha1_to_hex(sha1)); + + if (type != OBJ_COMMIT) + /* only commits */ continue; + commit = lookup_commit(sha1); + if (!commit) + die("not a commit? can't happen: %s", sha1_to_hex(sha1)); + + object = &commit->object; + if (object->flags & SHOWN) error("Object %s already has a mark", sha1_to_hex(sha1)); - if (object->type != OBJ_COMMIT) - /* only commits */ - continue; - mark_object(object, mark); object->flags |= SHOWN; diff --git a/builtin/fetch.c b/builtin/fetch.c index 4b6b1dfe66..564705555b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -36,7 +36,8 @@ static int tags = TAGS_DEFAULT, unshallow; static const char *depth; static const char *upload_pack; static struct strbuf default_rla = STRBUF_INIT; -static struct transport *transport; +static struct transport *gtransport; +static struct transport *gsecondary; static const char *submodule_prefix = ""; static const char *recurse_submodules_default; @@ -95,8 +96,10 @@ static struct option builtin_fetch_options[] = { static void unlock_pack(void) { - if (transport) - transport_unlock_pack(transport); + if (gtransport) + transport_unlock_pack(gtransport); + if (gsecondary) + transport_unlock_pack(gsecondary); } static void unlock_pack_on_signal(int signo) @@ -119,7 +122,7 @@ static void add_merge_config(struct ref **head, for (rm = *head; rm; rm = rm->next) { if (branch_merge_matches(branch, i, rm->name)) { - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; break; } } @@ -140,7 +143,7 @@ static void add_merge_config(struct ref **head, refspec.src = branch->merge[i]->src; get_fetch_map(remote_refs, &refspec, tail, 1); for (rm = *old_tail; rm; rm = rm->next) - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; } } @@ -160,6 +163,8 @@ static struct ref *get_ref_map(struct transport *transport, const struct ref *remote_refs = transport_get_remote_refs(transport); if (ref_count || tags == TAGS_SET) { + struct ref **old_tail; + for (i = 0; i < ref_count; i++) { get_fetch_map(remote_refs, &refs[i], &tail, 0); if (refs[i].dst && refs[i].dst[0]) @@ -167,9 +172,23 @@ static struct ref *get_ref_map(struct transport *transport, } /* Merge everything on the command line, but not --tags */ for (rm = ref_map; rm; rm = rm->next) - rm->merge = 1; + rm->fetch_head_status = FETCH_HEAD_MERGE; if (tags == TAGS_SET) get_fetch_map(remote_refs, tag_refspec, &tail, 0); + + /* + * For any refs that we happen to be fetching via command-line + * arguments, take the opportunity to update their configured + * counterparts. However, we do not want to mention these + * entries in FETCH_HEAD at all, as they would simply be + * duplicates of existing entries. + */ + old_tail = tail; + for (i = 0; i < transport->remote->fetch_refspec_nr; i++) + get_fetch_map(ref_map, &transport->remote->fetch[i], + &tail, 1); + for (rm = *old_tail; rm; rm = rm->next) + rm->fetch_head_status = FETCH_HEAD_IGNORE; } else { /* Use the defaults */ struct remote *remote = transport->remote; @@ -186,7 +205,7 @@ static struct ref *get_ref_map(struct transport *transport, *autotags = 1; if (!i && !has_merge && ref_map && !remote->fetch[0].pattern) - ref_map->merge = 1; + ref_map->fetch_head_status = FETCH_HEAD_MERGE; } /* * if the remote we're fetching from is the same @@ -202,7 +221,7 @@ static struct ref *get_ref_map(struct transport *transport, ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) die(_("Couldn't find remote ref HEAD")); - ref_map->merge = 1; + ref_map->fetch_head_status = FETCH_HEAD_MERGE; tail = &ref_map->next; } } @@ -389,7 +408,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); - int want_merge; + int want_status; fp = fopen(filename, "a"); if (!fp) @@ -407,19 +426,22 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } /* - * The first pass writes objects to be merged and then the - * second pass writes the rest, in order to allow using - * FETCH_HEAD as a refname to refer to the ref to be merged. + * We do a pass for each fetch_head_status type in their enum order, so + * merged entries are written before not-for-merge. That lets readers + * use FETCH_HEAD as a refname to refer to the ref to be merged. */ - for (want_merge = 1; 0 <= want_merge; want_merge--) { + for (want_status = FETCH_HEAD_MERGE; + want_status <= FETCH_HEAD_IGNORE; + want_status++) { for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; + const char *merge_status_marker = ""; commit = lookup_commit_reference_gently(rm->old_sha1, 1); if (!commit) - rm->merge = 0; + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; - if (rm->merge != want_merge) + if (rm->fetch_head_status != want_status) continue; if (rm->peer_ref) { @@ -465,16 +487,26 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_addf(¬e, "%s ", kind); strbuf_addf(¬e, "'%s' of ", what); } - fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(rm->old_sha1), - rm->merge ? "" : "not-for-merge", - note.buf); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); + switch (rm->fetch_head_status) { + case FETCH_HEAD_NOT_FOR_MERGE: + merge_status_marker = "not-for-merge"; + /* fall-through */ + case FETCH_HEAD_MERGE: + fprintf(fp, "%s\t%s\t%s", + sha1_to_hex(rm->old_sha1), + merge_status_marker, + note.buf); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); + break; + default: + /* do not write anything to FETCH_HEAD */ + break; + } strbuf_reset(¬e); if (ref) { @@ -571,7 +603,8 @@ static int add_existing(const char *refname, const unsigned char *sha1, { struct string_list *list = (struct string_list *)cbdata; struct string_list_item *item = string_list_insert(list, refname); - item->util = (void *)sha1; + item->util = xmalloc(20); + hashcpy(item->util, sha1); return 0; } @@ -590,7 +623,7 @@ static void find_non_local_tags(struct transport *transport, struct ref **head, struct ref ***tail) { - struct string_list existing_refs = STRING_LIST_INIT_NODUP; + struct string_list existing_refs = STRING_LIST_INIT_DUP; struct string_list remote_refs = STRING_LIST_INIT_NODUP; const struct ref *ref; struct string_list_item *item = NULL; @@ -636,7 +669,7 @@ static void find_non_local_tags(struct transport *transport, item = string_list_insert(&remote_refs, ref->name); item->util = (void *)ref->old_sha1; } - string_list_clear(&existing_refs, 0); + string_list_clear(&existing_refs, 1); /* * We may have a final lightweight tag that needs to be @@ -690,14 +723,56 @@ static int truncate_fetch_head(void) return 0; } +static void set_option(struct transport *transport, const char *name, const char *value) +{ + int r = transport_set_option(transport, name, value); + if (r < 0) + die(_("Option \"%s\" value \"%s\" is not valid for %s"), + name, value, transport->url); + if (r > 0) + warning(_("Option \"%s\" is ignored for %s\n"), + name, transport->url); +} + +static struct transport *prepare_transport(struct remote *remote) +{ + struct transport *transport; + transport = transport_get(remote, NULL); + transport_set_verbosity(transport, verbosity, progress); + if (upload_pack) + set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack); + if (keep) + set_option(transport, TRANS_OPT_KEEP, "yes"); + if (depth) + set_option(transport, TRANS_OPT_DEPTH, depth); + return transport; +} + +static void backfill_tags(struct transport *transport, struct ref *ref_map) +{ + if (transport->cannot_reuse) { + gsecondary = prepare_transport(transport->remote); + transport = gsecondary; + } + + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + fetch_refs(transport, ref_map); + + if (gsecondary) { + transport_disconnect(gsecondary); + gsecondary = NULL; + } +} + static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { - struct string_list existing_refs = STRING_LIST_INIT_NODUP; - struct string_list_item *peer_item = NULL; + struct string_list existing_refs = STRING_LIST_INIT_DUP; struct ref *ref_map; struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); + int retcode = 0; for_each_ref(add_existing, &existing_refs); @@ -713,9 +788,9 @@ static int do_fetch(struct transport *transport, /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { - int errcode = truncate_fetch_head(); - if (errcode) - return errcode; + retcode = truncate_fetch_head(); + if (retcode) + goto cleanup; } ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); @@ -724,8 +799,9 @@ static int do_fetch(struct transport *transport, for (rm = ref_map; rm; rm = rm->next) { if (rm->peer_ref) { - peer_item = string_list_lookup(&existing_refs, - rm->peer_ref->name); + struct string_list_item *peer_item = + string_list_lookup(&existing_refs, + rm->peer_ref->name); if (peer_item) hashcpy(rm->peer_ref->old_sha1, peer_item->util); @@ -736,7 +812,8 @@ static int do_fetch(struct transport *transport, transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); if (fetch_refs(transport, ref_map)) { free_refs(ref_map); - return 1; + retcode = 1; + goto cleanup; } if (prune) { /* If --tags was specified, pretend the user gave us the canonical tags refspec */ @@ -771,26 +848,14 @@ static int do_fetch(struct transport *transport, struct ref **tail = &ref_map; ref_map = NULL; find_non_local_tags(transport, &ref_map, &tail); - if (ref_map) { - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); - transport_set_option(transport, TRANS_OPT_DEPTH, "0"); - fetch_refs(transport, ref_map); - } + if (ref_map) + backfill_tags(transport, ref_map); free_refs(ref_map); } - return 0; -} - -static void set_option(const char *name, const char *value) -{ - int r = transport_set_option(transport, name, value); - if (r < 0) - die(_("Option \"%s\" value \"%s\" is not valid for %s"), - name, value, transport->url); - if (r > 0) - warning(_("Option \"%s\" is ignored for %s\n"), - name, transport->url); + cleanup: + string_list_clear(&existing_refs, 1); + return retcode; } static int get_one_remote_for_fetch(struct remote *remote, void *priv) @@ -915,15 +980,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) die(_("No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched.")); - transport = transport_get(remote, NULL); - transport_set_verbosity(transport, verbosity, progress); - if (upload_pack) - set_option(TRANS_OPT_UPLOADPACK, upload_pack); - if (keep) - set_option(TRANS_OPT_KEEP, "yes"); - if (depth) - set_option(TRANS_OPT_DEPTH, depth); - + gtransport = prepare_transport(remote); if (argc > 0) { int j = 0; refs = xcalloc(argc + 1, sizeof(const char *)); @@ -949,10 +1006,10 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); refspec = parse_fetch_refspec(ref_nr, refs); - exit_code = do_fetch(transport, refspec, ref_nr); + exit_code = do_fetch(gtransport, refspec, ref_nr); free_refspec(ref_nr, refspec); - transport_disconnect(transport); - transport = NULL; + transport_disconnect(gtransport); + gtransport = NULL; return exit_code; } diff --git a/builtin/fsck.c b/builtin/fsck.c index bb9a2cd447..9909b6d519 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -112,7 +112,7 @@ static int mark_object(struct object *obj, int type, void *data) return 1; } - add_object_array(obj, (void *) parent, &pending); + add_object_array(obj, NULL, &pending); return 0; } diff --git a/builtin/grep.c b/builtin/grep.c index 159e65d47a..d3b3b1db11 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, struct strbuf pathbuf = STRBUF_INIT; if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, -1, &pathbuf, - opt->prefix); + quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); strbuf_insert(&pathbuf, 0, filename, tree_name_len); } else { strbuf_addstr(&pathbuf, filename); @@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) struct strbuf buf = STRBUF_INIT; if (opt->relative && opt->prefix_length) - quote_path_relative(filename, -1, &buf, opt->prefix); + quote_path_relative(filename, opt->prefix, &buf); else strbuf_addstr(&buf, filename); @@ -376,7 +375,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int read_cache(); for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; + const struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL)) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 79dfe47320..9c1cfac442 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -77,8 +77,10 @@ static int nr_threads; static int from_stdin; static int strict; +static int do_fsck_object; static int verbose; static int show_stat; +static int check_self_contained_and_connected; static struct progress *progress; @@ -187,13 +189,13 @@ static int mark_link(struct object *obj, int type, void *data) /* The content of each linked object must have been checked or it must be already present in the object database */ -static void check_object(struct object *obj) +static unsigned check_object(struct object *obj) { if (!obj) - return; + return 0; if (!(obj->flags & FLAG_LINK)) - return; + return 0; if (!(obj->flags & FLAG_CHECKED)) { unsigned long size; @@ -201,17 +203,20 @@ static void check_object(struct object *obj) if (type != obj->type || type <= 0) die(_("object of unexpected type")); obj->flags |= FLAG_CHECKED; - return; + return 1; } + + return 0; } -static void check_objects(void) +static unsigned check_objects(void) { - unsigned i, max; + unsigned i, max, foreign_nr = 0; max = get_max_object_index(); for (i = 0; i < max; i++) - check_object(get_indexed_object(i)); + foreign_nr += check_object(get_indexed_object(i)); + return foreign_nr; } @@ -747,8 +752,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, int eaten; void *buf = (void *) data; - if (!buf) - buf = new_data = get_data_from_pack(obj_entry); + assert(data && "data can only be NULL for large _blobs_"); /* * we do not need to free the memory here, as the @@ -757,7 +761,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, obj = parse_object_buffer(sha1, type, size, buf, &eaten); if (!obj) die(_("invalid %s"), typename(type)); - if (fsck_object(obj, 1, fsck_error_function)) + if (do_fsck_object && + fsck_object(obj, 1, fsck_error_function)) die(_("Error in object")); if (fsck_walk(obj, mark_link, NULL)) die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1)); @@ -1491,6 +1496,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_sha1[20]; + unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); @@ -1512,6 +1518,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) fix_thin_pack = 1; } else if (!strcmp(arg, "--strict")) { strict = 1; + do_fsck_object = 1; + } else if (!strcmp(arg, "--check-self-contained-and-connected")) { + strict = 1; + check_self_contained_and_connected = 1; } else if (!strcmp(arg, "--verify")) { verify = 1; } else if (!strcmp(arg, "--verify-stat")) { @@ -1625,7 +1635,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) conclude_pack(fix_thin_pack, curr_pack, pack_sha1); free(deltas); if (strict) - check_objects(); + foreign_nr = check_objects(); if (show_stat) show_pack_info(stat_only); @@ -1651,5 +1661,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (index_name == NULL) free((void *) curr_index); + /* + * Let the caller know this pack is not self contained + */ + if (check_self_contained_and_connected && foreign_nr) + return 1; + return 0; } diff --git a/builtin/log.c b/builtin/log.c index 6e56a50002..2625f9881a 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,6 +19,7 @@ #include "remote.h" #include "string-list.h" #include "parse-options.h" +#include "line-log.h" #include "branch.h" #include "streaming.h" #include "version.h" @@ -42,6 +43,12 @@ static const char * const builtin_log_usage[] = { NULL }; +struct line_opt_callback_data { + struct rev_info *rev; + const char *prefix; + struct string_list args; +}; + static int parse_decoration_style(const char *var, const char *value) { switch (git_config_maybe_bool(var, value)) { @@ -76,6 +83,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse return 0; } +static int log_line_range_callback(const struct option *option, const char *arg, int unset) +{ + struct line_opt_callback_data *data = option->value; + + if (!arg) + return -1; + + data->rev->line_level_traverse = 1; + string_list_append(&data->args, arg); + + return 0; +} + static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) @@ -98,6 +118,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, { struct userformat_want w; int quiet = 0, source = 0, mailmap = 0; + static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; const struct option builtin_log_options[] = { OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")), @@ -105,9 +126,15 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, + OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", + "Process line range n,m in file, counting from 1", + log_line_range_callback), OPT_END() }; + line_cb.rev = rev; + line_cb.prefix = prefix; + mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, @@ -161,6 +188,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->show_decorations = 1; load_ref_decorations(decoration_style); } + + if (rev->line_level_traverse) + line_log_init(rev, line_cb.prefix, &line_cb.args); + setup_pager(); } @@ -206,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) int i = revs->early_output; int show_header = 1; - sort_in_topological_order(&list, revs->lifo); + sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; switch (simplify_commit(revs, commit)) { @@ -1081,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } +static int from_callback(const struct option *opt, const char *arg, int unset) +{ + char **from = opt->value; + + free(*from); + + if (unset) + *from = NULL; + else if (arg) + *from = xstrdup(arg); + else + *from = xstrdup(git_committer_info(IDENT_NO_DATE)); + return 0; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1103,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int quiet = 0; int reroll_count = -1; char *branch_name = NULL; + char *from = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), @@ -1146,6 +1193,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) 0, to_callback }, { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), 0, cc_callback }, + { OPTION_CALLBACK, 0, "from", &from, N_("ident"), + N_("set From address to <ident> (or committer ident if absent)"), + PARSE_OPT_OPTARG, from_callback }, OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), N_("make first mail a reply to <message-id>")), { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), @@ -1233,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.extra_headers = strbuf_detach(&buf, NULL); + if (from) { + if (split_ident_line(&rev.from_ident, from, strlen(from))) + die(_("invalid ident line: %s"), from); + } + if (start_number < 0) start_number = 1; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 87f3b331ca..85004460bd 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -46,10 +46,14 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; -static void write_name(const char* name, size_t len) +static void write_name(const char *name) { - write_name_quoted_relative(name, len, prefix, prefix_len, stdout, - line_terminator); + /* + * With "--full-name", prefix_len=0; this caller needs to pass + * an empty string in that case (a NULL is good for ""). + */ + write_name_quoted_relative(name, prefix_len ? prefix : NULL, + stdout, line_terminator); } static void show_dir_entry(const char *tag, struct dir_entry *ent) @@ -63,7 +67,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) return; fputs(tag, stdout); - write_name(ent->name, ent->len); + write_name(ent->name); } static void show_other_files(struct dir_struct *dir) @@ -127,7 +131,7 @@ static void show_killed_files(struct dir_struct *dir) } } -static void show_ce_entry(const char *tag, struct cache_entry *ce) +static void show_ce_entry(const char *tag, const struct cache_entry *ce) { int len = max_prefix_len; @@ -163,13 +167,15 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) find_unique_abbrev(ce->sha1,abbrev), ce_stage(ce)); } - write_name(ce->name, ce_namelen(ce)); + write_name(ce->name); if (debug_mode) { - printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec); - printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec); - printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino); - printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid); - printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags); + const struct stat_data *sd = &ce->ce_stat_data; + + printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); + printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); + printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); + printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); + printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); } } @@ -196,12 +202,12 @@ static void show_ru_info(void) printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], find_unique_abbrev(ui->sha1[i], abbrev), i + 1); - write_name(path, len); + write_name(path); } } } -static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce) +static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce) { int dtype = ce_to_dtype(ce); return is_excluded(dir, ce->name, &dtype); @@ -213,6 +219,8 @@ static void show_files(struct dir_struct *dir) /* For cached/deleted files we don't need to even do the readdir */ if (show_others || show_killed) { + if (!show_others) + dir->flags |= DIR_COLLECT_KILLED_ONLY; fill_directory(dir, pathspec); if (show_others) show_other_files(dir); @@ -221,7 +229,7 @@ static void show_files(struct dir_struct *dir) } if (show_cached || show_stage) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if ((dir->flags & DIR_SHOW_IGNORED) && !ce_excluded(dir, ce)) continue; @@ -235,7 +243,7 @@ static void show_files(struct dir_struct *dir) } if (show_deleted || show_modified) { for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; struct stat st; int err; if ((dir->flags & DIR_SHOW_IGNORED) && @@ -271,7 +279,7 @@ static void prune_cache(const char *prefix) last = active_nr; while (last > first) { int next = (last + first) >> 1; - struct cache_entry *ce = active_cache[next]; + const struct cache_entry *ce = active_cache[next]; if (!strncmp(ce->name, prefix, max_prefix_len)) { first = next+1; continue; @@ -389,7 +397,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char if (found_dup) continue; - name = quote_path_relative(pathspec[num], -1, &sb, prefix); + name = quote_path_relative(pathspec[num], prefix, &sb); error("pathspec '%s' did not match any file(s) known to git.", name); errors++; diff --git a/builtin/merge-index.c b/builtin/merge-index.c index be5e514324..b416d92849 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path) die("git merge-index: %s not in the cache", path); found = 0; do { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; int stage = ce_stage(ce); if (strcmp(ce->name, path)) @@ -58,7 +58,7 @@ static void merge_all(void) { int i; for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) continue; i += merge_entry(i, ce->name)-1; diff --git a/builtin/merge.c b/builtin/merge.c index 2ebe732896..34a6166b52 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = { }; static int show_diffstat = 1, shortlog_len = -1, squash; -static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only, option_edit = -1; +static int option_commit = 1; +static int option_edit = -1; static int allow_trivial = 1, have_message, verify_signatures; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; @@ -76,6 +76,14 @@ static struct strategy all_strategy[] = { static const char *pull_twohead, *pull_octopus; +enum ff_type { + FF_NO, + FF_ALLOW, + FF_ONLY +}; + +static enum ff_type fast_forward = FF_ALLOW; + static int option_parse_message(const struct option *opt, const char *arg, int unset) { @@ -178,6 +186,13 @@ static int option_parse_n(const struct option *opt, return 0; } +static int option_parse_ff_only(const struct option *opt, + const char *arg, int unset) +{ + fast_forward = FF_ONLY; + return 0; +} + static struct option builtin_merge_options[] = { { OPTION_CALLBACK, 'n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), @@ -194,10 +209,10 @@ static struct option builtin_merge_options[] = { N_("perform a commit if the merge succeeds (default)")), OPT_BOOL('e', "edit", &option_edit, N_("edit message before committing")), - OPT_BOOLEAN(0, "ff", &allow_fast_forward, - N_("allow fast-forward (default)")), - OPT_BOOLEAN(0, "ff-only", &fast_forward_only, - N_("abort if fast-forward is not possible")), + OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW), + { OPTION_CALLBACK, 0, "ff-only", NULL, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_ff_only }, OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, N_("Verify that the named commit has a valid GPG signature")), @@ -581,10 +596,9 @@ static int git_merge_config(const char *k, const char *v, void *cb) else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { - allow_fast_forward = boolval; + fast_forward = boolval ? FF_ALLOW : FF_NO; } else if (v && !strcmp(v, "only")) { - allow_fast_forward = 1; - fast_forward_only = 1; + fast_forward = FF_ONLY; } /* do not barf on values from future versions of git */ return 0; } else if (!strcmp(k, "merge.defaulttoupstream")) { @@ -863,7 +877,7 @@ static int finish_automerge(struct commit *head, free_commit_list(common); parents = remoteheads; - if (!head_subsumed || !allow_fast_forward) + if (!head_subsumed || fast_forward == FF_NO) commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); @@ -889,7 +903,7 @@ static int suggest_conflicts(int renormalizing) die_errno(_("Could not open '%s' for writing"), filename); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce)) { fprintf(fp, "\t%s\n", ce->name); @@ -948,7 +962,7 @@ static int evaluate_result(void) } /* - * Pretend as if the user told us to merge with the tracking + * Pretend as if the user told us to merge with the remote-tracking * branch we have for the upstream of the current branch */ static int setup_with_upstream(const char ***argv) @@ -967,7 +981,7 @@ static int setup_with_upstream(const char ***argv) args = xcalloc(branch->merge_nr + 1, sizeof(char *)); for (i = 0; i < branch->merge_nr; i++) { if (!branch->merge[i]->dst) - die(_("No remote tracking branch for %s from %s"), + die(_("No remote-tracking branch for %s from %s"), branch->merge[i]->src, branch->remote_name); args[i] = branch->merge[i]->dst; } @@ -1008,7 +1022,7 @@ static void write_merge_state(struct commit_list *remoteheads) if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); strbuf_reset(&buf); - if (!allow_fast_forward) + if (fast_forward == FF_NO) strbuf_addf(&buf, "no-ff"); if (write_in_full(fd, buf.buf, buf.len) != buf.len) die_errno(_("Could not write to '%s'"), filename); @@ -1157,14 +1171,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) show_diffstat = 0; if (squash) { - if (!allow_fast_forward) + if (fast_forward == FF_NO) die(_("You cannot combine --squash with --no-ff.")); option_commit = 0; } - if (!allow_fast_forward && fast_forward_only) - die(_("You cannot combine --no-ff with --ff-only.")); - if (!abort_current_merge) { if (!argc) { if (default_to_upstream) @@ -1206,7 +1217,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) "empty head")); if (squash) die(_("Squash commit into empty head not supported yet")); - if (!allow_fast_forward) + if (fast_forward == FF_NO) die(_("Non-fast-forward commit does not make sense into " "an empty head")); remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); @@ -1294,11 +1305,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) sha1_to_hex(commit->object.sha1)); setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); - if (!fast_forward_only && + if (fast_forward != FF_ONLY && merge_remote_util(commit) && merge_remote_util(commit)->obj && merge_remote_util(commit)->obj->type == OBJ_TAG) - allow_fast_forward = 0; + fast_forward = FF_NO; } if (option_edit < 0) @@ -1315,7 +1326,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < use_strategies_nr; i++) { if (use_strategies[i]->attr & NO_FAST_FORWARD) - allow_fast_forward = 0; + fast_forward = FF_NO; if (use_strategies[i]->attr & NO_TRIVIAL) allow_trivial = 0; } @@ -1345,7 +1356,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) */ finish_up_to_date("Already up-to-date."); goto done; - } else if (allow_fast_forward && !remoteheads->next && + } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ @@ -1392,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * only one common. */ refresh_cache(REFRESH_QUIET); - if (allow_trivial && !fast_forward_only) { + if (allow_trivial && fast_forward != FF_ONLY) { /* See if it is really trivial. */ git_committer_info(IDENT_STRICT); printf(_("Trying really trivial in-index merge...\n")); @@ -1433,7 +1444,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } - if (fast_forward_only) + if (fast_forward == FF_ONLY) die(_("Not possible to fast-forward, aborting.")); /* We are going to make a new commit. */ diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 6238247974..0aaa19e4ab 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -4,6 +4,7 @@ #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "sha1-lookup.h" #define CUTOFF_DATE_SLOP 86400 /* one day */ @@ -82,23 +83,88 @@ copy_data: } } +static int subpath_matches(const char *path, const char *filter) +{ + const char *subpath = path; + + while (subpath) { + if (!fnmatch(filter, subpath, 0)) + return subpath - path; + subpath = strchr(subpath, '/'); + if (subpath) + subpath++; + } + return -1; +} + +static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) +{ + if (shorten_unambiguous) + refname = shorten_unambiguous_ref(refname, 0); + else if (!prefixcmp(refname, "refs/heads/")) + refname = refname + 11; + else if (!prefixcmp(refname, "refs/")) + refname = refname + 5; + return refname; +} + struct name_ref_data { int tags_only; int name_only; const char *ref_filter; }; +static struct tip_table { + struct tip_table_entry { + unsigned char sha1[20]; + const char *refname; + } *table; + int nr; + int alloc; + int sorted; +} tip_table; + +static void add_to_tip_table(const unsigned char *sha1, const char *refname, + int shorten_unambiguous) +{ + refname = name_ref_abbrev(refname, shorten_unambiguous); + + ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); + hashcpy(tip_table.table[tip_table.nr].sha1, sha1); + tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.nr++; + tip_table.sorted = 0; +} + +static int tipcmp(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + return hashcmp(a->sha1, b->sha1); +} + static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct object *o = parse_object(sha1); struct name_ref_data *data = cb_data; + int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; if (data->tags_only && prefixcmp(path, "refs/tags/")) return 0; - if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) - return 0; + if (data->ref_filter) { + switch (subpath_matches(path, data->ref_filter)) { + case -1: /* did not match */ + return 0; + case 0: /* matched fully */ + break; + default: /* matched subpath */ + can_abbreviate_output = 1; + break; + } + } + + add_to_tip_table(sha1, path, can_abbreviate_output); while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; @@ -110,20 +176,38 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - if (!prefixcmp(path, "refs/heads/")) - path = path + 11; - else if (data->tags_only - && data->name_only - && !prefixcmp(path, "refs/tags/")) - path = path + 10; - else if (!prefixcmp(path, "refs/")) - path = path + 5; - + path = name_ref_abbrev(path, can_abbreviate_output); name_rev(commit, xstrdup(path), 0, 0, deref); } return 0; } +static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +{ + struct tip_table_entry *table = table_; + return table[ix].sha1; +} + +static const char *get_exact_ref_match(const struct object *o) +{ + int found; + + if (!tip_table.table || !tip_table.nr) + return NULL; + + if (!tip_table.sorted) { + qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table), + tipcmp); + tip_table.sorted = 1; + } + + found = sha1_pos(o->sha1, tip_table.table, tip_table.nr, + nth_tip_table_ent); + if (0 <= found) + return tip_table.table[found].refname; + return NULL; +} + /* returns a static buffer */ static const char *get_rev_name(const struct object *o) { @@ -132,7 +216,7 @@ static const char *get_rev_name(const struct object *o) struct commit *c; if (o->type != OBJ_COMMIT) - return NULL; + return get_exact_ref_match(o); c = (struct commit *) o; n = c->util; if (!n) @@ -223,7 +307,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; + int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), @@ -236,6 +320,12 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")), OPT_BOOLEAN(0, "always", &always, N_("show abbreviated commit object as fallback")), + { + /* A Hidden OPT_BOOL */ + OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL, + N_("dereference tags in the input (internal use)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1, + }, OPT_END(), }; @@ -250,7 +340,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) for (; argc; argc--, argv++) { unsigned char sha1[20]; - struct object *o; + struct object *object; struct commit *commit; if (get_sha1(*argv, sha1)) { @@ -259,17 +349,34 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) continue; } - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", + commit = NULL; + object = parse_object(sha1); + if (object) { + struct object *peeled = deref_tag(object, *argv, 0); + if (peeled && peeled->type == OBJ_COMMIT) + commit = (struct commit *)peeled; + } + + if (!object) { + fprintf(stderr, "Could not get object for %s. Skipping.\n", *argv); continue; } - commit = (struct commit *)o; - if (cutoff > commit->date) - cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); + if (commit) { + if (cutoff > commit->date) + cutoff = commit->date; + } + + if (peel_tag) { + if (!commit) { + fprintf(stderr, "Could not get commit for %s. Skipping.\n", + *argv); + continue; + } + object = (struct object *)commit; + } + add_object_array(object, *argv, &revs); } if (cutoff) diff --git a/builtin/notes.c b/builtin/notes.c index d9a67d9de4..e4100c4982 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -18,9 +18,7 @@ #include "parse-options.h" #include "string-list.h" #include "notes-merge.h" - -static void commit_notes(struct notes_tree *t, const char *msg); -static combine_notes_fn parse_combine_notes_fn(const char *v); +#include "notes-utils.h" static const char * const git_notes_usage[] = { N_("git notes [--ref <notes_ref>] [list [<object>]]"), @@ -287,139 +285,13 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset return parse_reuse_arg(opt, arg, unset); } -static void commit_notes(struct notes_tree *t, const char *msg) -{ - struct strbuf buf = STRBUF_INIT; - unsigned char commit_sha1[20]; - - if (!t) - t = &default_notes_tree; - if (!t->initialized || !t->ref || !*t->ref) - die(_("Cannot commit uninitialized/unreferenced notes tree")); - if (!t->dirty) - return; /* don't have to commit an unchanged tree */ - - /* Prepare commit message and reflog message */ - strbuf_addstr(&buf, msg); - if (buf.buf[buf.len - 1] != '\n') - strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - - create_notes_commit(t, NULL, &buf, commit_sha1); - strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ - update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); - - strbuf_release(&buf); -} - -static combine_notes_fn parse_combine_notes_fn(const char *v) -{ - if (!strcasecmp(v, "overwrite")) - return combine_notes_overwrite; - else if (!strcasecmp(v, "ignore")) - return combine_notes_ignore; - else if (!strcasecmp(v, "concatenate")) - return combine_notes_concatenate; - else if (!strcasecmp(v, "cat_sort_uniq")) - return combine_notes_cat_sort_uniq; - else - return NULL; -} - -static int notes_rewrite_config(const char *k, const char *v, void *cb) -{ - struct notes_rewrite_cfg *c = cb; - if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) { - c->enabled = git_config_bool(k, v); - return 0; - } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) { - if (!v) - config_error_nonbool(k); - c->combine = parse_combine_notes_fn(v); - if (!c->combine) { - error(_("Bad notes.rewriteMode value: '%s'"), v); - return 1; - } - return 0; - } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) { - /* note that a refs/ prefix is implied in the - * underlying for_each_glob_ref */ - if (!prefixcmp(v, "refs/notes/")) - string_list_add_refs_by_glob(c->refs, v); - else - warning(_("Refusing to rewrite notes in %s" - " (outside of refs/notes/)"), v); - return 0; - } - - return 0; -} - - -struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) -{ - struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg)); - const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT); - const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT); - c->cmd = cmd; - c->enabled = 1; - c->combine = combine_notes_concatenate; - c->refs = xcalloc(1, sizeof(struct string_list)); - c->refs->strdup_strings = 1; - c->refs_from_env = 0; - c->mode_from_env = 0; - if (rewrite_mode_env) { - c->mode_from_env = 1; - c->combine = parse_combine_notes_fn(rewrite_mode_env); - if (!c->combine) - /* TRANSLATORS: The first %s is the name of the - environment variable, the second %s is its value */ - error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT, - rewrite_mode_env); - } - if (rewrite_refs_env) { - c->refs_from_env = 1; - string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env); - } - git_config(notes_rewrite_config, c); - if (!c->enabled || !c->refs->nr) { - string_list_clear(c->refs, 0); - free(c->refs); - free(c); - return NULL; - } - c->trees = load_notes_trees(c->refs); - string_list_clear(c->refs, 0); - free(c->refs); - return c; -} - -int copy_note_for_rewrite(struct notes_rewrite_cfg *c, - const unsigned char *from_obj, const unsigned char *to_obj) -{ - int ret = 0; - int i; - for (i = 0; c->trees[i]; i++) - ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret; - return ret; -} - -void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c) -{ - int i; - for (i = 0; c->trees[i]; i++) { - commit_notes(c->trees[i], "Notes added by 'git notes copy'"); - free_notes(c->trees[i]); - } - free(c->trees); - free(c); -} - static int notes_copy_from_stdin(int force, const char *rewrite_cmd) { struct strbuf buf = STRBUF_INIT; struct notes_rewrite_cfg *c = NULL; struct notes_tree *t = NULL; int ret = 0; + const char *msg = "Notes added by 'git notes copy'"; if (rewrite_cmd) { c = init_copy_notes_for_rewrite(rewrite_cmd); @@ -461,10 +333,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } if (!rewrite_cmd) { - commit_notes(t, "Notes added by 'git notes copy'"); + commit_notes(t, msg); free_notes(t); } else { - finish_copy_notes_for_rewrite(c); + finish_copy_notes_for_rewrite(c, msg); } return ret; } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f069462cb0..dd117b379a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2378,7 +2378,7 @@ static void get_object_list(int ac, const char **av) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); - mark_edges_uninteresting(revs.commits, &revs, show_edge); + mark_edges_uninteresting(&revs, show_edge); traverse_commit_list(&revs, show_commit, show_object, NULL); if (keep_unreachable) diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index b5a0f88eb8..b20b1ec4c1 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,6 +1,6 @@ #include "builtin.h" #include "parse-options.h" -#include "pack-refs.h" +#include "refs.h" static char const * const pack_refs_usage[] = { N_("git pack-refs [options]"), diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index 83382c1fe1..fa6ce42f44 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -8,9 +8,6 @@ static const char * const prune_packed_usage[] = { NULL }; -#define DRY_RUN 01 -#define VERBOSE 02 - static struct progress *progress; static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) @@ -29,7 +26,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) if (!has_sha1_pack(sha1)) continue; memcpy(pathname + len, de->d_name, 38); - if (opts & DRY_RUN) + if (opts & PRUNE_PACKED_DRY_RUN) printf("rm -f %s\n", pathname); else unlink_or_warn(pathname); @@ -44,7 +41,7 @@ void prune_packed_objects(int opts) const char *dir = get_object_directory(); int len = strlen(dir); - if (opts == VERBOSE) + if (opts & PRUNE_PACKED_VERBOSE) progress = start_progress_delay("Removing duplicate objects", 256, 95, 2); @@ -71,10 +68,12 @@ void prune_packed_objects(int opts) int cmd_prune_packed(int argc, const char **argv, const char *prefix) { - int opts = isatty(2) ? VERBOSE : 0; + int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0; const struct option prune_packed_options[] = { - OPT_BIT('n', "dry-run", &opts, N_("dry run"), DRY_RUN), - OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), VERBOSE), + OPT_BIT('n', "dry-run", &opts, N_("dry run"), + PRUNE_PACKED_DRY_RUN), + OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), + PRUNE_PACKED_VERBOSE), OPT_END() }; diff --git a/builtin/prune.c b/builtin/prune.c index 85843d4f17..6366917c6d 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -132,8 +132,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix) 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_DATE(0, "expire", &expire, - N_("expire objects older than <time>")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("expire objects older than <time>")), OPT_END() }; char *s; @@ -165,7 +165,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) stop_progress(&progress); prune_object_dir(get_object_directory()); - prune_packed_objects(show_only); + prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0); remove_temporary_files(get_object_directory()); s = mkpathdup("%s/pack", get_object_directory()); remove_temporary_files(s); diff --git a/builtin/push.c b/builtin/push.c index 2d84d10720..04f0eaf179 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -92,7 +92,7 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote if (!short_upstream) short_upstream = branch->merge[0]->src; /* - * Don't show advice for people who explicitely set + * Don't show advice for people who explicitly set * push.default. */ if (push_default == PUSH_DEFAULT_UNSPECIFIED) @@ -120,10 +120,11 @@ static const char message_detached_head_die[] = "\n" " git push %s HEAD:<name-of-remote-branch>\n"); -static void setup_push_upstream(struct remote *remote, int simple) +static void setup_push_upstream(struct remote *remote, struct branch *branch, + int triangular) { struct strbuf refspec = STRBUF_INIT; - struct branch *branch = branch_get(NULL); + if (!branch) die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) @@ -137,18 +138,29 @@ static void setup_push_upstream(struct remote *remote, int simple) if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); - if (strcmp(branch->remote_name, remote->name)) + if (triangular) die(_("You are pushing to remote '%s', which is not the upstream of\n" "your current branch '%s', without telling me what to push\n" "to update which remote branch."), remote->name, branch->name); - if (simple && strcmp(branch->refname, branch->merge[0]->src)) - die_push_simple(branch, remote); + + if (push_default == PUSH_DEFAULT_SIMPLE) { + /* Additional safety */ + if (strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); + } strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); } +static void setup_push_current(struct remote *remote, struct branch *branch) +{ + if (!branch) + die(_(message_detached_head_die), remote->name); + add_refspec(branch->name); +} + static char warn_unspecified_push_default_msg[] = N_("push.default is unset; its implicit value is changing in\n" "Git 2.0 from 'matching' to 'simple'. To squelch this message\n" @@ -173,9 +185,16 @@ static void warn_unspecified_push_default_configuration(void) warning("%s\n", _(warn_unspecified_push_default_msg)); } +static int is_workflow_triangular(struct remote *remote) +{ + struct remote *fetch_remote = remote_get(NULL); + return (fetch_remote && fetch_remote != remote); +} + static void setup_default_push_refspecs(struct remote *remote) { - struct branch *branch; + struct branch *branch = branch_get(NULL); + int triangular = is_workflow_triangular(remote); switch (push_default) { default: @@ -188,18 +207,18 @@ static void setup_default_push_refspecs(struct remote *remote) break; case PUSH_DEFAULT_SIMPLE: - setup_push_upstream(remote, 1); + if (triangular) + setup_push_current(remote, branch); + else + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote, 0); + setup_push_upstream(remote, branch, triangular); break; case PUSH_DEFAULT_CURRENT: - branch = branch_get(NULL); - if (!branch) - die(_(message_detached_head_die), remote->name); - add_refspec(branch->name); + setup_push_current(remote, branch); break; case PUSH_DEFAULT_NOTHING: @@ -211,8 +230,8 @@ static void setup_default_push_refspecs(struct remote *remote) static const char message_advice_pull_before_push[] = N_("Updates were rejected because the tip of your current branch is behind\n" - "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n" - "before pushing again.\n" + "its remote counterpart. Integrate the remote changes (e.g.\n" + "'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_use_upstream[] = @@ -223,15 +242,15 @@ static const char message_advice_use_upstream[] = static const char message_advice_checkout_pull_push[] = N_("Updates were rejected because a pushed branch tip is behind its remote\n" - "counterpart. Check out this branch and merge the remote changes\n" - "(e.g. 'git pull') before pushing again.\n" + "counterpart. Check out this branch and integrate the remote changes\n" + "(e.g. 'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_ref_fetch_first[] = N_("Updates were rejected because the remote contains work that you do\n" "not have locally. This is usually caused by another repository pushing\n" - "to the same ref. You may want to first merge the remote changes (e.g.,\n" - "'git pull') before pushing again.\n" + "to the same ref. You may want to first integrate the remote changes\n" + "(e.g., 'git pull ...') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); static const char message_advice_ref_already_exists[] = diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 042ac1b84f..0f5d7fe23f 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -66,7 +66,7 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg, return 0; } -static void debug_stage(const char *label, struct cache_entry *ce, +static void debug_stage(const char *label, const struct cache_entry *ce, struct unpack_trees_options *o) { printf("%s ", label); @@ -80,7 +80,8 @@ static void debug_stage(const char *label, struct cache_entry *ce, sha1_to_hex(ce->sha1)); } -static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o) +static int debug_merge(const struct cache_entry * const *stages, + struct unpack_trees_options *o) { int i; diff --git a/builtin/reflog.c b/builtin/reflog.c index 72a0af70c3..54184b3d13 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -496,11 +496,9 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l { if (!value) return config_error_nonbool(var); - if (!strcmp(value, "never") || !strcmp(value, "false")) { - *expire = 0; - return 0; - } - *expire = approxidate(value); + if (parse_expiry_date(value, expire)) + return error(_("%s' for '%s' is not a valid timestamp"), + value, var); return 0; } @@ -614,11 +612,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) cb.dry_run = 1; else if (!prefixcmp(arg, "--expire=")) { - cb.expire_total = approxidate(arg + 9); + if (parse_expiry_date(arg + 9, &cb.expire_total)) + die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } else if (!prefixcmp(arg, "--expire-unreachable=")) { - cb.expire_unreachable = approxidate(arg + 21); + if (parse_expiry_date(arg + 21, &cb.expire_unreachable)) + die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } else if (!strcmp(arg, "--stale-fix")) diff --git a/builtin/reset.c b/builtin/reset.c index 6032131a90..afa6e020e8 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -93,10 +93,12 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet) static void print_new_head_line(struct commit *commit) { const char *hex, *body; + char *msg; hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); printf(_("HEAD is now at %s"), hex); - body = strstr(commit->buffer, "\n\n"); + msg = logmsg_reencode(commit, NULL, get_log_output_encoding()); + body = strstr(msg, "\n\n"); if (body) { const char *eol; size_t len; @@ -107,6 +109,7 @@ static void print_new_head_line(struct commit *commit) } else printf("\n"); + logmsg_free(msg, commit); } static void update_index_from_diff(struct diff_queue_struct *q, diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 67701be551..4fc1616637 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -111,6 +111,7 @@ static void show_commit(struct commit *commit, void *data) ctx.date_mode = revs->date_mode; ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; + ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &buf); if (revs->graph) { if (buf.len) { @@ -335,7 +336,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); if (revs.tree_objects) - mark_edges_uninteresting(revs.commits, &revs, show_edge); + mark_edges_uninteresting(&revs, show_edge); if (bisect_list) { int reaches = reaches, all = all; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index f267a1d3b5..de894c7577 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -212,11 +212,17 @@ static void show_datestring(const char *flag, const char *datestr) show(buffer); } -static int show_file(const char *arg) +static int show_file(const char *arg, int output_prefix) { show_default(); if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { - show(arg); + if (output_prefix) { + const char *prefix = startup_info->prefix; + show(prefix_filename(prefix, + prefix ? strlen(prefix) : 0, + arg)); + } else + show(arg); return 1; } return 0; @@ -470,6 +476,7 @@ N_("git rev-parse --parseopt [options] -- [<args>...]\n" int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; + int output_prefix = 0; unsigned char sha1[20]; const char *name = NULL; @@ -503,7 +510,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (as_is) { - if (show_file(arg) && as_is < 2) + if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); continue; } @@ -527,7 +534,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) as_is = 2; /* Pass on the "--" if we show anything but files.. */ if (filter & (DO_FLAGS | DO_REVS)) - show_file(arg); + show_file(arg, 0); continue; } if (!strcmp(arg, "--default")) { @@ -535,6 +542,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) i++; continue; } + if (!strcmp(arg, "--prefix")) { + prefix = argv[i+1]; + startup_info->prefix = prefix; + output_prefix = 1; + i++; + continue; + } if (!strcmp(arg, "--revs-only")) { filter &= ~DO_NOREV; continue; @@ -754,7 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (verify) die_no_single_rev(quiet); as_is = 1; - if (!show_file(arg)) + if (!show_file(arg, output_prefix)) continue; verify_filename(prefix, arg, 1); } diff --git a/builtin/revert.c b/builtin/revert.c index 0401fdb02c..1d2648b756 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -54,6 +54,7 @@ static int option_parse_x(const struct option *opt, return 0; } +LAST_ARG_MUST_BE_NULL static void verify_opt_compatible(const char *me, const char *base_opt, ...) { const char *this_opt; @@ -70,6 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } +LAST_ARG_MUST_BE_NULL static void verify_opt_mutually_compatible(const char *me, ...) { const char *opt1, *opt2 = NULL; diff --git a/builtin/rm.c b/builtin/rm.c index 7b91d52f39..0df0b4d942 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -9,6 +9,7 @@ #include "cache-tree.h" #include "tree-walk.h" #include "parse-options.h" +#include "string-list.h" #include "submodule.h" static const char * const builtin_rm_usage[] = { @@ -36,15 +37,52 @@ static int get_ours_cache_pos(const char *path, int pos) return -1; } +static void print_error_files(struct string_list *files_list, + const char *main_msg, + const char *hints_msg, + int *errs) +{ + if (files_list->nr) { + int i; + struct strbuf err_msg = STRBUF_INIT; + + strbuf_addstr(&err_msg, main_msg); + for (i = 0; i < files_list->nr; i++) + strbuf_addf(&err_msg, + "\n %s", + files_list->items[i].string); + if (advice_rm_hints) + strbuf_addstr(&err_msg, hints_msg); + *errs = error("%s", err_msg.buf); + strbuf_release(&err_msg); + } +} + +static void error_removing_concrete_submodules(struct string_list *files, int *errs) +{ + print_error_files(files, + Q_("the following submodule (or one of its nested " + "submodules)\n" + "uses a .git directory:", + "the following submodules (or one of its nested " + "submodules)\n" + "use a .git directory:", files->nr), + _("\n(use 'rm -rf' if you really want to remove " + "it including all of its history)"), + errs); + string_list_clear(files, 0); +} + static int check_submodules_use_gitfiles(void) { int i; int errs = 0; + struct string_list files = STRING_LIST_INIT_NODUP; for (i = 0; i < list.nr; i++) { const char *name = list.entry[i].name; int pos; - struct cache_entry *ce; + const struct cache_entry *ce; struct stat st; pos = cache_name_pos(name, strlen(name)); @@ -61,12 +99,11 @@ static int check_submodules_use_gitfiles(void) continue; if (!submodule_uses_gitfile(name)) - errs = error(_("submodule '%s' (or one of its nested " - "submodules) uses a .git directory\n" - "(use 'rm -rf' if you really want to remove " - "it including all of its history)"), name); + string_list_append(&files, name); } + error_removing_concrete_submodules(&files, &errs); + return errs; } @@ -81,12 +118,16 @@ static int check_local_mod(unsigned char *head, int index_only) */ int i, no_head; int errs = 0; + struct string_list files_staged = STRING_LIST_INIT_NODUP; + struct string_list files_cached = STRING_LIST_INIT_NODUP; + struct string_list files_submodule = STRING_LIST_INIT_NODUP; + struct string_list files_local = STRING_LIST_INIT_NODUP; no_head = is_null_sha1(head); for (i = 0; i < list.nr; i++) { struct stat st; int pos; - struct cache_entry *ce; + const struct cache_entry *ce; const char *name = list.entry[i].name; unsigned char sha1[20]; unsigned mode; @@ -171,29 +212,50 @@ static int check_local_mod(unsigned char *head, int index_only) */ if (local_changes && staged_changes) { if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD)) - errs = error(_("'%s' has staged content different " - "from both the file and the HEAD\n" - "(use -f to force removal)"), name); + string_list_append(&files_staged, name); } else if (!index_only) { if (staged_changes) - errs = error(_("'%s' has changes staged in the index\n" - "(use --cached to keep the file, " - "or -f to force removal)"), name); + string_list_append(&files_cached, name); if (local_changes) { if (S_ISGITLINK(ce->ce_mode) && - !submodule_uses_gitfile(name)) { - errs = error(_("submodule '%s' (or one of its nested " - "submodules) uses a .git directory\n" - "(use 'rm -rf' if you really want to remove " - "it including all of its history)"), name); - } else - errs = error(_("'%s' has local modifications\n" - "(use --cached to keep the file, " - "or -f to force removal)"), name); + !submodule_uses_gitfile(name)) + string_list_append(&files_submodule, name); + else + string_list_append(&files_local, name); } } } + print_error_files(&files_staged, + Q_("the following file has staged content different " + "from both the\nfile and the HEAD:", + "the following files have staged content different" + " from both the\nfile and the HEAD:", + files_staged.nr), + _("\n(use -f to force removal)"), + &errs); + string_list_clear(&files_staged, 0); + print_error_files(&files_cached, + Q_("the following file has changes " + "staged in the index:", + "the following files have changes " + "staged in the index:", files_cached.nr), + _("\n(use --cached to keep the file," + " or -f to force removal)"), + &errs); + string_list_clear(&files_cached, 0); + + error_removing_concrete_submodules(&files_submodule, &errs); + + print_error_files(&files_local, + Q_("the following file has local modifications:", + "the following files have local modifications:", + files_local.nr), + _("\n(use --cached to keep the file," + " or -f to force removal)"), + &errs); + string_list_clear(&files_local, 0); + return errs; } @@ -258,7 +320,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) seen = xcalloc(i, 1); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + const struct cache_entry *ce = active_cache[i]; if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) continue; ALLOC_GROW(list.entry, list.nr + 1, list.alloc); diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 1fd6f8ac59..84ffbb798e 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -127,9 +127,11 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) author = buffer + 7; buffer = eol; } - if (!author) - die(_("Missing author: %s"), + if (!author) { + warning(_("Missing author: %s"), sha1_to_hex(commit->object.sha1)); + return; + } if (log->user_format) { struct pretty_print_context ctx = {0}; ctx.fmt = CMIT_FMT_USERFORMAT; @@ -137,6 +139,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.subject = ""; ctx.after_subject = ""; ctx.date_mode = DATE_NORMAL; + ctx.output_encoding = get_log_output_encoding(); pretty_print_commit(&ctx, commit, &ufbuf); buffer = ufbuf.buf; } else if (*buffer) { diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 90fc6b1b9d..9788eb115b 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int num_rev, i, extra = 0; int all_heads = 0, all_remotes = 0; int all_mask, all_revs; - int lifo = 1; + enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER; char head[128]; const char *head_p; int head_len; @@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) N_("show possible merge bases")), OPT_BOOLEAN(0, "independent", &independent, N_("show refs unreachable from any other ref")), - OPT_BOOLEAN(0, "topo-order", &lifo, - N_("show commits in topological order")), + OPT_SET_INT(0, "topo-order", &sort_order, + N_("show commits in topological order"), + REV_SORT_IN_GRAPH_ORDER), OPT_BOOLEAN(0, "topics", &topics, N_("show only commits not on the first branch")), OPT_SET_INT(0, "sparse", &dense, N_("show merges reachable from only one tip"), 0), - OPT_SET_INT(0, "date-order", &lifo, - N_("show commits where no parent comes before its " - "children"), 0), + OPT_SET_INT(0, "date-order", &sort_order, + N_("topologically sort, maintaining date order " + "where possible"), + REV_SORT_BY_COMMIT_DATE), { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"), N_("show <n> most recent ref-log entries starting at " "base"), @@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) exit(0); /* Sort topologically */ - sort_in_topological_order(&seen, lifo); + sort_in_topological_order(&seen, sort_order); /* Give names to commits */ if (!sha1_name && !no_name) diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 8d9b76a02f..87806ad5b0 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -31,6 +31,9 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo const char *hex; unsigned char peeled[20]; + if (show_head && !strcmp(refname, "HEAD")) + goto match; + if (tags_only || heads_only) { int match; @@ -103,7 +106,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag */ static int exclude_existing(const char *match) { - static struct string_list existing_refs = STRING_LIST_INIT_NODUP; + static struct string_list existing_refs = STRING_LIST_INIT_DUP; char buf[1024]; int matchlen = match ? strlen(match) : 0; @@ -167,9 +170,10 @@ static const struct option show_ref_options[] = { OPT_BOOLEAN(0, "verify", &verify, N_("stricter reference checking, " "requires exact ref path")), { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL, - N_("show the HEAD reference"), + N_("show the HEAD reference, even if it would be filtered out"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_BOOLEAN(0, "head", &show_head, N_("show the HEAD reference")), + OPT_BOOLEAN(0, "head", &show_head, + N_("show the HEAD reference, even if it would be filtered out")), OPT_BOOLEAN('d', "dereference", &deref_tags, N_("dereference tags into object IDs")), { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"), diff --git a/builtin/update-index.c b/builtin/update-index.c index 5c7762eef4..c317981516 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -83,7 +83,7 @@ static int process_lstat_error(const char *path, int err) return error("lstat(\"%s\"): %s", path, strerror(errno)); } -static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st) +static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) { int option, size; struct cache_entry *ce; @@ -142,7 +142,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Exact match: file or existing gitlink */ if (pos >= 0) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ @@ -158,7 +158,7 @@ static int process_directory(const char *path, int len, struct stat *st) /* Inexact match: is there perhaps a subdirectory match? */ pos = -pos-1; while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos++]; + const struct cache_entry *ce = active_cache[pos++]; if (strncmp(ce->name, path, len)) break; @@ -183,7 +183,7 @@ static int process_path(const char *path) { int pos, len; struct stat st; - struct cache_entry *ce; + const struct cache_entry *ce; len = strlen(path); if (has_symlink_leading_path(path, len)) @@ -448,7 +448,7 @@ static int unresolve_one(const char *path) /* already merged */ pos = unmerge_cache_entry_at(pos); if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_stage(ce) && ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) @@ -462,7 +462,7 @@ static int unresolve_one(const char *path) */ pos = -pos-1; if (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; if (ce_namelen(ce) == namelen && !memcmp(ce->name, path, namelen)) { fprintf(stderr, @@ -558,7 +558,7 @@ static int do_reupdate(int ac, const char **av, has_head = 0; redo: for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; + const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; int save_nr; |