Re: [PATCH v7 01/17] ref-filter: implement %(if), %(then), and %(else) atoms

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Nov 8, 2016 at 12:11 PM, Karthik Nayak <karthik.188@xxxxxxxxx> wrote:
> From: Karthik Nayak <karthik.188@xxxxxxxxx>
>
> 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.
>

Ok, so I have only one minor nit, but otherwise this looks quite good
to me. A few comments explaining my understanding, but only one
suggested
change which is really a minor nit and not worth re-rolling just for it.

Thanks,
Jake

> Mentored-by: Christian Couder <christian.couder@xxxxxxxxx>
> Mentored-by: Matthieu Moy <matthieu.moy@xxxxxxxxxxxxxxx>
> Signed-off-by: Karthik Nayak <karthik.188@xxxxxxxxx>
> ---
>  Documentation/git-for-each-ref.txt |  40 +++++++++++
>  ref-filter.c                       | 133 +++++++++++++++++++++++++++++++++++--
>  t/t6302-for-each-ref-filter.sh     |  76 +++++++++++++++++++++
>  3 files changed, 242 insertions(+), 7 deletions(-)
>
> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
> index f57e69b..fed8126 100644
> --- a/Documentation/git-for-each-ref.txt
> +++ b/Documentation/git-for-each-ref.txt
> @@ -146,6 +146,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.
> @@ -181,6 +191,20 @@ 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 (i.e. one of
> +`--shell`, `--perl`, `--python`, `--tcl` is used), except for opening
> +atoms, replacement from every %(atom) is quoted when and only when it
> +appears at the top-level (that is, when it appears outside
> +%($open)...%(end)).
> +
> +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 its result is
> +quoted.
> +
>

Nice, I like the explanation above.

>  EXAMPLES
>  --------
> @@ -268,6 +292,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) %(color:red)Authored by: %(authorname)%(end)"
> +------------
> +
>  SEE ALSO
>  --------
>  linkgit:git-show-ref[1]
> diff --git a/ref-filter.c b/ref-filter.c
> index d4c2931..8c183a0 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -21,6 +21,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().
> @@ -203,6 +209,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 }
> @@ -210,7 +219,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;
>  };
>
> @@ -343,13 +352,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)
>  {

So we now have to pass an array of stacks to the end_align_handler
instead? Ok. But for align this is simple since it just expects a
singleton.

> -       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);
>  }
>
> @@ -363,6 +373,103 @@ 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);

So here, once we have a satisfied condition, we just drop the "else"
element entirely.

> +               } else {
> +                       strbuf_swap(&cur->output, &prev->output);
> +                       strbuf_reset(&cur->output);
> +                       pop_stack_element(&cur);

Otherwise, we swap our current value into the value of the previous
element, and then drop the current. This is a bit tricky, but it
works.

> +               }
> +       } else if (!if_then_else->condition_satisfied)

Minor nit. I'm not sure what standard we use here at Git, but
traditionally, I prefer to see { } blocks on all sections even if only
one of them needs it. (That is, only drop the braces when every
section is one line.) It also looks weird with a comment since it
appears as multiple lines to the reader. I think the braces improve
readability.

I don't know whether that's Git's code base standard or not, however.
It's not really worth a re-roll unless something else would need to
change.

> +               /*
> +                * No %(else) atom: just drop the %(then) branch if the
> +                * condition is not satisfied.
> +                */
> +               strbuf_reset(&cur->output);

Finally, if no else element, then we just reset the current pointer.

> +
> +       *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;
> +}
> +

Ok, so the new method is that to handle "if"s we push the sets onto
the stack and check their values. I like this, it makes things pretty
straight forward and simple. Allows for quite a bit of expression.

> +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);
> +}

So once we have a "%(then)" atom, we reset all the accumulated string
data we've gotten so far. Simple.

> +
> +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;
> +}

So for an else atom, we basically create another stack element on top
of the current one. Nice.

> +
>  static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
>  {
>         struct ref_formatting_stack *current = state->stack;
> @@ -370,14 +477,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);
>         }
> @@ -1029,6 +1139,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;
>
> diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
> index d0ab09f..fed3013 100755
> --- a/t/t6302-for-each-ref-filter.sh
> +++ b/t/t6302-for-each-ref-filter.sh
> @@ -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
> --
> 2.10.2
>



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]