ref-filter: implement %(if), %(then), and %(else) atoms

Implement %(if), %(then) and %(else) atoms. Used as
%(if)...%(then)...%(end) or %(if)...%(then)...%(else)...%(end). If the
format string between %(if) and %(then) expands to an empty string, or
to only whitespaces, then the whole %(if)...%(end) expands to the string
following %(then). Otherwise, it expands to the string following
%(else), if any. Nesting of this construct is possible.

This is in preparation for porting over `git branch -l` to use
ref-filter APIs for printing.

Add documentation and tests regarding the same.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Karthik Nayak 2017-01-10 14:19:34 +05:30 committed by Junio C Hamano
parent 1d1bdafd64
commit c58492d434
3 changed files with 237 additions and 7 deletions

View File

@ -149,6 +149,16 @@ align::
quoted, but if nested then only the topmost level performs quoted, but if nested then only the topmost level performs
quoting. quoting.
if::
Used as %(if)...%(then)...%(end) or
%(if)...%(then)...%(else)...%(end). If there is an atom with
value or string literal after the %(if) then everything after
the %(then) is printed, else if the %(else) atom is used, then
everything after %(else) is printed. We ignore space when
evaluating the string before %(then), this is useful when we
use the %(HEAD) atom which prints either "*" or " " and we
want to apply the 'if' condition only on the 'HEAD' ref.
In addition to the above, for commit and tag objects, the header In addition to the above, for commit and tag objects, the header
field names (`tree`, `parent`, `object`, `type`, and `tag`) can field names (`tree`, `parent`, `object`, `type`, and `tag`) can
be used to specify the value in the header field. be used to specify the value in the header field.
@ -186,6 +196,14 @@ As a special case for the date-type fields, you may specify a format for
the date by adding `:` followed by date format name (see the the date by adding `:` followed by date format name (see the
values the `--date` option to linkgit:git-rev-list[1] takes). values the `--date` option to linkgit:git-rev-list[1] takes).
Some atoms like %(align) and %(if) always require a matching %(end).
We call them "opening atoms" and sometimes denote them as %($open).
When a scripting language specific quoting is in effect, everything
between a top-level opening atom and its matching %(end) is evaluated
according to the semantics of the opening atom and only its result
from the top-level is quoted.
EXAMPLES EXAMPLES
-------- --------
@ -273,6 +291,22 @@ eval=`git for-each-ref --shell --format="$fmt" \
eval "$eval" eval "$eval"
------------ ------------
An example to show the usage of %(if)...%(then)...%(else)...%(end).
This prefixes the current branch with a star.
------------
git for-each-ref --format="%(if)%(HEAD)%(then)* %(else) %(end)%(refname:short)" refs/heads/
------------
An example to show the usage of %(if)...%(then)...%(end).
This prints the authorname, if present.
------------
git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)"
------------
SEE ALSO SEE ALSO
-------- --------
linkgit:git-show-ref[1] linkgit:git-show-ref[1]

View File

@ -22,6 +22,12 @@ struct align {
unsigned int width; unsigned int width;
}; };
struct if_then_else {
unsigned int then_atom_seen : 1,
else_atom_seen : 1,
condition_satisfied : 1;
};
/* /*
* An atom is a valid field atom listed below, possibly prefixed with * An atom is a valid field atom listed below, possibly prefixed with
* a "*" to denote deref_tag(). * a "*" to denote deref_tag().
@ -214,6 +220,9 @@ static struct {
{ "color", FIELD_STR, color_atom_parser }, { "color", FIELD_STR, color_atom_parser },
{ "align", FIELD_STR, align_atom_parser }, { "align", FIELD_STR, align_atom_parser },
{ "end" }, { "end" },
{ "if" },
{ "then" },
{ "else" },
}; };
#define REF_FORMATTING_STATE_INIT { 0, NULL } #define REF_FORMATTING_STATE_INIT { 0, NULL }
@ -221,7 +230,7 @@ static struct {
struct ref_formatting_stack { struct ref_formatting_stack {
struct ref_formatting_stack *prev; struct ref_formatting_stack *prev;
struct strbuf output; struct strbuf output;
void (*at_end)(struct ref_formatting_stack *stack); void (*at_end)(struct ref_formatting_stack **stack);
void *at_end_data; void *at_end_data;
}; };
@ -354,13 +363,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
*stack = prev; *stack = prev;
} }
static void end_align_handler(struct ref_formatting_stack *stack) static void end_align_handler(struct ref_formatting_stack **stack)
{ {
struct align *align = (struct align *)stack->at_end_data; struct ref_formatting_stack *cur = *stack;
struct align *align = (struct align *)cur->at_end_data;
struct strbuf s = STRBUF_INIT; struct strbuf s = STRBUF_INIT;
strbuf_utf8_align(&s, align->position, align->width, stack->output.buf); strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
strbuf_swap(&stack->output, &s); strbuf_swap(&cur->output, &s);
strbuf_release(&s); strbuf_release(&s);
} }
@ -374,6 +384,104 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s
new->at_end_data = &atomv->u.align; new->at_end_data = &atomv->u.align;
} }
static void if_then_else_handler(struct ref_formatting_stack **stack)
{
struct ref_formatting_stack *cur = *stack;
struct ref_formatting_stack *prev = cur->prev;
struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data;
if (!if_then_else->then_atom_seen)
die(_("format: %%(if) atom used without a %%(then) atom"));
if (if_then_else->else_atom_seen) {
/*
* There is an %(else) atom: we need to drop one state from the
* stack, either the %(else) branch if the condition is satisfied, or
* the %(then) branch if it isn't.
*/
if (if_then_else->condition_satisfied) {
strbuf_reset(&cur->output);
pop_stack_element(&cur);
} else {
strbuf_swap(&cur->output, &prev->output);
strbuf_reset(&cur->output);
pop_stack_element(&cur);
}
} else if (!if_then_else->condition_satisfied) {
/*
* No %(else) atom: just drop the %(then) branch if the
* condition is not satisfied.
*/
strbuf_reset(&cur->output);
}
*stack = cur;
free(if_then_else);
}
static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{
struct ref_formatting_stack *new;
struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
push_stack_element(&state->stack);
new = state->stack;
new->at_end = if_then_else_handler;
new->at_end_data = if_then_else;
}
static int is_empty(const char *s)
{
while (*s != '\0') {
if (!isspace(*s))
return 0;
s++;
}
return 1;
}
static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{
struct ref_formatting_stack *cur = state->stack;
struct if_then_else *if_then_else = NULL;
if (cur->at_end == if_then_else_handler)
if_then_else = (struct if_then_else *)cur->at_end_data;
if (!if_then_else)
die(_("format: %%(then) atom used without an %%(if) atom"));
if (if_then_else->then_atom_seen)
die(_("format: %%(then) atom used more than once"));
if (if_then_else->else_atom_seen)
die(_("format: %%(then) atom used after %%(else)"));
if_then_else->then_atom_seen = 1;
/*
* If there exists non-empty string between the 'if' and
* 'then' atom then the 'if' condition is satisfied.
*/
if (cur->output.len && !is_empty(cur->output.buf))
if_then_else->condition_satisfied = 1;
strbuf_reset(&cur->output);
}
static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{
struct ref_formatting_stack *prev = state->stack;
struct if_then_else *if_then_else = NULL;
if (prev->at_end == if_then_else_handler)
if_then_else = (struct if_then_else *)prev->at_end_data;
if (!if_then_else)
die(_("format: %%(else) atom used without an %%(if) atom"));
if (!if_then_else->then_atom_seen)
die(_("format: %%(else) atom used without a %%(then) atom"));
if (if_then_else->else_atom_seen)
die(_("format: %%(else) atom used more than once"));
if_then_else->else_atom_seen = 1;
push_stack_element(&state->stack);
state->stack->at_end_data = prev->at_end_data;
state->stack->at_end = prev->at_end;
}
static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
{ {
struct ref_formatting_stack *current = state->stack; struct ref_formatting_stack *current = state->stack;
@ -381,14 +489,17 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta
if (!current->at_end) if (!current->at_end)
die(_("format: %%(end) atom used without corresponding atom")); die(_("format: %%(end) atom used without corresponding atom"));
current->at_end(current); current->at_end(&state->stack);
/* Stack may have been popped within at_end(), hence reset the current pointer */
current = state->stack;
/* /*
* Perform quote formatting when the stack element is that of * Perform quote formatting when the stack element is that of
* a supporting atom. If nested then perform quote formatting * a supporting atom. If nested then perform quote formatting
* only on the topmost supporting atom. * only on the topmost supporting atom.
*/ */
if (!state->stack->prev->prev) { if (!current->prev->prev) {
quote_formatting(&s, current->output.buf, state->quote_style); quote_formatting(&s, current->output.buf, state->quote_style);
strbuf_swap(&current->output, &s); strbuf_swap(&current->output, &s);
} }
@ -1049,6 +1160,15 @@ static void populate_value(struct ref_array_item *ref)
} else if (!strcmp(name, "end")) { } else if (!strcmp(name, "end")) {
v->handler = end_atom_handler; v->handler = end_atom_handler;
continue; continue;
} else if (!strcmp(name, "if")) {
v->handler = if_atom_handler;
continue;
} else if (!strcmp(name, "then")) {
v->handler = then_atom_handler;
continue;
} else if (!strcmp(name, "else")) {
v->handler = else_atom_handler;
continue;
} else } else
continue; continue;

View File

@ -327,4 +327,80 @@ test_expect_success 'reverse version sort' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'improper usage of %(if), %(then), %(else) and %(end) atoms' '
test_must_fail git for-each-ref --format="%(if)" &&
test_must_fail git for-each-ref --format="%(then) %(end)" &&
test_must_fail git for-each-ref --format="%(else) %(end)" &&
test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
test_must_fail git for-each-ref --format="%(if) %(then) %(then) %(end)" &&
test_must_fail git for-each-ref --format="%(then) %(else) %(end)" &&
test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
test_must_fail git for-each-ref --format="%(if) %(then) %(else)" &&
test_must_fail git for-each-ref --format="%(if) %(else) %(then) %(end)" &&
test_must_fail git for-each-ref --format="%(if) %(then) %(else) %(else) %(end)" &&
test_must_fail git for-each-ref --format="%(if) %(end)"
'
test_expect_success 'check %(if)...%(then)...%(end) atoms' '
git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Author: %(authorname)%(end)" >actual &&
cat >expect <<-\EOF &&
refs/heads/master Author: A U Thor
refs/heads/side Author: A U Thor
refs/odd/spot Author: A U Thor
refs/tags/annotated-tag
refs/tags/doubly-annotated-tag
refs/tags/doubly-signed-tag
refs/tags/foo1.10 Author: A U Thor
refs/tags/foo1.3 Author: A U Thor
refs/tags/foo1.6 Author: A U Thor
refs/tags/four Author: A U Thor
refs/tags/one Author: A U Thor
refs/tags/signed-tag
refs/tags/three Author: A U Thor
refs/tags/two Author: A U Thor
EOF
test_cmp expect actual
'
test_expect_success 'check %(if)...%(then)...%(else)...%(end) atoms' '
git for-each-ref --format="%(if)%(authorname)%(then)%(authorname)%(else)No author%(end): %(refname)" >actual &&
cat >expect <<-\EOF &&
A U Thor: refs/heads/master
A U Thor: refs/heads/side
A U Thor: refs/odd/spot
No author: refs/tags/annotated-tag
No author: refs/tags/doubly-annotated-tag
No author: refs/tags/doubly-signed-tag
A U Thor: refs/tags/foo1.10
A U Thor: refs/tags/foo1.3
A U Thor: refs/tags/foo1.6
A U Thor: refs/tags/four
A U Thor: refs/tags/one
No author: refs/tags/signed-tag
A U Thor: refs/tags/three
A U Thor: refs/tags/two
EOF
test_cmp expect actual
'
test_expect_success 'ignore spaces in %(if) atom usage' '
git for-each-ref --format="%(refname:short): %(if)%(HEAD)%(then)Head ref%(else)Not Head ref%(end)" >actual &&
cat >expect <<-\EOF &&
master: Head ref
side: Not Head ref
odd/spot: Not Head ref
annotated-tag: Not Head ref
doubly-annotated-tag: Not Head ref
doubly-signed-tag: Not Head ref
foo1.10: Not Head ref
foo1.3: Not Head ref
foo1.6: Not Head ref
four: Not Head ref
one: Not Head ref
signed-tag: Not Head ref
three: Not Head ref
two: Not Head ref
EOF
test_cmp expect actual
'
test_done test_done