Skip to content

Commit 803e705

Browse files
LemmingAvalanchegitster
authored andcommitted
name-rev: learn --format=<pretty>
Teach git-name-rev(1) to format the given revisions instead of creating symbolic names. Sometimes you want to format commits. Most of the time you’re walking the graph, e.g. getting a range of commits like `master..topic`. That’s a job for git-log(1). But sometimes you might want to format commits that you encounter on demand: • Full hashes in running text that you might want to pretty-print • git-last-modified(1) outputs full hashes that you can do the same with • git-cherry(1) has `-v` for commit subject, but maybe you want something else? But now you can’t use git-log(1), git-show(1), or git-rev-list(1): • You can’t feed commits piecemeal to these commands, one input for one output; they block until standard in is closed • You can’t feed a list of possibly duplicate commits, like the output of git-last-modified(1); they effectively deduplicate the output Beyond these two points there’s also the input massage problem: you cannot feed mixed input (revisions mixed with arbitrary text). One might hope that git-cat-file(1) can save us. But it doesn’t support pretty formats. But there is one command that already both handles revisions as arguments, revisions on standard input, and even revisions mixed in with arbitrary text. Namely git-name-rev(1). Teach it to work in a format mode where the output for each revision is the pretty output (implies `--name-only`). This can be used to format any revision expression when given as arguments, and all full commit hashes in running text on stdin. Just bring the hashes (to the pipeline). We will pretty print them. Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 7fe5a5f commit 803e705

File tree

3 files changed

+198
-8
lines changed

3 files changed

+198
-8
lines changed

Documentation/git-name-rev.adoc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-name-rev - Find symbolic names for given revs
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git name-rev' [--tags] [--refs=<pattern>]
12+
'git name-rev' [--tags] [--refs=<pattern>] [--format=<pretty>]
1313
( --all | --annotate-stdin | <commit-ish>... )
1414

1515
DESCRIPTION
@@ -21,6 +21,14 @@ format parsable by 'git rev-parse'.
2121
OPTIONS
2222
-------
2323

24+
--format=<pretty>::
25+
--no-format::
26+
Format revisions instead of outputting symbolic names. The
27+
default is `--no-format`.
28+
+
29+
Implies `--name-only`. The negation `--no-format` implies
30+
`--no-name-only` (the default for the command).
31+
2432
--tags::
2533
Do not use branch names, but only tags to name the commits
2634

builtin/name-rev.c

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#include "commit-graph.h"
1919
#include "wildmatch.h"
2020
#include "mem-pool.h"
21+
#include "pretty.h"
22+
#include "revision.h"
23+
#include "notes.h"
2124

2225
/*
2326
* One day. See the 'name a rev shortly after epoch' test in t6120 when
@@ -33,6 +36,16 @@ struct rev_name {
3336
int from_tag;
3437
};
3538

39+
struct pretty_format {
40+
struct pretty_print_context ctx;
41+
struct userformat_want want;
42+
};
43+
44+
struct format_cb_data {
45+
const char *format;
46+
int *name_only;
47+
};
48+
3649
define_commit_slab(commit_rev_name, struct rev_name);
3750

3851
static timestamp_t generation_cutoff = GENERATION_NUMBER_INFINITY;
@@ -454,14 +467,35 @@ static const char *get_exact_ref_match(const struct object *o)
454467
}
455468

456469
/* may return a constant string or use "buf" as scratch space */
457-
static const char *get_rev_name(const struct object *o, struct strbuf *buf)
470+
static const char *get_rev_name(const struct object *o,
471+
struct pretty_format *format_ctx,
472+
struct strbuf *buf)
458473
{
459474
struct rev_name *n;
460475
const struct commit *c;
461476

462477
if (o->type != OBJ_COMMIT)
463478
return get_exact_ref_match(o);
464479
c = (const struct commit *) o;
480+
481+
if (format_ctx) {
482+
strbuf_reset(buf);
483+
484+
if (format_ctx->want.notes) {
485+
struct strbuf notebuf = STRBUF_INIT;
486+
487+
format_display_notes(&c->object.oid, &notebuf,
488+
get_log_output_encoding(),
489+
format_ctx->ctx.fmt == CMIT_FMT_USERFORMAT);
490+
format_ctx->ctx.notes_message = strbuf_detach(&notebuf, NULL);
491+
}
492+
493+
pretty_print_commit(&format_ctx->ctx, c, buf);
494+
FREE_AND_NULL(format_ctx->ctx.notes_message);
495+
496+
return buf->buf;
497+
}
498+
465499
n = get_commit_rev_name(c);
466500
if (!n)
467501
return NULL;
@@ -479,6 +513,7 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf)
479513

