Skip to content

Commit c58492d

Browse files
KarthikNayakgitster
authored andcommitted
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 <[email protected]> Mentored-by: Matthieu Moy <[email protected]> Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1d1bdaf commit c58492d

File tree

3 files changed

+237
-7
lines changed

3 files changed

+237
-7
lines changed

Documentation/git-for-each-ref.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ align::
149149
quoted, but if nested then only the topmost level performs
150150
quoting.
151151

152+
if::
153+
Used as %(if)...%(then)...%(end) or
154+
%(if)...%(then)...%(else)...%(end). If there is an atom with
155+
value or string literal after the %(if) then everything after
156+
the %(then) is printed, else if the %(else) atom is used, then
157+
everything after %(else) is printed. We ignore space when
158+
evaluating the string before %(then), this is useful when we
159+
use the %(HEAD) atom which prints either "*" or " " and we
160+
want to apply the 'if' condition only on the 'HEAD' ref.
161+
152162
In addition to the above, for commit and tag objects, the header
153163
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
154164
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
186196
the date by adding `:` followed by date format name (see the
187197
values the `--date` option to linkgit:git-rev-list[1] takes).
188198

199+
Some atoms like %(align) and %(if) always require a matching %(end).
200+
We call them "opening atoms" and sometimes denote them as %($open).
201+
202+
When a scripting language specific quoting is in effect, everything
203+
between a top-level opening atom and its matching %(end) is evaluated
204+
according to the semantics of the opening atom and only its result
205+
from the top-level is quoted.
206+
189207

