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:
parent
1d1bdafd64
commit
c58492d434
@ -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]
|
||||||
|
134
ref-filter.c
134
ref-filter.c
@ -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(¤t->output, &s);
|
strbuf_swap(¤t->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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user