diff --git a/Documentation/git-name-rev.adoc b/Documentation/git-name-rev.adoc index d4f1c4d594..65348690c8 100644 --- a/Documentation/git-name-rev.adoc +++ b/Documentation/git-name-rev.adoc @@ -9,7 +9,7 @@ git-name-rev - Find symbolic names for given revs SYNOPSIS -------- [verse] -'git name-rev' [--tags] [--refs=] +'git name-rev' [--tags] [--refs=] [--format=] ( --all | --annotate-stdin | ... ) DESCRIPTION @@ -21,6 +21,14 @@ format parsable by 'git rev-parse'. OPTIONS ------- +--format=:: +--no-format:: + Format revisions instead of outputting symbolic names. The + default is `--no-format`. ++ +Implies `--name-only`. The negation `--no-format` implies +`--no-name-only` (the default for the command). + --tags:: Do not use branch names, but only tags to name the commits diff --git a/builtin/name-rev.c b/builtin/name-rev.c index d6594ada53..058eafb78d 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -17,6 +17,9 @@ #include "commit-graph.h" #include "wildmatch.h" #include "mem-pool.h" +#include "pretty.h" +#include "revision.h" +#include "notes.h" /* * One day. See the 'name a rev shortly after epoch' test in t6120 when @@ -32,6 +35,16 @@ struct rev_name { int from_tag; }; +struct pretty_format { + struct pretty_print_context ctx; + struct userformat_want want; +}; + +struct format_cb_data { + const char *format; + int *name_only; +}; + define_commit_slab(commit_rev_name, struct rev_name); static timestamp_t generation_cutoff = GENERATION_NUMBER_INFINITY; @@ -452,7 +465,9 @@ static const char *get_exact_ref_match(const struct object *o) } /* may return a constant string or use "buf" as scratch space */ -static const char *get_rev_name(const struct object *o, struct strbuf *buf) +static const char *get_rev_name(const struct object *o, + struct pretty_format *format_ctx, + struct strbuf *buf) { struct rev_name *n; const struct commit *c; @@ -460,13 +475,32 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) if (o->type != OBJ_COMMIT) return get_exact_ref_match(o); c = (const struct commit *) o; + + if (format_ctx) { + strbuf_reset(buf); + + if (format_ctx->want.notes) { + struct strbuf notebuf = STRBUF_INIT; + + format_display_notes(&c->object.oid, ¬ebuf, + get_log_output_encoding(), + format_ctx->ctx.fmt == CMIT_FMT_USERFORMAT); + format_ctx->ctx.notes_message = strbuf_detach(¬ebuf, NULL); + } + + pretty_print_commit(&format_ctx->ctx, c, buf); + FREE_AND_NULL(format_ctx->ctx.notes_message); + + return buf->buf; + } + n = get_commit_rev_name(c); if (!n) return NULL; - if (!n->generation) + if (!n->generation) { return n->tip_name; - else { + } else { strbuf_reset(buf); strbuf_addstr(buf, n->tip_name); strbuf_strip_suffix(buf, "^0"); @@ -477,6 +511,7 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) static void show_name(const struct object *obj, const char *caller_name, + struct pretty_format *format_ctx, int always, int allow_undefined, int name_only) { const char *name; @@ -485,7 +520,7 @@ static void show_name(const struct object *obj, if (!name_only) printf("%s ", caller_name ? caller_name : oid_to_hex(oid)); - name = get_rev_name(obj, &buf); + name = get_rev_name(obj, format_ctx, &buf); if (name) printf("%s\n", name); else if (allow_undefined) @@ -505,7 +540,9 @@ static char const * const name_rev_usage[] = { NULL }; -static void name_rev_line(char *p, struct name_ref_data *data) +static void name_rev_line(char *p, + struct name_ref_data *data, + struct pretty_format *format_ctx) { struct strbuf buf = STRBUF_INIT; int counter = 0; @@ -514,9 +551,9 @@ static void name_rev_line(char *p, struct name_ref_data *data) for (p_start = p; *p; p++) { #define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) - if (!ishex(*p)) + if (!ishex(*p)) { counter = 0; - else if (++counter == hexsz && + } else if (++counter == hexsz && !ishex(*(p+1))) { struct object_id oid; const char *name = NULL; @@ -530,7 +567,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) struct object *o = lookup_object(the_repository, &oid); if (o) - name = get_rev_name(o, &buf); + name = get_rev_name(o, format_ctx, &buf); } *(p+1) = c; @@ -552,6 +589,16 @@ static void name_rev_line(char *p, struct name_ref_data *data) strbuf_release(&buf); } +static int format_cb(const struct option *option, + const char *arg, + int unset) +{ + struct format_cb_data *data = option->value; + data->format = arg; + *data->name_only = !unset; + return 0; +} + int cmd_name_rev(int argc, const char **argv, const char *prefix, @@ -565,6 +612,12 @@ int cmd_name_rev(int argc, #endif int all = 0, annotate_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; + static struct format_cb_data format_cb_data = { 0 }; + struct display_notes_opt format_notes_opt; + struct rev_info format_rev = REV_INFO_INIT; + struct pretty_format *format_ctx = NULL; + struct pretty_format format_pp = { 0 }; + struct string_list notes = STRING_LIST_INIT_NODUP; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), @@ -582,6 +635,10 @@ int cmd_name_rev(int argc, PARSE_OPT_HIDDEN), #endif /* WITH_BREAKING_CHANGES */ OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), + OPT_CALLBACK(0, "format", &format_cb_data, N_("format"), + N_("pretty-print output instead"), format_cb), + OPT_STRING_LIST(0, "notes", ¬es, N_("notes"), + N_("display notes for --format")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), @@ -590,6 +647,8 @@ int cmd_name_rev(int argc, OPT_END(), }; + init_display_notes(&format_notes_opt); + format_cb_data.name_only = &data.name_only; mem_pool_init(&string_pool, 0); init_commit_rev_name(&rev_names); repo_config(the_repository, git_default_config, NULL); @@ -604,6 +663,31 @@ int cmd_name_rev(int argc, } #endif + if (format_cb_data.format) { + get_commit_format(format_cb_data.format, &format_rev); + format_pp.ctx.rev = &format_rev; + format_pp.ctx.fmt = format_rev.commit_format; + format_pp.ctx.abbrev = format_rev.abbrev; + format_pp.ctx.date_mode_explicit = format_rev.date_mode_explicit; + format_pp.ctx.date_mode = format_rev.date_mode; + format_pp.ctx.color = GIT_COLOR_AUTO; + + userformat_find_requirements(format_cb_data.format, + &format_pp.want); + if (format_pp.want.notes) { + int ignore_show_notes = 0; + struct string_list_item *n; + + for_each_string_list_item(n, ¬es) + enable_ref_display_notes(&format_notes_opt, + &ignore_show_notes, + n->string); + load_display_notes(&format_notes_opt); + } + + format_ctx = &format_pp; + } + if (all + annotate_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); @@ -661,7 +745,7 @@ int cmd_name_rev(int argc, while (strbuf_getline(&sb, stdin) != EOF) { strbuf_addch(&sb, '\n'); - name_rev_line(sb.buf, &data); + name_rev_line(sb.buf, &data, format_ctx); } strbuf_release(&sb); } else if (all) { @@ -672,18 +756,20 @@ int cmd_name_rev(int argc, struct object *obj = get_indexed_object(the_repository, i); if (!obj || obj->type != OBJ_COMMIT) continue; - show_name(obj, NULL, + show_name(obj, NULL, format_ctx, always, allow_undefined, data.name_only); } } else { int i; for (i = 0; i < revs.nr; i++) - show_name(revs.objects[i].item, revs.objects[i].name, + show_name(revs.objects[i].item, revs.objects[i].name, format_ctx, always, allow_undefined, data.name_only); } string_list_clear(&data.ref_filters, 0); string_list_clear(&data.exclude_filters, 0); + string_list_clear(¬es, 0); + release_display_notes(&format_notes_opt); mem_pool_discard(&string_pool, 0); object_array_clear(&revs); return 0; diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 2c70cc561a..0b7e9fe396 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -658,6 +658,102 @@ test_expect_success 'name-rev --annotate-stdin works with commitGraph' ' ) ' +test_expect_success 'name-rev --format setup' ' + mkdir repo-format && + git -C repo-format init && + test_commit -C repo-format first && + test_commit -C repo-format second && + test_commit -C repo-format third && + test_commit -C repo-format fourth && + test_commit -C repo-format fifth && + test_commit -C repo-format sixth && + test_commit -C repo-format seventh && + test_commit -C repo-format eighth +' + +test_expect_success 'name-rev --format --no-name-only' ' + cat >expect <<-\EOF && + HEAD~3 [fifth] + HEAD [eighth] + HEAD~5 [third] + EOF + git -C repo-format name-rev --format="[%s]" \ + --no-name-only HEAD~3 HEAD HEAD~5 >actual && + test_cmp expect actual +' + +test_expect_success 'name-rev --format --no-format is the same as regular name-rev' ' + git -C repo-format name-rev HEAD~2 HEAD~3 >expect && + test_file_not_empty expect && + git -C repo-format name-rev --format="huh?" \ + --no-format HEAD~2 HEAD~3 >actual && + test_cmp expect actual +' + +test_expect_success 'name-rev --format=%s for argument revs' ' + cat >expect <<-\EOF && + eighth + seventh + fifth + EOF + git -C repo-format name-rev --format=%s \ + HEAD HEAD~ HEAD~3 >actual && + test_cmp expect actual +' + +test_expect_success '--name-rev --format=reference --annotate-stdin from rev-list same as log' ' + git -C repo-format log --format=reference >expect && + test_file_not_empty expect && + git -C repo-format rev-list HEAD >list && + git -C repo-format name-rev --format=reference \ + --annotate-stdin actual && + test_cmp expect actual +' + +test_expect_success '--name-rev --format= --annotate-stdin with running text and tree oid' ' + cmit_oid=$(git -C repo-format rev-parse :/fifth) && + reference=$(git -C repo-format log -n1 --format=reference :/fifth) && + tree=$(git -C repo-format rev-parse HEAD^{tree}) && + cat >expect <<-EOF && + We thought we fixed this in ${reference}. + But look at this tree: ${tree}. + EOF + git -C repo-format name-rev --format=reference --annotate-stdin \ + >actual <<-EOF && + We thought we fixed this in ${cmit_oid}. + But look at this tree: ${tree}. + EOF + test_cmp expect actual +' + +test_expect_success 'name-rev --format= with %N (note)' ' + test_when_finished "git -C repo-format notes remove" && + git -C repo-format notes add -m"Make a note" && + printf "Make a note\n\n\n" >expect && + git -C repo-format name-rev --format="tformat:%N" \ + HEAD HEAD~ >actual && + test_cmp expect actual +' + +test_expect_success 'name-rev --format= --notes' ' + # One custom notes ref + test_when_finished "git -C repo-format notes remove" && + test_when_finished "git -C repo-format notes --ref=word remove" && + git -C repo-format notes add -m"default" && + git -C repo-format notes --ref=word add -m"custom" && + printf "custom\n\n" >expect && + git -C repo-format name-rev --format="tformat:%N" \ + --notes=word \ + HEAD >actual && + test_cmp expect actual && + # Glob all + printf "default\ncustom\n\n" >expect && + git -C repo-format name-rev --format="tformat:%N" \ + --notes=* \ + HEAD >actual && + test_cmp expect actual +' + # B # o # H \