190208
EXAMPLES
191209
--------
@@ -273,6 +291,22 @@ eval=`git for-each-ref --shell --format="$fmt" \
273291
eval "$eval"
274292
------------
275293

294+
295+
An example to show the usage of %(if)...%(then)...%(else)...%(end).
296+
This prefixes the current branch with a star.
297+
298+
------------
299+
git for-each-ref --format="%(if)%(HEAD)%(then)* %(else) %(end)%(refname:short)" refs/heads/
300+
------------
301+
302+
303+
An example to show the usage of %(if)...%(then)...%(end).
304+
This prints the authorname, if present.
305+
306+
------------
307+
git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Authored by: %(authorname)%(end)"
308+
------------
309+
276310
SEE ALSO
277311
--------
278312
linkgit:git-show-ref[1]

ref-filter.c

Lines changed: 127 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ struct align {
2222
unsigned int width;
2323
};
2424

25+
struct if_then_else {
26+
unsigned int then_atom_seen : 1,
27+
else_atom_seen : 1,
28+
condition_satisfied : 1;
29+
};
30+
2531
/*
2632
* An atom is a valid field atom listed below, possibly prefixed with
2733
* a "*" to denote deref_tag().
@@ -214,14 +220,17 @@ static struct {
214220
{ "color", FIELD_STR, color_atom_parser },
215221
{ "align", FIELD_STR, align_atom_parser },
216222
{ "end" },
223+
{ "if" },
224+
{ "then" },
225+
{ "else" },
217226
};
218227

219228
#define REF_FORMATTING_STATE_INIT { 0, NULL }
220229

221230
struct ref_formatting_stack {
222231
struct ref_formatting_stack *prev;
223232
struct strbuf output;
224-
void (*at_end)(struct ref_formatting_stack *stack);
233+
void (*at_end)(struct ref_formatting_stack **stack);
225234
void *at_end_data;
226235
};
227236

@@ -354,13 +363,14 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
354363
*stack = prev;
355364
}
356365

357-
static void end_align_handler(struct ref_formatting_stack *stack)
366+
static void end_align_handler(struct ref_formatting_stack **stack)
358367
{
359-
struct align *align = (struct align *)stack->at_end_data;
368+
struct ref_formatting_stack *cur = *stack;
369+
struct align *align = (struct align *)cur->at_end_data;
360370
struct strbuf s = STRBUF_INIT;
361371

362-
strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
363-
strbuf_swap(&stack->output, &s);
372+
strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
373+
strbuf_swap(&cur->output, &s);
364374
strbuf_release(&s);
365375
}
366376

@@ -374,21 +384,122 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s
374384
new->at_end_data = &atomv->u.align;
375385
}
376386

387+
static void if_then_else_handler(struct ref_formatting_stack **stack)
388+
{
389+
struct ref_formatting_stack *cur = *stack;
390+
struct ref_formatting_stack *prev = cur->prev;
391+
struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data;
392+
393+
if (!if_then_else->then_atom_seen)
394+
die(_("format: %%(if) atom used without a %%(then) atom"));
395+
396+
if (if_then_else->else_atom_seen) {
397+
/*
398+
* There is an %(else) atom: we need to drop one state from the
399+
* stack, either the %(else) branch if the condition is satisfied, or
400+
* the %(then) branch if it isn't.
401+
*/
402+
if (if_then_else->condition_satisfied) {
403+
strbuf_reset(&cur->output);
404+
pop_stack_element(&cur);
405+
} else {
406+
strbuf_swap(&cur->output, &prev->output);
407+
strbuf_reset(&cur->output);
408+
pop_stack_element(&cur);
409+
}
410+
} else if (!if_then_else->condition_satisfied) {
411+
/*
412+
* No %(else) atom: just drop the %(then) branch if the
413+
* condition is not satisfied.
414+
*/
415+
strbuf_reset(&cur->output);
416+
}
417+
418+
*stack = cur;
419+
free(if_then_else);
420+
}
421+
422+
static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
423+
{
424+
struct ref_formatting_stack *new;
425+
struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
426+
427+
push_stack_element(&state->stack);
428+
new = state->stack;
429+
new->at_end = if_then_else_handler;
430+
new->at_end_data = if_then_else;
431+
}
432+
433+
static int is_empty(const char *s)
434+
{
435+
while (*s != '\0') {
436+
if (!isspace(*s))
437+
return 0;
438+
s++;
439+
}
440+
return 1;
441+
}
442+
443+
static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
444+
{
445+
struct ref_formatting_stack *cur = state->stack;
446+
struct if_then_else *if_then_else = NULL;
447+
448+
if (cur->at_end == if_then_else_handler)
449+
if_then_else = (struct if_then_else *)cur->at_end_data;
450+
if (!if_then_else)
451+
die(_("format: %%(then) atom used without an %%(if) atom"));
452+
if (if_then_else->then_atom_seen)
453+
die(_("format: %%(then) atom used more than once"));
454+
if (if_then_else->else_atom_seen)
455+
die(_("format: %%(then) atom used after %%(else)"));
456+
if_then_else->then_atom_seen = 1;
457+
/*
458+
* If there exists non-empty string between the 'if' and
459+
* 'then' atom then the 'if' condition is satisfied.
460+
*/
461+
if (cur->output.len && !is_empty(cur->output.buf))
462+
if_then_else->condition_satisfied = 1;
463+
strbuf_reset(&cur->output);
464+
}
465+
466+
static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
467+
{
468+
struct ref_formatting_stack *prev = state->stack;
469+
struct if_then_else *if_then_else = NULL;
470+
471+
if (prev->at_end == if_then_else_handler)
472+
if_then_else = (struct if_then_else *)prev->at_end_data;
473+
if (!if_then_else)
474+
die(_("format: %%(else) atom used without an %%(if) atom"));
475+
if (!if_then_else->then_atom_seen)
476+
die(_("format: %%(else) atom used without a %%(then) atom"));
477+
if (if_then_else->else_atom_seen)
478+
die(_("format: %%(else) atom used more than once"));
479+
if_then_else->else_atom_seen = 1;
480+
push_stack_element(&state->stack);
481+
state->stack->at_end_data = prev->at_end_data;
482+
state->stack->at_end = prev->at_end;
483+
}
484+
377485
static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
378486
{
379487
struct ref_formatting_stack *current = state->stack;
380488
struct strbuf s = STRBUF_INIT;
381489

382490
if (!current->at_end)
383491
die(_("format: %%(end) atom used without corresponding atom"));
384-
current->at_end(current);
492+
current->at_end(&state->stack);
493+
494+
/* Stack may have been popped within at_end(), hence reset the current pointer */
495+
current = state->stack;
385496

386497
/*
387498
* Perform quote formatting when the stack element is that of
388499
* a supporting atom. If nested then perform quote formatting
389500
* only on the topmost supporting atom.
390501
*/
391-
if (!state->stack->prev->prev) {
502+
if (!current->prev->prev) {
392503
quote_formatting(&s, current->output.buf, state->quote_style);
393504
strbuf_swap(&current->output, &s);
394505
}
@@ -1049,6 +1160,15 @@ static void populate_value(struct ref_array_item *ref)
10491160
} else if (!strcmp(name, "end")) {
10501161
v->handler = end_atom_handler;
10511162
continue;
1163+
} else if (!strcmp(name, "if")) {
1164+
v->handler = if_atom_handler;
1165+
continue;
1166+
} else if (!strcmp(name, "then")) {
1167+
v->handler = then_atom_handler;
1168+
continue;
1169+
} else if (!strcmp(name, "else")) {
1170+
v->handler = else_atom_handler;
1171+
continue;
10521172
} else
10531173
continue;
10541174

t/t6302-for-each-ref-filter.sh

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,80 @@ test_expect_success 'reverse version sort' '
327327
test_cmp expect actual
328328
'
329329

330+
test_expect_success 'improper usage of %(if), %(then), %(else) and %(end) atoms' '
331+
test_must_fail git for-each-ref --format="%(if)" &&
332+
test_must_fail git for-each-ref --format="%(then) %(end)" &&
333+
test_must_fail git for-each-ref --format="%(else) %(end)" &&
334+
test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
335+
test_must_fail git for-each-ref --format="%(if) %(then) %(then) %(end)" &&
336+
test_must_fail git for-each-ref --format="%(then) %(else) %(end)" &&
337+
test_must_fail git for-each-ref --format="%(if) %(else) %(end)" &&
338+
test_must_fail git for-each-ref --format="%(if) %(then) %(else)" &&
339+
test_must_fail git for-each-ref --format="%(if) %(else) %(then) %(end)" &&
340+
test_must_fail git for-each-ref --format="%(if) %(then) %(else) %(else) %(end)" &&
341+
test_must_fail git for-each-ref --format="%(if) %(end)"
342+
'
343+
344+
test_expect_success 'check %(if)...%(then)...%(end) atoms' '
345+
git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Author: %(authorname)%(end)" >actual &&
346+
cat >expect <<-\EOF &&
347+
refs/heads/master Author: A U Thor
348+
refs/heads/side Author: A U Thor
349+
refs/odd/spot Author: A U Thor
350+
refs/tags/annotated-tag
351+
refs/tags/doubly-annotated-tag
352+
refs/tags/doubly-signed-tag
353+
refs/tags/foo1.10 Author: A U Thor
354+
refs/tags/foo1.3 Author: A U Thor
355+
refs/tags/foo1.6 Author: A U Thor
356+
refs/tags/four Author: A U Thor
357+
refs/tags/one Author: A U Thor
358+
refs/tags/signed-tag
359+
refs/tags/three Author: A U Thor
360+
refs/tags/two Author: A U Thor
361+
EOF
362+
test_cmp expect actual
363+
'
364+
365+
test_expect_success 'check %(if)...%(then)...%(else)...%(end) atoms' '
366+
git for-each-ref --format="%(if)%(authorname)%(then)%(authorname)%(else)No author%(end): %(refname)" >actual &&
367+
cat >expect <<-\EOF &&
368+
A U Thor: refs/heads/master
369+
A U Thor: refs/heads/side
370+
A U Thor: refs/odd/spot
371+
No author: refs/tags/annotated-tag
372+
No author: refs/tags/doubly-annotated-tag
373+
No author: refs/tags/doubly-signed-tag
374+
A U Thor: refs/tags/foo1.10
375+
A U Thor: refs/tags/foo1.3
376+
A U Thor: refs/tags/foo1.6
377+
A U Thor: refs/tags/four
378+
A U Thor: refs/tags/one
379+
No author: refs/tags/signed-tag
380+
A U Thor: refs/tags/three
381+
A U Thor: refs/tags/two
382+
EOF
383+
test_cmp expect actual
384+
'
385+
test_expect_success 'ignore spaces in %(if) atom usage' '
386+
git for-each-ref --format="%(refname:short): %(if)%(HEAD)%(then)Head ref%(else)Not Head ref%(end)" >actual &&
387+
cat >expect <<-\EOF &&
388+
master: Head ref
389+
side: Not Head ref
390+
odd/spot: Not Head ref
391+
annotated-tag: Not Head ref
392+
doubly-annotated-tag: Not Head ref
393+
doubly-signed-tag: Not Head ref
394+
foo1.10: Not Head ref
395+
foo1.3: Not Head ref
396+
foo1.6: Not Head ref
397+
four: Not Head ref
398+
one: Not Head ref
399+
signed-tag: Not Head ref
400+
three: Not Head ref
401+
two: Not Head ref
402+
EOF
403+
test_cmp expect actual
404+
'
405+
330406
test_done

0 commit comments

Comments
 (0)