提交 c58492d4 编写于 作者: K Karthik Nayak 提交者: Junio C Hamano

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: NChristian Couder <christian.couder@gmail.com>
Mentored-by: NMatthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: NKarthik Nayak <karthik.188@gmail.com>
Signed-off-by: NJunio C Hamano <gitster@pobox.com>
上级 1d1bdafd
......@@ -149,6 +149,16 @@ align::
quoted, but if nested then only the topmost level performs
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
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
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
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
--------
......@@ -273,6 +291,22 @@ eval=`git for-each-ref --shell --format="$fmt" \
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
--------
linkgit:git-show-ref[1]
......
......@@ -22,6 +22,12 @@ struct align {
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
* a "*" to denote deref_tag().
......@@ -214,6 +220,9 @@ static struct {
{ "color", FIELD_STR, color_atom_parser },
{ "align", FIELD_STR, align_atom_parser },
{ "end" },
{ "if" },
{ "then" },
{ "else" },
};
#define REF_FORMATTING_STATE_INIT { 0, NULL }
......@@ -221,7 +230,7 @@ static struct {
struct ref_formatting_stack {
struct ref_formatting_stack *prev;
struct strbuf output;
void (*at_end)(struct ref_formatting_stack *stack);
void (*at_end)(struct ref_formatting_stack **stack);
void *at_end_data;
};
......@@ -354,13 +363,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
*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;
strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
strbuf_swap(&stack->output, &s);
strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
strbuf_swap(&cur->output, &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;
}
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)
{
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)
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
* a supporting atom. If nested then perform quote formatting
* only on the topmost supporting atom.
*/
if (!state->stack->prev->prev) {
if (!current->prev->prev) {
quote_formatting(&s, current->output.buf, state->quote_style);
strbuf_swap(&current->output, &s);
}
......@@ -1049,6 +1160,15 @@ static void populate_value(struct ref_array_item *ref)
} else if (!strcmp(name, "end")) {
v->handler = end_atom_handler;
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
continue;
......
......@@ -327,4 +327,80 @@ test_expect_success 'reverse version sort' '
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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册