480514
static void show_name(const struct object *obj,
481515
const char *caller_name,
516+
struct pretty_format *format_ctx,
482517
int always, int allow_undefined, int name_only)
483518
{
484519
const char *name;
@@ -487,7 +522,7 @@ static void show_name(const struct object *obj,
487522

488523
if (!name_only)
489524
printf("%s ", caller_name ? caller_name : oid_to_hex(oid));
490-
name = get_rev_name(obj, &buf);
525+
name = get_rev_name(obj, format_ctx, &buf);
491526
if (name)
492527
printf("%s\n", name);
493528
else if (allow_undefined)
@@ -507,7 +542,9 @@ static char const * const name_rev_usage[] = {
507542
NULL
508543
};
509544

510-
static void name_rev_line(char *p, struct name_ref_data *data)
545+
static void name_rev_line(char *p,
546+
struct name_ref_data *data,
547+
struct pretty_format *format_ctx)
511548
{
512549
struct strbuf buf = STRBUF_INIT;
513550
int counter = 0;
@@ -532,7 +569,7 @@ static void name_rev_line(char *p, struct name_ref_data *data)
532569
struct object *o =
533570
lookup_object(the_repository, &oid);
534571
if (o)
535-
name = get_rev_name(o, &buf);
572+
name = get_rev_name(o, format_ctx, &buf);
536573
}
537574
*(p+1) = c;
538575

@@ -554,6 +591,16 @@ static void name_rev_line(char *p, struct name_ref_data *data)
554591
strbuf_release(&buf);
555592
}
556593

594+
static int format_cb(const struct option *option,
595+
const char *arg,
596+
int unset)
597+
{
598+
struct format_cb_data *data = option->value;
599+
data->format = arg;
600+
*data->name_only = !unset;
601+
return 0;
602+
}
603+
557604
int cmd_name_rev(int argc,
558605
const char **argv,
559606
const char *prefix,
@@ -567,6 +614,12 @@ int cmd_name_rev(int argc,
567614
#endif
568615
int all = 0, annotate_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
569616
struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
617+
static struct format_cb_data format_cb_data = { 0 };
618+
struct display_notes_opt format_notes_opt;
619+
struct rev_info format_rev = REV_INFO_INIT;
620+
struct pretty_format *format_ctx = NULL;
621+
struct pretty_format format_pp = { 0 };
622+
struct string_list notes = STRING_LIST_INIT_NODUP;
570623
struct option opts[] = {
571624
OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")),
572625
OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
@@ -584,6 +637,10 @@ int cmd_name_rev(int argc,
584637
PARSE_OPT_HIDDEN),
585638
#endif /* WITH_BREAKING_CHANGES */
586639
OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")),
640+
OPT_CALLBACK(0, "format", &format_cb_data, N_("format"),
641+
N_("pretty-print output instead"), format_cb),
642+
OPT_STRING_LIST(0, "notes", &notes, N_("notes"),
643+
N_("display notes for --format")),
587644
OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
588645
OPT_BOOL(0, "always", &always,
589646
N_("show abbreviated commit object as fallback")),
@@ -592,6 +649,8 @@ int cmd_name_rev(int argc,
592649
OPT_END(),
593650
};
594651

652+
init_display_notes(&format_notes_opt);
653+
format_cb_data.name_only = &data.name_only;
595654
mem_pool_init(&string_pool, 0);
596655
init_commit_rev_name(&rev_names);
597656
repo_config(the_repository, git_default_config, NULL);
@@ -606,6 +665,31 @@ int cmd_name_rev(int argc,
606665
}
607666
#endif
608667

668+
if (format_cb_data.format) {
669+
get_commit_format(format_cb_data.format, &format_rev);
670+
format_pp.ctx.rev = &format_rev;
671+
format_pp.ctx.fmt = format_rev.commit_format;
672+
format_pp.ctx.abbrev = format_rev.abbrev;
673+
format_pp.ctx.date_mode_explicit = format_rev.date_mode_explicit;
674+
format_pp.ctx.date_mode = format_rev.date_mode;
675+
format_pp.ctx.color = GIT_COLOR_AUTO;
676+
677+
userformat_find_requirements(format_cb_data.format,
678+
&format_pp.want);
679+
if (format_pp.want.notes) {
680+
int ignore_show_notes = 0;
681+
struct string_list_item *n;
682+
683+
for_each_string_list_item(n, &notes)
684+
enable_ref_display_notes(&format_notes_opt,
685+
&ignore_show_notes,
686+
n->string);
687+
load_display_notes(&format_notes_opt);
688+
}
689+
690+
format_ctx = &format_pp;
691+
}
692+
609693
if (all + annotate_stdin + !!argc > 1) {
610694
error("Specify either a list, or --all, not both!");
611695
usage_with_options(name_rev_usage, opts);
@@ -663,7 +747,7 @@ int cmd_name_rev(int argc,
663747

664748
while (strbuf_getline(&sb, stdin) != EOF) {
665749
strbuf_addch(&sb, '\n');
666-
name_rev_line(sb.buf, &data);
750+
name_rev_line(sb.buf, &data, format_ctx);
667751
}
668752
strbuf_release(&sb);
669753
} else if (all) {
@@ -674,18 +758,20 @@ int cmd_name_rev(int argc,
674758
struct object *obj = get_indexed_object(the_repository, i);
675759
if (!obj || obj->type != OBJ_COMMIT)
676760
continue;
677-
show_name(obj, NULL,
761+
show_name(obj, NULL, format_ctx,
678762
always, allow_undefined, data.name_only);
679763
}
680764
} else {
681765
int i;
682766
for (i = 0; i < revs.nr; i++)
683-
show_name(revs.objects[i].item, revs.objects[i].name,
767+
show_name(revs.objects[i].item, revs.objects[i].name, format_ctx,
684768
always, allow_undefined, data.name_only);
685769
}
686770

687771
string_list_clear(&data.ref_filters, 0);
688772
string_list_clear(&data.exclude_filters, 0);
773+
string_list_clear(&notes, 0);
774+
release_display_notes(&format_notes_opt);
689775
mem_pool_discard(&string_pool, 0);
690776
object_array_clear(&revs);
691777
return 0;

t/t6120-describe.sh

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,102 @@ test_expect_success 'name-rev --annotate-stdin works with commitGraph' '
658658
)
659659
'
660660

661+
test_expect_success 'name-rev --format setup' '
662+
mkdir repo-format &&
663+
git -C repo-format init &&
664+
test_commit -C repo-format first &&
665+
test_commit -C repo-format second &&
666+
test_commit -C repo-format third &&
667+
test_commit -C repo-format fourth &&
668+
test_commit -C repo-format fifth &&
669+
test_commit -C repo-format sixth &&
670+
test_commit -C repo-format seventh &&
671+
test_commit -C repo-format eighth
672+
'
673+
674+
test_expect_success 'name-rev --format --no-name-only' '
675+
cat >expect <<-\EOF &&
676+
HEAD~3 [fifth]
677+
HEAD [eighth]
678+
HEAD~5 [third]
679+
EOF
680+
git -C repo-format name-rev --format="[%s]" \
681+
--no-name-only HEAD~3 HEAD HEAD~5 >actual &&
682+
test_cmp expect actual
683+
'
684+
685+
test_expect_success 'name-rev --format --no-format is the same as regular name-rev' '
686+
git -C repo-format name-rev HEAD~2 HEAD~3 >expect &&
687+
test_file_not_empty expect &&
688+
git -C repo-format name-rev --format="huh?" \
689+
--no-format HEAD~2 HEAD~3 >actual &&
690+
test_cmp expect actual
691+
'
692+
693+
test_expect_success 'name-rev --format=%s for argument revs' '
694+
cat >expect <<-\EOF &&
695+
eighth
696+
seventh
697+
fifth
698+
EOF
699+
git -C repo-format name-rev --format=%s \
700+
HEAD HEAD~ HEAD~3 >actual &&
701+
test_cmp expect actual
702+
'
703+
704+
test_expect_success '--name-rev --format=reference --annotate-stdin from rev-list same as log' '
705+
git -C repo-format log --format=reference >expect &&
706+
test_file_not_empty expect &&
707+
git -C repo-format rev-list HEAD >list &&
708+
git -C repo-format name-rev --format=reference \
709+
--annotate-stdin <list >actual &&
710+
test_cmp expect actual
711+
'
712+
713+
test_expect_success '--name-rev --format=<pretty> --annotate-stdin with running text and tree oid' '
714+
cmit_oid=$(git -C repo-format rev-parse :/fifth) &&
715+
reference=$(git -C repo-format log -n1 --format=reference :/fifth) &&
716+
tree=$(git -C repo-format rev-parse HEAD^{tree}) &&
717+
cat >expect <<-EOF &&
718+
We thought we fixed this in ${reference}.
719+
But look at this tree: ${tree}.
720+
EOF
721+
git -C repo-format name-rev --format=reference --annotate-stdin \
722+
>actual <<-EOF &&
723+
We thought we fixed this in ${cmit_oid}.
724+
But look at this tree: ${tree}.
725+
EOF
726+
test_cmp expect actual
727+
'
728+
729+
test_expect_success 'name-rev --format=<pretty> with %N (note)' '
730+
test_when_finished "git -C repo-format notes remove" &&
731+
git -C repo-format notes add -m"Make a note" &&
732+
printf "Make a note\n\n\n" >expect &&
733+
git -C repo-format name-rev --format="tformat:%N" \
734+
HEAD HEAD~ >actual &&
735+
test_cmp expect actual
736+
'
737+
738+
test_expect_success 'name-rev --format=<pretty> --notes<ref>' '
739+
# One custom notes ref
740+
test_when_finished "git -C repo-format notes remove" &&
741+
test_when_finished "git -C repo-format notes --ref=word remove" &&
742+
git -C repo-format notes add -m"default" &&
743+
git -C repo-format notes --ref=word add -m"custom" &&
744+
printf "custom\n\n" >expect &&
745+
git -C repo-format name-rev --format="tformat:%N" \
746+
--notes=word \
747+
HEAD >actual &&
748+
test_cmp expect actual &&
749+
# Glob all
750+
printf "default\ncustom\n\n" >expect &&
751+
git -C repo-format name-rev --format="tformat:%N" \
752+
--notes=* \
753+
HEAD >actual &&
754+
test_cmp expect actual
755+
'
756+
661757
# B
662758
# o
663759
# H \

0 commit comments

Comments
 (0)