{"thread":{"id":"40316","subject":"[PATCH v17 01/14] ref-filter: move `struct atom_value` to ref-filter.c","startedAt":"2015-09-10T15:48:16Z","lastAt":"2015-09-18T15:44:51Z","messageCount":56,"participants":["Karthik Nayak","Matthieu Moy","Junio C Hamano","Eric Sunshine","John Keeping"],"isPatch":true,"patchVersion":17,"patchTotal":14},"messages":[{"id":"269685","messageId":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":null,"subject":"[PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:16Z","receivedAt":"2015-09-10T15:48:16Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"This is part of the series of unifying the code used by\n\"git tag -l, git branch -l, git for-each-ref\".\n\nThe previous version can be found here (version 16):\narticle.gmane.org/gmane.comp.version-control.git/277394\n\nChanges in this version:\n* The arguments of the %(align) atom are interchangeable.\n* Small grammatical changes.\n* Small changes in the tests to reflect changes in the align\natom code.\n\nKarthik Nayak (14):\n  ref-filter: move `struct atom_value` to ref-filter.c\n  ref-filter: introduce ref_formatting_state and ref_formatting_stack\n  utf8: add function to align a string into given strbuf\n  ref-filter: introduce handler function for each atom\n  ref-filter: introduce match_atom_name()\n  ref-filter: implement an `align` atom\n  ref-filter: add option to filter out tags, branches and remotes\n  ref-filter: add support for %(contents:lines=X)\n  ref-filter: add support to sort by version\n  ref-filter: add option to match literal pattern\n  tag.c: use 'ref-filter' data structures\n  tag.c: use 'ref-filter' APIs\n  tag.c: implement '--format' option\n  tag.c: implement '--merged' and '--no-merged' options\n\n Documentation/git-for-each-ref.txt |  17 +-\n Documentation/git-tag.txt          |  27 ++-\n builtin/for-each-ref.c             |   1 +\n builtin/tag.c                      | 369 ++++++---------------------------\n ref-filter.c                       | 412 ++++++++++++++++++++++++++++++++-----\n ref-filter.h                       |  25 ++-\n refs.c                             |   9 +\n refs.h                             |   1 +\n t/t6302-for-each-ref-filter.sh     | 174 ++++++++++++++++\n t/t7004-tag.sh                     |  47 ++++-\n utf8.c                             |  21 ++\n utf8.h                             |  15 ++\n 12 files changed, 742 insertions(+), 376 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex c5154bb..16b4ac5 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -128,14 +128,15 @@ color::\n \tare described in `color.branch.*`.\n \n align::\n-\tLeft-, middle-, or right-align the content between %(align:...)\n-\tand %(end). Followed by `:<width>,<position>`, where the\n-\t`<position>` is either left, right or middle and `<width>` is\n-\tthe total length of the content with alignment. If the\n-\tcontents length is more than the width then no alignment is\n-\tperformed. If used with '--quote' everything in between\n-\t%(align:...) and %(end) is quoted, but if nested then only the\n-\ttopmost level performs quoting.\n+\tLeft-, middle-, or right-align the content between\n+\t%(align:...) and %(end). The \"align:\" is followed by `<width>`\n+\tand `<position>` in any order separated by a comma, where the\n+\t`<position>` is either left, right or middle, default being\n+\tleft and `<width>` is the total length of the content with\n+\talignment. If the contents length is more than the width then\n+\tno alignment is performed. If used with '--quote' everything\n+\tin between %(align:...) and %(end) is quoted, but if nested\n+\tthen only the topmost level performs quoting.\n \n In addition to the above, for commit and tag objects, the header\n field names (`tree`, `parent`, `object`, `type`, and `tag`) can\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f55dfda..081fe84 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -42,10 +42,11 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con\n \t\tfilter->lines = 0;\n \n \tif (!format) {\n-\t\tif (filter->lines)\n-\t\t\tformat = to_free = xstrfmt(\"%%(align:15,left)%%(refname:short)%%(end) \"\n-\t\t\t\t\t\t   \"%%(contents:lines=%d)\", filter->lines);\n-\t\telse\n+\t\tif (filter->lines) {\n+\t\t\tformat = xstrfmt(\"%s %%(contents:lines=%d)\",\n+\t\t\t\t\t \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n+\t\t\tto_free = format;\n+\t\t} else\n \t\t\tformat = \"%(refname:short)\";\n \t}\n \ndiff --git a/ref-filter.c b/ref-filter.c\nindex e3024d3..59716db 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -274,7 +274,7 @@ static int match_atom_name(const char *name, const char *atom_name, const char *\n \t}\n \tif (body[0] != ':')\n \t\treturn 0; /* \"atom_namefoo\" is not \"atom_name\" or \"atom_name:...\" */\n-\t*val = body + 1; /* \"atomname:val\" */\n+\t*val = body + 1; /* \"atom_name:val\" */\n \treturn 1;\n }\n \n@@ -884,43 +884,40 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\tcontinue;\n \t\t} else if (match_atom_name(name, \"align\", &valp)) {\n \t\t\tstruct align *align = &v->u.align;\n-\t\t\tstruct strbuf **s;\n+\t\t\tstruct strbuf **s, **to_free;\n+\t\t\tint width = -1;\n \n \t\t\tif (!valp)\n-\t\t\t\tdie(_(\"expected format: %%(align:<width>, <position>)\"));\n+\t\t\t\tdie(_(\"expected format: %%(align:<width>,<position>)\"));\n \n \t\t\t/*\n \t\t\t * TODO: Implement a function similar to strbuf_split_str()\n-\t\t\t * which would strip the terminator at the end.\n+\t\t\t * which would omit the separator from the end of each value.\n \t\t\t */\n-\t\t\ts = strbuf_split_str(valp, ',', 0);\n-\n-\t\t\t/* If the position is given trim the ',' from the first strbuf */\n-\t\t\tif (s[1])\n-\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n-\t\t\tif (s[2])\n-\t\t\t\tdie(_(\"align:<width>,<position> followed by garbage: %s\"), s[2]->buf);\n-\n-\t\t\tif (strtoul_ui(s[0]->buf, 10, &align->width))\n-\t\t\t\tdie(_(\"positive width expected align:%s\"), s[0]->buf);\n-\n-\t\t\t/*\n-\t\t\t * TODO: Implement a more general check, so that the values\n-\t\t\t * do not always have to be in a specific order.\n-\t\t\t */\n-\t\t\tif (!s[1])\n-\t\t\t\talign->position = ALIGN_LEFT;\n-\t\t\telse if (!strcmp(s[1]->buf, \"left\"))\n-\t\t\t\talign->position = ALIGN_LEFT;\n-\t\t\telse if (!strcmp(s[1]->buf, \"right\"))\n-\t\t\t\talign->position = ALIGN_RIGHT;\n-\t\t\telse if (!strcmp(s[1]->buf, \"middle\"))\n-\t\t\t\talign->position = ALIGN_MIDDLE;\n-\t\t\telse\n-\t\t\t\tdie(_(\"improper format entered align:%s\"), s[1]->buf);\n-\n-\t\t\tstrbuf_list_free(s);\n+\t\t\ts = to_free = strbuf_split_str(valp, ',', 0);\n+\n+\t\t\talign->position = ALIGN_LEFT;\n+\n+\t\t\twhile (*s) {\n+\t\t\t\tif (s[1])\n+\t\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n+\t\t\t\tif (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n+\t\t\t\t\t;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"left\"))\n+\t\t\t\t\talign->position = ALIGN_LEFT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"right\"))\n+\t\t\t\t\talign->position = ALIGN_RIGHT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"middle\"))\n+\t\t\t\t\talign->position = ALIGN_MIDDLE;\n+\t\t\t\telse\n+\t\t\t\t\tdie(_(\"improper format entered align:%s\"), s[0]->buf);\n+\t\t\t\ts++;\n+\t\t\t}\n \n+\t\t\tif (width < 0)\n+\t\t\t\tdie(_(\"positive width expected with the %%(align) atom\"));\n+\t\t\talign->width = width;\n+\t\t\tstrbuf_list_free(to_free);\n \t\t\tv->handler = align_atom_handler;\n \t\t\tcontinue;\n \t\t} else if (!strcmp(name, \"end\")) {\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 4bc1055..fe4796c 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -85,7 +85,7 @@ test_expect_success '%(color) must fail' '\n \ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n '\n \n-test_expect_success 'left alignment' '\n+test_expect_success 'left alignment is default' '\n \tcat >expect <<-\\EOF &&\n \trefname is refs/heads/master  |refs/heads/master\n \trefname is refs/heads/side    |refs/heads/side\n@@ -97,7 +97,7 @@ test_expect_success 'left alignment' '\n \trefname is refs/tags/three    |refs/tags/three\n \trefname is refs/tags/two      |refs/tags/two\n \tEOF\n-\tgit for-each-ref --format=\"%(align:30,left)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\tgit for-each-ref --format=\"%(align:30)refname is %(refname)%(end)|%(refname)\" >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -113,7 +113,7 @@ test_expect_success 'middle alignment' '\n \t|  refname is refs/tags/three  |refs/tags/three\n \t|   refname is refs/tags/two   |refs/tags/two\n \tEOF\n-\tgit for-each-ref --format=\"|%(align:30,middle)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\tgit for-each-ref --format=\"|%(align:middle,30)refname is %(refname)%(end)|%(refname)\" >actual &&\n \ttest_cmp expect actual\n '\n \n@@ -137,17 +137,17 @@ test_expect_success 'right alignment' '\n \n test_expect_success 'alignment with format quote' \"\n \tcat >expect <<-\\EOF &&\n-\t|'       master| A U Thor       '|\n-\t|'        side| A U Thor        '|\n-\t|'      odd/spot| A U Thor      '|\n-\t|'         double-tag|          '|\n-\t|'        four| A U Thor        '|\n-\t|'        one| A U Thor         '|\n-\t|'         signed-tag|          '|\n-\t|'       three| A U Thor        '|\n-\t|'        two| A U Thor         '|\n+\t|'      '\\''master| A U Thor'\\''      '|\n+\t|'       '\\''side| A U Thor'\\''       '|\n+\t|'     '\\''odd/spot| A U Thor'\\''     '|\n+\t|'        '\\''double-tag| '\\''        '|\n+\t|'       '\\''four| A U Thor'\\''       '|\n+\t|'       '\\''one| A U Thor'\\''        '|\n+\t|'        '\\''signed-tag| '\\''        '|\n+\t|'      '\\''three| A U Thor'\\''       '|\n+\t|'       '\\''two| A U Thor'\\''        '|\n \tEOF\n-\tgit for-each-ref --shell --format='|%(align:30,middle)%(refname:short)| %(authorname)%(end)|' >actual &&\n+\tgit for-each-ref --shell --format=\\\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\\\" >actual &&\n \ttest_cmp expect actual\n \"\n \n-- \n2.5.1\n"},{"id":"269684","messageId":"1441900110-4015-2-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 01/14] ref-filter: move `struct atom_value` to ref-filter.c","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:17Z","receivedAt":"2015-09-10T15:48:17Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Since atom_value is only required for the internal working of\nref-filter it doesn't belong in the public header.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n ref-filter.c | 5 +++++\n ref-filter.h | 5 +----\n 2 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 46963a5..e53c77e 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -55,6 +55,11 @@ static struct {\n \t{ \"color\" },\n };\n \n+struct atom_value {\n+\tconst char *s;\n+\tunsigned long ul; /* used for sorting when not FIELD_STR */\n+};\n+\n /*\n  * An atom is a valid field atom listed above, possibly prefixed with\n  * a \"*\" to denote deref_tag().\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 6bf27d8..45026d0 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -16,10 +16,7 @@\n #define FILTER_REFS_INCLUDE_BROKEN 0x1\n #define FILTER_REFS_ALL 0x2\n \n-struct atom_value {\n-\tconst char *s;\n-\tunsigned long ul; /* used for sorting when not FIELD_STR */\n-};\n+struct atom_value;\n \n struct ref_sorting {\n \tstruct ref_sorting *next;\n-- \n2.5.1\n"},{"id":"269686","messageId":"1441900110-4015-3-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 02/14] ref-filter: introduce ref_formatting_state and ref_formatting_stack","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:18Z","receivedAt":"2015-09-10T15:48:18Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Introduce ref_formatting_state which will hold the formatted output\nstrbuf instead of directly printing to stdout. This will help us in\ncreating modifier atoms which modify the format specified before\nprinting to stdout.\n\nImplement a stack machinery for ref_formatting_state, this allows us\nto push and pop elements onto the stack. Whenever we pop an element\nfrom the stack, the strbuf from that element is appended to the strbuf\nof the next element on the stack, this will allow us to support\nnesting of modifier atoms.\n\nRename some functions to reflect the changes made:\nprint_value() -> append_atom()\nemit()        -> append_literal()\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n ref-filter.c | 78 +++++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 59 insertions(+), 19 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex e53c77e..432cea0 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -55,6 +55,18 @@ static struct {\n \t{ \"color\" },\n };\n \n+#define REF_FORMATTING_STATE_INIT  { 0, NULL }\n+\n+struct ref_formatting_stack {\n+\tstruct ref_formatting_stack *prev;\n+\tstruct strbuf output;\n+};\n+\n+struct ref_formatting_state {\n+\tint quote_style;\n+\tstruct ref_formatting_stack *stack;\n+};\n+\n struct atom_value {\n \tconst char *s;\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n@@ -129,6 +141,27 @@ int parse_ref_filter_atom(const char *atom, const char *ep)\n \treturn at;\n }\n \n+static void push_stack_element(struct ref_formatting_stack **stack)\n+{\n+\tstruct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));\n+\n+\tstrbuf_init(&s->output, 0);\n+\ts->prev = *stack;\n+\t*stack = s;\n+}\n+\n+static void pop_stack_element(struct ref_formatting_stack **stack)\n+{\n+\tstruct ref_formatting_stack *current = *stack;\n+\tstruct ref_formatting_stack *prev = current->prev;\n+\n+\tif (prev)\n+\t\tstrbuf_addbuf(&prev->output, &current->output);\n+\tstrbuf_release(&current->output);\n+\tfree(current);\n+\t*stack = prev;\n+}\n+\n /*\n  * In a format string, find the next occurrence of %(atom).\n  */\n@@ -1195,30 +1228,27 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)\n \tqsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);\n }\n \n-static void print_value(struct atom_value *v, int quote_style)\n+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)\n {\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tswitch (quote_style) {\n+\tstruct strbuf *s = &state->stack->output;\n+\n+\tswitch (state->quote_style) {\n \tcase QUOTE_NONE:\n-\t\tfputs(v->s, stdout);\n+\t\tstrbuf_addstr(s, v->s);\n \t\tbreak;\n \tcase QUOTE_SHELL:\n-\t\tsq_quote_buf(&sb, v->s);\n+\t\tsq_quote_buf(s, v->s);\n \t\tbreak;\n \tcase QUOTE_PERL:\n-\t\tperl_quote_buf(&sb, v->s);\n+\t\tperl_quote_buf(s, v->s);\n \t\tbreak;\n \tcase QUOTE_PYTHON:\n-\t\tpython_quote_buf(&sb, v->s);\n+\t\tpython_quote_buf(s, v->s);\n \t\tbreak;\n \tcase QUOTE_TCL:\n-\t\ttcl_quote_buf(&sb, v->s);\n+\t\ttcl_quote_buf(s, v->s);\n \t\tbreak;\n \t}\n-\tif (quote_style != QUOTE_NONE) {\n-\t\tfputs(sb.buf, stdout);\n-\t\tstrbuf_release(&sb);\n-\t}\n }\n \n static int hex1(char ch)\n@@ -1239,8 +1269,10 @@ static int hex2(const char *cp)\n \t\treturn -1;\n }\n \n-static void emit(const char *cp, const char *ep)\n+static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)\n {\n+\tstruct strbuf *s = &state->stack->output;\n+\n \twhile (*cp && (!ep || cp < ep)) {\n \t\tif (*cp == '%') {\n \t\t\tif (cp[1] == '%')\n@@ -1248,13 +1280,13 @@ static void emit(const char *cp, const char *ep)\n \t\t\telse {\n \t\t\t\tint ch = hex2(cp + 1);\n \t\t\t\tif (0 <= ch) {\n-\t\t\t\t\tputchar(ch);\n+\t\t\t\t\tstrbuf_addch(s, ch);\n \t\t\t\t\tcp += 3;\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t}\n \t\t}\n-\t\tputchar(*cp);\n+\t\tstrbuf_addch(s, *cp);\n \t\tcp++;\n \t}\n }\n@@ -1262,19 +1294,24 @@ static void emit(const char *cp, const char *ep)\n void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)\n {\n \tconst char *cp, *sp, *ep;\n+\tstruct strbuf *final_buf;\n+\tstruct ref_formatting_state state = REF_FORMATTING_STATE_INIT;\n+\n+\tstate.quote_style = quote_style;\n+\tpush_stack_element(&state.stack);\n \n \tfor (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {\n \t\tstruct atom_value *atomv;\n \n \t\tep = strchr(sp, ')');\n \t\tif (cp < sp)\n-\t\t\temit(cp, sp);\n+\t\t\tappend_literal(cp, sp, &state);\n \t\tget_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);\n-\t\tprint_value(atomv, quote_style);\n+\t\tappend_atom(atomv, &state);\n \t}\n \tif (*cp) {\n \t\tsp = cp + strlen(cp);\n-\t\temit(cp, sp);\n+\t\tappend_literal(cp, sp, &state);\n \t}\n \tif (need_color_reset_at_eol) {\n \t\tstruct atom_value resetv;\n@@ -1283,8 +1320,11 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu\n \t\tif (color_parse(\"reset\", color) < 0)\n \t\t\tdie(\"BUG: couldn't parse 'reset' as a color\");\n \t\tresetv.s = color;\n-\t\tprint_value(&resetv, quote_style);\n+\t\tappend_atom(&resetv, &state);\n \t}\n+\tfinal_buf = &state.stack->output;\n+\tfwrite(final_buf->buf, 1, final_buf->len, stdout);\n+\tpop_stack_element(&state.stack);\n \tputchar('\\n');\n }\n \n-- \n2.5.1\n"},{"id":"269687","messageId":"1441900110-4015-4-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 03/14] utf8: add function to align a string into given strbuf","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:19Z","receivedAt":"2015-09-10T15:48:19Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Add strbuf_utf8_align() which will align a given string into a strbuf\nas per given align_type and width. If the width is greater than the\nstring length then no alignment is performed.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n utf8.c | 21 +++++++++++++++++++++\n utf8.h | 15 +++++++++++++++\n 2 files changed, 36 insertions(+)\n\ndiff --git a/utf8.c b/utf8.c\nindex 28e6d76..00e10c8 100644\n--- a/utf8.c\n+++ b/utf8.c\n@@ -644,3 +644,24 @@ int skip_utf8_bom(char **text, size_t len)\n \t*text += strlen(utf8_bom);\n \treturn 1;\n }\n+\n+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,\n+\t\t       const char *s)\n+{\n+\tint slen = strlen(s);\n+\tint display_len = utf8_strnwidth(s, slen, 0);\n+\tint utf8_compensation = slen - display_len;\n+\n+\tif (display_len >= width) {\n+\t\tstrbuf_addstr(buf, s);\n+\t\treturn;\n+\t}\n+\n+\tif (position == ALIGN_LEFT)\n+\t\tstrbuf_addf(buf, \"%-*s\", width + utf8_compensation, s);\n+\telse if (position == ALIGN_MIDDLE) {\n+\t\tint left = (width - display_len) / 2;\n+\t\tstrbuf_addf(buf, \"%*s%-*s\", left, \"\", width - left + utf8_compensation, s);\n+\t} else if (position == ALIGN_RIGHT)\n+\t\tstrbuf_addf(buf, \"%*s\", width + utf8_compensation, s);\n+}\ndiff --git a/utf8.h b/utf8.h\nindex 5a9e94b..7930b44 100644\n--- a/utf8.h\n+++ b/utf8.h\n@@ -55,4 +55,19 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);\n  */\n int is_hfs_dotgit(const char *path);\n \n+typedef enum {\n+\tALIGN_LEFT,\n+\tALIGN_MIDDLE,\n+\tALIGN_RIGHT\n+} align_type;\n+\n+/*\n+ * Align the string given and store it into a strbuf as per the\n+ * 'position' and 'width'. If the given string length is larger than\n+ * 'width' than then the input string is not truncated and no\n+ * alignment is done.\n+ */\n+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,\n+\t\t       const char *s);\n+\n #endif\n-- \n2.5.1\n"},{"id":"269690","messageId":"1441900110-4015-5-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 04/14] ref-filter: introduce handler function for each atom","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:20Z","receivedAt":"2015-09-10T15:48:20Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Introduce a handler function for each atom, which is called when the\natom is processed in show_ref_array_item().\n\nIn this context make append_atom() as the default handler function and\nextract quote_formatting() out of append_atom(). Bump this to the top.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n ref-filter.c | 54 ++++++++++++++++++++++++++++++------------------------\n 1 file changed, 30 insertions(+), 24 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 432cea0..a993216 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -69,6 +69,7 @@ struct ref_formatting_state {\n \n struct atom_value {\n \tconst char *s;\n+\tvoid (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n };\n \n@@ -141,6 +142,32 @@ int parse_ref_filter_atom(const char *atom, const char *ep)\n \treturn at;\n }\n \n+static void quote_formatting(struct strbuf *s, const char *str, int quote_style)\n+{\n+\tswitch (quote_style) {\n+\tcase QUOTE_NONE:\n+\t\tstrbuf_addstr(s, str);\n+\t\tbreak;\n+\tcase QUOTE_SHELL:\n+\t\tsq_quote_buf(s, str);\n+\t\tbreak;\n+\tcase QUOTE_PERL:\n+\t\tperl_quote_buf(s, str);\n+\t\tbreak;\n+\tcase QUOTE_PYTHON:\n+\t\tpython_quote_buf(s, str);\n+\t\tbreak;\n+\tcase QUOTE_TCL:\n+\t\ttcl_quote_buf(s, str);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)\n+{\n+\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+}\n+\n static void push_stack_element(struct ref_formatting_stack **stack)\n {\n \tstruct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));\n@@ -662,6 +689,8 @@ static void populate_value(struct ref_array_item *ref)\n \t\tconst char *formatp;\n \t\tstruct branch *branch = NULL;\n \n+\t\tv->handler = append_atom;\n+\n \t\tif (*name == '*') {\n \t\t\tderef = 1;\n \t\t\tname++;\n@@ -1228,29 +1257,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)\n \tqsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);\n }\n \n-static void append_atom(struct atom_value *v, struct ref_formatting_state *state)\n-{\n-\tstruct strbuf *s = &state->stack->output;\n-\n-\tswitch (state->quote_style) {\n-\tcase QUOTE_NONE:\n-\t\tstrbuf_addstr(s, v->s);\n-\t\tbreak;\n-\tcase QUOTE_SHELL:\n-\t\tsq_quote_buf(s, v->s);\n-\t\tbreak;\n-\tcase QUOTE_PERL:\n-\t\tperl_quote_buf(s, v->s);\n-\t\tbreak;\n-\tcase QUOTE_PYTHON:\n-\t\tpython_quote_buf(s, v->s);\n-\t\tbreak;\n-\tcase QUOTE_TCL:\n-\t\ttcl_quote_buf(s, v->s);\n-\t\tbreak;\n-\t}\n-}\n-\n static int hex1(char ch)\n {\n \tif ('0' <= ch && ch <= '9')\n@@ -1307,7 +1313,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu\n \t\tif (cp < sp)\n \t\t\tappend_literal(cp, sp, &state);\n \t\tget_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);\n-\t\tappend_atom(atomv, &state);\n+\t\tatomv->handler(atomv, &state);\n \t}\n \tif (*cp) {\n \t\tsp = cp + strlen(cp);\n-- \n2.5.1\n"},{"id":"269688","messageId":"1441900110-4015-6-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:21Z","receivedAt":"2015-09-10T15:48:21Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Introduce match_atom_name() which helps in checking if a particular\natom is the atom we're looking for and if it has a value attached to\nit or not.\n\nUse it instead of starts_with() for checking the value of %(color:...)\natom. Write a test for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nThanks-to: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n ref-filter.c                   | 23 +++++++++++++++++++++--\n t/t6302-for-each-ref-filter.sh |  4 ++++\n 2 files changed, 25 insertions(+), 2 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex a993216..70d36fe 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -189,6 +189,22 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n \t*stack = prev;\n }\n \n+static int match_atom_name(const char *name, const char *atom_name, const char **val)\n+{\n+\tconst char *body;\n+\n+\tif (!skip_prefix(name, atom_name, &body))\n+\t\treturn 0; /* doesn't even begin with \"atom_name\" */\n+\tif (!body[0] || !body[1]) {\n+\t\t*val = NULL; /* %(atom_name) and no customization */\n+\t\treturn 1;\n+\t}\n+\tif (body[0] != ':')\n+\t\treturn 0; /* \"atom_namefoo\" is not \"atom_name\" or \"atom_name:...\" */\n+\t*val = body + 1; /* \"atom_name:val\" */\n+\treturn 1;\n+}\n+\n /*\n  * In a format string, find the next occurrence of %(atom).\n  */\n@@ -687,6 +703,7 @@ static void populate_value(struct ref_array_item *ref)\n \t\tint deref = 0;\n \t\tconst char *refname;\n \t\tconst char *formatp;\n+\t\tconst char *valp;\n \t\tstruct branch *branch = NULL;\n \n \t\tv->handler = append_atom;\n@@ -721,10 +738,12 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\trefname = branch_get_push(branch, NULL);\n \t\t\tif (!refname)\n \t\t\t\tcontinue;\n-\t\t} else if (starts_with(name, \"color:\")) {\n+\t\t} else if (match_atom_name(name, \"color\", &valp)) {\n \t\t\tchar color[COLOR_MAXLEN] = \"\";\n \n-\t\t\tif (color_parse(name + 6, color) < 0)\n+\t\t\tif (!valp)\n+\t\t\t\tdie(_(\"expected format: %%(color:<color>)\"));\n+\t\t\tif (color_parse(valp, color) < 0)\n \t\t\t\tdie(_(\"unable to parse format\"));\n \t\t\tv->s = xstrdup(color);\n \t\t\tcontinue;\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 505a360..c4f0378 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -81,4 +81,8 @@ test_expect_success 'filtering with --contains' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '%(color) must fail' '\n+\ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269689","messageId":"1441900110-4015-7-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 06/14] ref-filter: implement an `align` atom","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:22Z","receivedAt":"2015-09-10T15:48:22Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Implement an `align` atom which left-, middle-, or right-aligns the\ncontent between %(align:...) and %(end).\n\nThe \"align:\" is followed by `<width>` and `<position>` in any order\nseparated by a comma, where the `<position>` is either left, right or\nmiddle, default being left and `<width>` is the total length of the\ncontent with alignment. If the contents length is more than the width\nthen no alignment is performed.  e.g. to align a refname atom to the\nmiddle with a total width of 40 we can do:\n--format=\"%(align:middle,40)%(refname)%(end)\".\n\nWe introduce an `at_end` function for each element of the stack which\nis to be called when the `end` atom is encountered. Using this we\nimplement end_align_handler() for the `align` atom, this aligns the\nfinal strbuf by calling `strbuf_utf8_align()` from utf8.c.\n\nEnsure that quote formatting is performed on the whole of\n%(align:...)...%(end) rather than individual atoms inside. We skip\nquote formatting for individual atoms when the current stack element\nis handling an %(align:...) atom and perform quote formatting at the\nend when we encounter the %(end) atom of the second element of then\nstack.\n\nAdd documentation and tests for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  11 ++++\n ref-filter.c                       | 109 ++++++++++++++++++++++++++++++++++++-\n t/t6302-for-each-ref-filter.sh     |  82 ++++++++++++++++++++++++++++\n 3 files changed, 201 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex e49d578..3a271bf 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -127,6 +127,17 @@ color::\n \tChange output color.  Followed by `:<colorname>`, where names\n \tare described in `color.branch.*`.\n \n+align::\n+\tLeft-, middle-, or right-align the content between\n+\t%(align:...) and %(end). The \"align:\" is followed by `<width>`\n+\tand `<position>` in any order separated by a comma, where the\n+\t`<position>` is either left, right or middle, default being\n+\tleft and `<width>` is the total length of the content with\n+\talignment. If the contents length is more than the width then\n+\tno alignment is performed. If used with '--quote' everything\n+\tin between %(align:...) and %(end) is quoted, but if nested\n+\tthen only the topmost level performs quoting.\n+\n In addition to the above, for commit and tag objects, the header\n field names (`tree`, `parent`, `object`, `type`, and `tag`) can\n be used to specify the value in the header field.\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 70d36fe..b8f719c 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -10,6 +10,7 @@\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"revision.h\"\n+#include \"utf8.h\"\n \n typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;\n \n@@ -53,13 +54,22 @@ static struct {\n \t{ \"flag\" },\n \t{ \"HEAD\" },\n \t{ \"color\" },\n+\t{ \"align\" },\n+\t{ \"end\" },\n };\n \n #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n \n+struct align {\n+\talign_type position;\n+\tunsigned int width;\n+};\n+\n struct ref_formatting_stack {\n \tstruct ref_formatting_stack *prev;\n \tstruct strbuf output;\n+\tvoid (*at_end)(struct ref_formatting_stack *stack);\n+\tvoid *at_end_data;\n };\n \n struct ref_formatting_state {\n@@ -69,6 +79,9 @@ struct ref_formatting_state {\n \n struct atom_value {\n \tconst char *s;\n+\tunion {\n+\t\tstruct align align;\n+\t} u;\n \tvoid (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n };\n@@ -165,7 +178,16 @@ static void quote_formatting(struct strbuf *s, const char *str, int quote_style)\n \n static void append_atom(struct atom_value *v, struct ref_formatting_state *state)\n {\n-\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\t/*\n+\t * Quote formatting is only done when the stack has a single\n+\t * element. Otherwise quote formatting is done on the\n+\t * element's entire output strbuf when the %(end) atom is\n+\t * encountered.\n+\t */\n+\tif (!state->stack->prev)\n+\t\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\telse\n+\t\tstrbuf_addstr(&state->stack->output, v->s);\n }\n \n static void push_stack_element(struct ref_formatting_stack **stack)\n@@ -189,6 +211,48 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n \t*stack = prev;\n }\n \n+static void end_align_handler(struct ref_formatting_stack *stack)\n+{\n+\tstruct align *align = (struct align *)stack->at_end_data;\n+\tstruct strbuf s = STRBUF_INIT;\n+\n+\tstrbuf_utf8_align(&s, align->position, align->width, stack->output.buf);\n+\tstrbuf_swap(&stack->output, &s);\n+\tstrbuf_release(&s);\n+}\n+\n+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)\n+{\n+\tstruct ref_formatting_stack *new;\n+\n+\tpush_stack_element(&state->stack);\n+\tnew = state->stack;\n+\tnew->at_end = end_align_handler;\n+\tnew->at_end_data = &atomv->u.align;\n+}\n+\n+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)\n+{\n+\tstruct ref_formatting_stack *current = state->stack;\n+\tstruct strbuf s = STRBUF_INIT;\n+\n+\tif (!current->at_end)\n+\t\tdie(_(\"format: %%(end) atom used without corresponding atom\"));\n+\tcurrent->at_end(current);\n+\n+\t/*\n+\t * Perform quote formatting when the stack element is that of\n+\t * a supporting atom. If nested then perform quote formatting\n+\t * only on the topmost supporting atom.\n+\t */\n+\tif (!state->stack->prev->prev) {\n+\t\tquote_formatting(&s, current->output.buf, state->quote_style);\n+\t\tstrbuf_swap(&current->output, &s);\n+\t}\n+\tstrbuf_release(&s);\n+\tpop_stack_element(&state->stack);\n+}\n+\n static int match_atom_name(const char *name, const char *atom_name, const char **val)\n {\n \tconst char *body;\n@@ -773,6 +837,47 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\telse\n \t\t\t\tv->s = \" \";\n \t\t\tcontinue;\n+\t\t} else if (match_atom_name(name, \"align\", &valp)) {\n+\t\t\tstruct align *align = &v->u.align;\n+\t\t\tstruct strbuf **s, **to_free;\n+\t\t\tint width = -1;\n+\n+\t\t\tif (!valp)\n+\t\t\t\tdie(_(\"expected format: %%(align:<width>,<position>)\"));\n+\n+\t\t\t/*\n+\t\t\t * TODO: Implement a function similar to strbuf_split_str()\n+\t\t\t * which would omit the separator from the end of each value.\n+\t\t\t */\n+\t\t\ts = to_free = strbuf_split_str(valp, ',', 0);\n+\n+\t\t\talign->position = ALIGN_LEFT;\n+\n+\t\t\twhile (*s) {\n+\t\t\t\tif (s[1])\n+\t\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n+\t\t\t\tif (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n+\t\t\t\t\t;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"left\"))\n+\t\t\t\t\talign->position = ALIGN_LEFT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"right\"))\n+\t\t\t\t\talign->position = ALIGN_RIGHT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"middle\"))\n+\t\t\t\t\talign->position = ALIGN_MIDDLE;\n+\t\t\t\telse\n+\t\t\t\t\tdie(_(\"improper format entered align:%s\"), s[0]->buf);\n+\t\t\t\ts++;\n+\t\t\t}\n+\n+\t\t\tif (width < 0)\n+\t\t\t\tdie(_(\"positive width expected with the %%(align) atom\"));\n+\t\t\talign->width = width;\n+\t\t\tstrbuf_list_free(to_free);\n+\t\t\tv->handler = align_atom_handler;\n+\t\t\tcontinue;\n+\t\t} else if (!strcmp(name, \"end\")) {\n+\t\t\tv->handler = end_atom_handler;\n+\t\t\tcontinue;\n \t\t} else\n \t\t\tcontinue;\n \n@@ -1347,6 +1452,8 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu\n \t\tresetv.s = color;\n \t\tappend_atom(&resetv, &state);\n \t}\n+\tif (state.stack->prev)\n+\t\tdie(_(\"format: %%(end) atom missing\"));\n \tfinal_buf = &state.stack->output;\n \tfwrite(final_buf->buf, 1, final_buf->len, stdout);\n \tpop_stack_element(&state.stack);\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex c4f0378..f596035 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -85,4 +85,86 @@ test_expect_success '%(color) must fail' '\n \ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n '\n \n+test_expect_success 'left alignment is default' '\n+\tcat >expect <<-\\EOF &&\n+\trefname is refs/heads/master  |refs/heads/master\n+\trefname is refs/heads/side    |refs/heads/side\n+\trefname is refs/odd/spot      |refs/odd/spot\n+\trefname is refs/tags/double-tag|refs/tags/double-tag\n+\trefname is refs/tags/four     |refs/tags/four\n+\trefname is refs/tags/one      |refs/tags/one\n+\trefname is refs/tags/signed-tag|refs/tags/signed-tag\n+\trefname is refs/tags/three    |refs/tags/three\n+\trefname is refs/tags/two      |refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"%(align:30)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'middle alignment' '\n+\tcat >expect <<-\\EOF &&\n+\t| refname is refs/heads/master |refs/heads/master\n+\t|  refname is refs/heads/side  |refs/heads/side\n+\t|   refname is refs/odd/spot   |refs/odd/spot\n+\t|refname is refs/tags/double-tag|refs/tags/double-tag\n+\t|  refname is refs/tags/four   |refs/tags/four\n+\t|   refname is refs/tags/one   |refs/tags/one\n+\t|refname is refs/tags/signed-tag|refs/tags/signed-tag\n+\t|  refname is refs/tags/three  |refs/tags/three\n+\t|   refname is refs/tags/two   |refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"|%(align:middle,30)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'right alignment' '\n+\tcat >expect <<-\\EOF &&\n+\t|  refname is refs/heads/master|refs/heads/master\n+\t|    refname is refs/heads/side|refs/heads/side\n+\t|      refname is refs/odd/spot|refs/odd/spot\n+\t|refname is refs/tags/double-tag|refs/tags/double-tag\n+\t|     refname is refs/tags/four|refs/tags/four\n+\t|      refname is refs/tags/one|refs/tags/one\n+\t|refname is refs/tags/signed-tag|refs/tags/signed-tag\n+\t|    refname is refs/tags/three|refs/tags/three\n+\t|      refname is refs/tags/two|refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"|%(align:30,right)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+# Individual atoms inside %(align:...) and %(end) must not be quoted.\n+\n+test_expect_success 'alignment with format quote' \"\n+\tcat >expect <<-\\EOF &&\n+\t|'      '\\''master| A U Thor'\\''      '|\n+\t|'       '\\''side| A U Thor'\\''       '|\n+\t|'     '\\''odd/spot| A U Thor'\\''     '|\n+\t|'        '\\''double-tag| '\\''        '|\n+\t|'       '\\''four| A U Thor'\\''       '|\n+\t|'       '\\''one| A U Thor'\\''        '|\n+\t|'        '\\''signed-tag| '\\''        '|\n+\t|'      '\\''three| A U Thor'\\''       '|\n+\t|'       '\\''two| A U Thor'\\''        '|\n+\tEOF\n+\tgit for-each-ref --shell --format=\\\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\\\" >actual &&\n+\ttest_cmp expect actual\n+\"\n+\n+test_expect_success 'nested alignment with quote formatting' \"\n+\tcat >expect <<-\\EOF &&\n+\t|'         master               '|\n+\t|'           side               '|\n+\t|'       odd/spot               '|\n+\t|'     double-tag               '|\n+\t|'           four               '|\n+\t|'            one               '|\n+\t|'     signed-tag               '|\n+\t|'          three               '|\n+\t|'            two               '|\n+\tEOF\n+\tgit for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&\n+\ttest_cmp expect actual\n+\"\n+\n test_done\n-- \n2.5.1\n"},{"id":"269692","messageId":"1441900110-4015-8-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 07/14] ref-filter: add option to filter out tags, branches and remotes","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:23Z","receivedAt":"2015-09-10T15:48:23Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd a function called 'for_each_fullref_in()' to refs.{c,h} which\niterates through each ref for the given path without trimming the path\nand also accounting for broken refs, if mentioned.\n\nAdd 'filter_ref_kind()' in ref-filter.c to check the kind of ref being\nhandled and return the kind to 'ref_filter_handler()', where we\ndiscard refs which we do not need and assign the kind to needed refs.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n ref-filter.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-----\n ref-filter.h | 13 ++++++++++--\n refs.c       |  9 +++++++++\n refs.h       |  1 +\n 4 files changed, 81 insertions(+), 7 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex b8f719c..7d2732a 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1189,6 +1189,34 @@ static struct ref_array_item *new_ref_array_item(const char *refname,\n \treturn ref;\n }\n \n+static int filter_ref_kind(struct ref_filter *filter, const char *refname)\n+{\n+\tunsigned int i;\n+\n+\tstatic struct {\n+\t\tconst char *prefix;\n+\t\tunsigned int kind;\n+\t} ref_kind[] = {\n+\t\t{ \"refs/heads/\" , FILTER_REFS_BRANCHES },\n+\t\t{ \"refs/remotes/\" , FILTER_REFS_REMOTES },\n+\t\t{ \"refs/tags/\", FILTER_REFS_TAGS}\n+\t};\n+\n+\tif (filter->kind == FILTER_REFS_BRANCHES ||\n+\t    filter->kind == FILTER_REFS_REMOTES ||\n+\t    filter->kind == FILTER_REFS_TAGS)\n+\t\treturn filter->kind;\n+\telse if (!strcmp(refname, \"HEAD\"))\n+\t\treturn FILTER_REFS_DETACHED_HEAD;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(ref_kind); i++) {\n+\t\tif (starts_with(refname, ref_kind[i].prefix))\n+\t\t\treturn ref_kind[i].kind;\n+\t}\n+\n+\treturn FILTER_REFS_OTHERS;\n+}\n+\n /*\n  * A call-back given to for_each_ref().  Filter refs and keep them for\n  * later object processing.\n@@ -1199,6 +1227,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,\n \tstruct ref_filter *filter = ref_cbdata->filter;\n \tstruct ref_array_item *ref;\n \tstruct commit *commit = NULL;\n+\tunsigned int kind;\n \n \tif (flag & REF_BAD_NAME) {\n \t\twarning(\"ignoring ref with broken name %s\", refname);\n@@ -1210,6 +1239,11 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,\n \t\treturn 0;\n \t}\n \n+\t/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */\n+\tkind = filter_ref_kind(filter, refname);\n+\tif (!(kind & filter->kind))\n+\t\treturn 0;\n+\n \tif (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))\n \t\treturn 0;\n \n@@ -1241,6 +1275,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,\n \n \tREALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);\n \tref_cbdata->array->items[ref_cbdata->array->nr++] = ref;\n+\tref->kind = kind;\n \treturn 0;\n }\n \n@@ -1317,17 +1352,37 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int\n {\n \tstruct ref_filter_cbdata ref_cbdata;\n \tint ret = 0;\n+\tunsigned int broken = 0;\n \n \tref_cbdata.array = array;\n \tref_cbdata.filter = filter;\n \n+\tif (type & FILTER_REFS_INCLUDE_BROKEN)\n+\t\tbroken = 1;\n+\tfilter->kind = type & FILTER_REFS_KIND_MASK;\n+\n \t/*  Simple per-ref filtering */\n-\tif (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))\n-\t\tret = for_each_rawref(ref_filter_handler, &ref_cbdata);\n-\telse if (type & FILTER_REFS_ALL)\n-\t\tret = for_each_ref(ref_filter_handler, &ref_cbdata);\n-\telse if (type)\n+\tif (!filter->kind)\n \t\tdie(\"filter_refs: invalid type\");\n+\telse {\n+\t\t/*\n+\t\t * For common cases where we need only branches or remotes or tags,\n+\t\t * we only iterate through those refs. If a mix of refs is needed,\n+\t\t * we iterate over all refs and filter out required refs with the help\n+\t\t * of filter_ref_kind().\n+\t\t */\n+\t\tif (filter->kind == FILTER_REFS_BRANCHES)\n+\t\t\tret = for_each_fullref_in(\"refs/heads/\", ref_filter_handler, &ref_cbdata, broken);\n+\t\telse if (filter->kind == FILTER_REFS_REMOTES)\n+\t\t\tret = for_each_fullref_in(\"refs/remotes/\", ref_filter_handler, &ref_cbdata, broken);\n+\t\telse if (filter->kind == FILTER_REFS_TAGS)\n+\t\t\tret = for_each_fullref_in(\"refs/tags/\", ref_filter_handler, &ref_cbdata, broken);\n+\t\telse if (filter->kind & FILTER_REFS_ALL)\n+\t\t\tret = for_each_fullref_in(\"\", ref_filter_handler, &ref_cbdata, broken);\n+\t\tif (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))\n+\t\t\thead_ref(ref_filter_handler, &ref_cbdata);\n+\t}\n+\n \n \t/*  Filters that need revision walking */\n \tif (filter->merge_commit)\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 45026d0..0913ba9 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -13,8 +13,15 @@\n #define QUOTE_PYTHON 4\n #define QUOTE_TCL 8\n \n-#define FILTER_REFS_INCLUDE_BROKEN 0x1\n-#define FILTER_REFS_ALL 0x2\n+#define FILTER_REFS_INCLUDE_BROKEN 0x0001\n+#define FILTER_REFS_TAGS           0x0002\n+#define FILTER_REFS_BRANCHES       0x0004\n+#define FILTER_REFS_REMOTES        0x0008\n+#define FILTER_REFS_OTHERS         0x0010\n+#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \\\n+\t\t\t\t    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)\n+#define FILTER_REFS_DETACHED_HEAD  0x0020\n+#define FILTER_REFS_KIND_MASK      (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)\n \n struct atom_value;\n \n@@ -27,6 +34,7 @@ struct ref_sorting {\n struct ref_array_item {\n \tunsigned char objectname[20];\n \tint flag;\n+\tunsigned int kind;\n \tconst char *symref;\n \tstruct commit *commit;\n \tstruct atom_value *value;\n@@ -51,6 +59,7 @@ struct ref_filter {\n \tstruct commit *merge_commit;\n \n \tunsigned int with_commit_tag_algo : 1;\n+\tunsigned int kind;\n };\n \n struct ref_filter_cbdata {\ndiff --git a/refs.c b/refs.c\nindex 4e15f60..a9469c2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2108,6 +2108,15 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)\n \treturn do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);\n }\n \n+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)\n+{\n+\tunsigned int flag = 0;\n+\n+\tif (broken)\n+\t\tflag = DO_FOR_EACH_INCLUDE_BROKEN;\n+\treturn do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);\n+}\n+\n int for_each_ref_in_submodule(const char *submodule, const char *prefix,\n \t\teach_ref_fn fn, void *cb_data)\n {\ndiff --git a/refs.h b/refs.h\nindex e9a5f32..6d30c98 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -173,6 +173,7 @@ typedef int each_ref_fn(const char *refname,\n extern int head_ref(each_ref_fn fn, void *cb_data);\n extern int for_each_ref(each_ref_fn fn, void *cb_data);\n extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);\n+extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);\n extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);\n extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);\n extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);\n-- \n2.5.1\n"},{"id":"269691","messageId":"1441900110-4015-9-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 08/14] ref-filter: add support for %(contents:lines=X)","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:24Z","receivedAt":"2015-09-10T15:48:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"In 'tag.c' we can print N lines from the annotation of the tag using\nthe '-n<num>' option. Copy code from 'tag.c' to 'ref-filter' and\nmodify it to support appending of N lines from the annotation of tags\nto the given strbuf.\n\nImplement %(contents:lines=X) where X lines of the given object are\nobtained.\n\nAdd documentation and test for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  3 ++-\n builtin/tag.c                      |  4 +++\n ref-filter.c                       | 45 ++++++++++++++++++++++++++++++++-\n ref-filter.h                       |  3 ++-\n t/t6302-for-each-ref-filter.sh     | 52 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 104 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 3a271bf..324ad2c 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -150,7 +150,8 @@ The complete message in a commit and tag object is `contents`.\n Its first line is `contents:subject`, where subject is the concatenation\n of all lines of the commit message up to the first blank line.  The next\n line is 'contents:body', where body is all of the lines after the first\n-blank line.  Finally, the optional GPG signature is `contents:signature`.\n+blank line.  The optional GPG signature is `contents:signature`.  The\n+first `N` lines of the message is obtained using `contents:lines=N`.\n \n For sorting purposes, fields with numeric values sort in numeric\n order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 471d6b1..b0bc1c5 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -185,6 +185,10 @@ static enum contains_result contains(struct commit *candidate,\n \treturn contains_test(candidate, want);\n }\n \n+/*\n+ * Currently modified and used in ref-filter as append_lines(), will\n+ * eventually be removed as we port tag.c to use ref-filter APIs.\n+ */\n static void show_tag_lines(const struct object_id *oid, int lines)\n {\n \tint i;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 7d2732a..b098b16 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -56,6 +56,7 @@ static struct {\n \t{ \"color\" },\n \t{ \"align\" },\n \t{ \"end\" },\n+\t{ \"contents:lines\" },\n };\n \n #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n@@ -65,6 +66,11 @@ struct align {\n \tunsigned int width;\n };\n \n+struct contents {\n+\tunsigned int lines;\n+\tstruct object_id oid;\n+};\n+\n struct ref_formatting_stack {\n \tstruct ref_formatting_stack *prev;\n \tstruct strbuf output;\n@@ -81,6 +87,7 @@ struct atom_value {\n \tconst char *s;\n \tunion {\n \t\tstruct align align;\n+\t\tstruct contents contents;\n \t} u;\n \tvoid (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n@@ -643,6 +650,30 @@ static void find_subpos(const char *buf, unsigned long sz,\n \t*nonsiglen = *sig - buf;\n }\n \n+/*\n+ * If 'lines' is greater than 0, append that many lines from the given\n+ * 'buf' of length 'size' to the given strbuf.\n+ */\n+static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)\n+{\n+\tint i;\n+\tconst char *sp, *eol;\n+\tsize_t len;\n+\n+\tsp = buf;\n+\n+\tfor (i = 0; i < lines && sp < buf + size; i++) {\n+\t\tif (i)\n+\t\t\tstrbuf_addstr(out, \"\\n    \");\n+\t\teol = memchr(sp, '\\n', size - (sp - buf));\n+\t\tlen = eol ? eol - sp : size - (sp - buf);\n+\t\tstrbuf_add(out, sp, len);\n+\t\tif (!eol)\n+\t\t\tbreak;\n+\t\tsp = eol + 1;\n+\t}\n+}\n+\n /* See grab_values */\n static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)\n {\n@@ -653,6 +684,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj\n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i];\n \t\tstruct atom_value *v = &val[i];\n+\t\tconst char *valp = NULL;\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n@@ -662,7 +694,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj\n \t\t    strcmp(name, \"contents\") &&\n \t\t    strcmp(name, \"contents:subject\") &&\n \t\t    strcmp(name, \"contents:body\") &&\n-\t\t    strcmp(name, \"contents:signature\"))\n+\t\t    strcmp(name, \"contents:signature\") &&\n+\t\t    !starts_with(name, \"contents:lines=\"))\n \t\t\tcontinue;\n \t\tif (!subpos)\n \t\t\tfind_subpos(buf, sz,\n@@ -682,6 +715,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj\n \t\t\tv->s = xmemdupz(sigpos, siglen);\n \t\telse if (!strcmp(name, \"contents\"))\n \t\t\tv->s = xstrdup(subpos);\n+\t\telse if (skip_prefix(name, \"contents:lines=\", &valp)) {\n+\t\t\tstruct strbuf s = STRBUF_INIT;\n+\t\t\tconst char *contents_end = bodylen + bodypos - siglen;\n+\n+\t\t\tif (strtoul_ui(valp, 10, &v->u.contents.lines))\n+\t\t\t\tdie(_(\"positive value expected contents:lines=%s\"), valp);\n+\t\t\t/*  Size is the length of the message after removing the signature */\n+\t\t\tappend_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);\n+\t\t\tv->s = strbuf_detach(&s, NULL);\n+\t\t}\n \t}\n }\n \ndiff --git a/ref-filter.h b/ref-filter.h\nindex 0913ba9..ab76b22 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -59,7 +59,8 @@ struct ref_filter {\n \tstruct commit *merge_commit;\n \n \tunsigned int with_commit_tag_algo : 1;\n-\tunsigned int kind;\n+\tunsigned int kind,\n+\t\tlines;\n };\n \n struct ref_filter_cbdata {\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex f596035..bab1f28 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -167,4 +167,56 @@ test_expect_success 'nested alignment with quote formatting' \"\n \ttest_cmp expect actual\n \"\n \n+test_expect_success 'check `%(contents:lines=1)`' '\n+\tcat >expect <<-\\EOF &&\n+\tmaster |three\n+\tside |four\n+\todd/spot |three\n+\tdouble-tag |Annonated doubly\n+\tfour |four\n+\tone |one\n+\tsigned-tag |A signed tag message\n+\tthree |three\n+\ttwo |two\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname:short) |%(contents:lines=1)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check `%(contents:lines=0)`' '\n+\tcat >expect <<-\\EOF &&\n+\tmaster |\n+\tside |\n+\todd/spot |\n+\tdouble-tag |\n+\tfour |\n+\tone |\n+\tsigned-tag |\n+\tthree |\n+\ttwo |\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname:short) |%(contents:lines=0)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check `%(contents:lines=99999)`' '\n+\tcat >expect <<-\\EOF &&\n+\tmaster |three\n+\tside |four\n+\todd/spot |three\n+\tdouble-tag |Annonated doubly\n+\tfour |four\n+\tone |one\n+\tsigned-tag |A signed tag message\n+\tthree |three\n+\ttwo |two\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname:short) |%(contents:lines=99999)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '`%(contents:lines=-1)` should fail' '\n+\ttest_must_fail git for-each-ref --format=\"%(refname:short) |%(contents:lines=-1)\"\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269693","messageId":"1441900110-4015-10-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 09/14] ref-filter: add support to sort by version","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:25Z","receivedAt":"2015-09-10T15:48:25Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd support to sort by version using the \"v:refname\" and\n\"version:refname\" option. This is achieved by using the 'versioncmp()'\nfunction as the comparing function for qsort.\n\nThis option is included to support sorting by versions in `git tag -l`\nwhich will eventually be ported to use ref-filter APIs.\n\nAdd documentation and tests for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  3 +++\n ref-filter.c                       | 15 ++++++++++-----\n ref-filter.h                       |  3 ++-\n t/t6302-for-each-ref-filter.sh     | 36 ++++++++++++++++++++++++++++++++++++\n 4 files changed, 51 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 324ad2c..16b4ac5 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -157,6 +157,9 @@ For sorting purposes, fields with numeric values sort in numeric\n order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).\n All other fields are used to sort in their byte-value order.\n \n+There is also an option to sort by versions, this can be done by using\n+the fieldname `version:refname` or its alias `v:refname`.\n+\n In any case, a field name that refers to a field inapplicable to\n the object referred by the ref does not cause an error.  It\n returns an empty string instead.\ndiff --git a/ref-filter.c b/ref-filter.c\nindex b098b16..77035d1 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -11,6 +11,8 @@\n #include \"ref-filter.h\"\n #include \"revision.h\"\n #include \"utf8.h\"\n+#include \"git-compat-util.h\"\n+#include \"version.h\"\n \n typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;\n \n@@ -1442,19 +1444,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru\n \n \tget_ref_atom_value(a, s->atom, &va);\n \tget_ref_atom_value(b, s->atom, &vb);\n-\tswitch (cmp_type) {\n-\tcase FIELD_STR:\n+\tif (s->version)\n+\t\tcmp = versioncmp(va->s, vb->s);\n+\telse if (cmp_type == FIELD_STR)\n \t\tcmp = strcmp(va->s, vb->s);\n-\t\tbreak;\n-\tdefault:\n+\telse {\n \t\tif (va->ul < vb->ul)\n \t\t\tcmp = -1;\n \t\telse if (va->ul == vb->ul)\n \t\t\tcmp = 0;\n \t\telse\n \t\t\tcmp = 1;\n-\t\tbreak;\n \t}\n+\n \treturn (s->reverse) ? -cmp : cmp;\n }\n \n@@ -1587,6 +1589,9 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)\n \t\ts->reverse = 1;\n \t\targ++;\n \t}\n+\tif (skip_prefix(arg, \"version:\", &arg) ||\n+\t    skip_prefix(arg, \"v:\", &arg))\n+\t\ts->version = 1;\n \tlen = strlen(arg);\n \ts->atom = parse_ref_filter_atom(arg, arg+len);\n \treturn 0;\ndiff --git a/ref-filter.h b/ref-filter.h\nindex ab76b22..ef25b6e 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -28,7 +28,8 @@ struct atom_value;\n struct ref_sorting {\n \tstruct ref_sorting *next;\n \tint atom; /* index into used_atom array (internal) */\n-\tunsigned reverse : 1;\n+\tunsigned reverse : 1,\n+\t\tversion : 1;\n };\n \n struct ref_array_item {\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex bab1f28..fe4796c 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -219,4 +219,40 @@ test_expect_success '`%(contents:lines=-1)` should fail' '\n \ttest_must_fail git for-each-ref --format=\"%(refname:short) |%(contents:lines=-1)\"\n '\n \n+test_expect_success 'setup for version sort' '\n+\ttest_commit foo1.3 &&\n+\ttest_commit foo1.6 &&\n+\ttest_commit foo1.10\n+'\n+\n+test_expect_success 'version sort' '\n+\tgit for-each-ref --sort=version:refname --format=\"%(refname:short)\" refs/tags/ | grep \"foo\" >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tfoo1.3\n+\tfoo1.6\n+\tfoo1.10\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'version sort (shortened)' '\n+\tgit for-each-ref --sort=v:refname --format=\"%(refname:short)\" refs/tags/ | grep \"foo\" >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tfoo1.3\n+\tfoo1.6\n+\tfoo1.10\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'reverse version sort' '\n+\tgit for-each-ref --sort=-version:refname --format=\"%(refname:short)\" refs/tags/ | grep \"foo\" >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tfoo1.10\n+\tfoo1.6\n+\tfoo1.3\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269694","messageId":"1441900110-4015-11-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 10/14] ref-filter: add option to match literal pattern","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:26Z","receivedAt":"2015-09-10T15:48:26Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nSince 'ref-filter' only has an option to match path names add an\noption for plain fnmatch pattern-matching.\n\nThis is to support the pattern matching options which are used in `git\ntag -l` and `git branch -l` where we can match patterns like `git tag\n-l foo*` which would match all tags which has a \"foo*\" pattern.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/for-each-ref.c |  1 +\n ref-filter.c           | 40 +++++++++++++++++++++++++++++++++++++---\n ref-filter.h           |  3 ++-\n 3 files changed, 40 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 40f343b..4e9f6c2 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -68,6 +68,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \tgit_config(git_default_config, NULL);\n \n \tfilter.name_patterns = argv;\n+\tfilter.match_as_path = 1;\n \tfilter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);\n \tref_array_sort(sorting, &array);\n \ndiff --git a/ref-filter.c b/ref-filter.c\nindex 77035d1..59716db 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -1164,9 +1164,33 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)\n \n /*\n  * Return 1 if the refname matches one of the patterns, otherwise 0.\n+ * A pattern can be a literal prefix (e.g. a refname \"refs/heads/master\"\n+ * matches a pattern \"refs/heads/mas\") or a wildcard (e.g. the same ref\n+ * matches \"refs/heads/mas*\", too).\n+ */\n+static int match_pattern(const char **patterns, const char *refname)\n+{\n+\t/*\n+\t * When no '--format' option is given we need to skip the prefix\n+\t * for matching refs of tags and branches.\n+\t */\n+\t(void)(skip_prefix(refname, \"refs/tags/\", &refname) ||\n+\t       skip_prefix(refname, \"refs/heads/\", &refname) ||\n+\t       skip_prefix(refname, \"refs/remotes/\", &refname) ||\n+\t       skip_prefix(refname, \"refs/\", &refname));\n+\n+\tfor (; *patterns; patterns++) {\n+\t\tif (!wildmatch(*patterns, refname, 0, NULL))\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Return 1 if the refname matches one of the patterns, otherwise 0.\n  * A pattern can be path prefix (e.g. a refname \"refs/heads/master\"\n- * matches a pattern \"refs/heads/\") or a wildcard (e.g. the same ref\n- * matches \"refs/heads/m*\",too).\n+ * matches a pattern \"refs/heads/\" but not \"refs/heads/m\") or a\n+ * wildcard (e.g. the same ref matches \"refs/heads/m*\", too).\n  */\n static int match_name_as_path(const char **pattern, const char *refname)\n {\n@@ -1187,6 +1211,16 @@ static int match_name_as_path(const char **pattern, const char *refname)\n \treturn 0;\n }\n \n+/* Return 1 if the refname matches one of the patterns, otherwise 0. */\n+static int filter_pattern_match(struct ref_filter *filter, const char *refname)\n+{\n+\tif (!*filter->name_patterns)\n+\t\treturn 1; /* No pattern always matches */\n+\tif (filter->match_as_path)\n+\t\treturn match_name_as_path(filter->name_patterns, refname);\n+\treturn match_pattern(filter->name_patterns, refname);\n+}\n+\n /*\n  * Given a ref (sha1, refname), check if the ref belongs to the array\n  * of sha1s. If the given ref is a tag, check if the given tag points\n@@ -1289,7 +1323,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,\n \tif (!(kind & filter->kind))\n \t\treturn 0;\n \n-\tif (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))\n+\tif (!filter_pattern_match(filter, refname))\n \t\treturn 0;\n \n \tif (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))\ndiff --git a/ref-filter.h b/ref-filter.h\nindex ef25b6e..a5cfa5e 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -59,7 +59,8 @@ struct ref_filter {\n \t} merge;\n \tstruct commit *merge_commit;\n \n-\tunsigned int with_commit_tag_algo : 1;\n+\tunsigned int with_commit_tag_algo : 1,\n+\t\tmatch_as_path : 1;\n \tunsigned int kind,\n \t\tlines;\n };\n-- \n2.5.1\n"},{"id":"269695","messageId":"1441900110-4015-12-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 11/14] tag.c: use 'ref-filter' data structures","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T15:48:27Z","receivedAt":"2015-09-10T15:48:27Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nMake 'tag.c' use 'ref-filter' data structures and make changes to\nsupport the new data structures. This is a part of the process\nof porting 'tag.c' to use 'ref-filter' APIs.\n\nThis is a temporary step before porting 'tag.c' to use 'ref-filter'\ncompletely. As this is a temporary step, most of the code\nintroduced here will be removed when 'tag.c' is ported over to use\n'ref-filter' APIs.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/tag.c | 106 +++++++++++++++++++++++++++++++---------------------------\n 1 file changed, 57 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex b0bc1c5..fe66f7b 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -17,6 +17,7 @@\n #include \"gpg-interface.h\"\n #include \"sha1-array.h\"\n #include \"column.h\"\n+#include \"ref-filter.h\"\n \n static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n@@ -34,15 +35,6 @@ static const char * const git_tag_usage[] = {\n \n static int tag_sort;\n \n-struct tag_filter {\n-\tconst char **patterns;\n-\tint lines;\n-\tint sort;\n-\tstruct string_list tags;\n-\tstruct commit_list *with_commit;\n-};\n-\n-static struct sha1_array points_at;\n static unsigned int colopts;\n \n static int match_pattern(const char **patterns, const char *ref)\n@@ -61,19 +53,20 @@ static int match_pattern(const char **patterns, const char *ref)\n  * removed as we port tag.c to use the ref-filter APIs.\n  */\n static const unsigned char *match_points_at(const char *refname,\n-\t\t\t\t\t    const unsigned char *sha1)\n+\t\t\t\t\t    const unsigned char *sha1,\n+\t\t\t\t\t    struct sha1_array *points_at)\n {\n \tconst unsigned char *tagged_sha1 = NULL;\n \tstruct object *obj;\n \n-\tif (sha1_array_lookup(&points_at, sha1) >= 0)\n+\tif (sha1_array_lookup(points_at, sha1) >= 0)\n \t\treturn sha1;\n \tobj = parse_object(sha1);\n \tif (!obj)\n \t\tdie(_(\"malformed object at '%s'\"), refname);\n \tif (obj->type == OBJ_TAG)\n \t\ttagged_sha1 = ((struct tag *)obj)->tagged->sha1;\n-\tif (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)\n+\tif (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)\n \t\treturn tagged_sha1;\n \treturn NULL;\n }\n@@ -228,12 +221,24 @@ free_return:\n \tfree(buf);\n }\n \n+static void ref_array_append(struct ref_array *array, const char *refname)\n+{\n+\tsize_t len = strlen(refname);\n+\tstruct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);\n+\tmemcpy(ref->refname, refname, len);\n+\tref->refname[len] = '\\0';\n+\tREALLOC_ARRAY(array->items, array->nr + 1);\n+\tarray->items[array->nr++] = ref;\n+}\n+\n static int show_reference(const char *refname, const struct object_id *oid,\n \t\t\t  int flag, void *cb_data)\n {\n-\tstruct tag_filter *filter = cb_data;\n+\tstruct ref_filter_cbdata *data = cb_data;\n+\tstruct ref_array *array = data->array;\n+\tstruct ref_filter *filter = data->filter;\n \n-\tif (match_pattern(filter->patterns, refname)) {\n+\tif (match_pattern(filter->name_patterns, refname)) {\n \t\tif (filter->with_commit) {\n \t\t\tstruct commit *commit;\n \n@@ -244,12 +249,12 @@ static int show_reference(const char *refname, const struct object_id *oid,\n \t\t\t\treturn 0;\n \t\t}\n \n-\t\tif (points_at.nr && !match_points_at(refname, oid->hash))\n+\t\tif (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at))\n \t\t\treturn 0;\n \n \t\tif (!filter->lines) {\n-\t\t\tif (filter->sort)\n-\t\t\t\tstring_list_append(&filter->tags, refname);\n+\t\t\tif (tag_sort)\n+\t\t\t\tref_array_append(array, refname);\n \t\t\telse\n \t\t\t\tprintf(\"%s\\n\", refname);\n \t\t\treturn 0;\n@@ -264,36 +269,36 @@ static int show_reference(const char *refname, const struct object_id *oid,\n \n static int sort_by_version(const void *a_, const void *b_)\n {\n-\tconst struct string_list_item *a = a_;\n-\tconst struct string_list_item *b = b_;\n-\treturn versioncmp(a->string, b->string);\n+\tconst struct ref_array_item *a = *((struct ref_array_item **)a_);\n+\tconst struct ref_array_item *b = *((struct ref_array_item **)b_);\n+\treturn versioncmp(a->refname, b->refname);\n }\n \n-static int list_tags(const char **patterns, int lines,\n-\t\t     struct commit_list *with_commit, int sort)\n+static int list_tags(struct ref_filter *filter, int sort)\n {\n-\tstruct tag_filter filter;\n+\tstruct ref_array array;\n+\tstruct ref_filter_cbdata data;\n+\n+\tmemset(&array, 0, sizeof(array));\n+\tdata.array = &array;\n+\tdata.filter = filter;\n \n-\tfilter.patterns = patterns;\n-\tfilter.lines = lines;\n-\tfilter.sort = sort;\n-\tfilter.with_commit = with_commit;\n-\tmemset(&filter.tags, 0, sizeof(filter.tags));\n-\tfilter.tags.strdup_strings = 1;\n+\tif (filter->lines == -1)\n+\t\tfilter->lines = 0;\n \n-\tfor_each_tag_ref(show_reference, (void *)&filter);\n+\tfor_each_tag_ref(show_reference, &data);\n \tif (sort) {\n \t\tint i;\n \t\tif ((sort & SORT_MASK) == VERCMP_SORT)\n-\t\t\tqsort(filter.tags.items, filter.tags.nr,\n-\t\t\t      sizeof(struct string_list_item), sort_by_version);\n+\t\t\tqsort(array.items, array.nr,\n+\t\t\t      sizeof(struct ref_array_item *), sort_by_version);\n \t\tif (sort & REVERSE_SORT)\n-\t\t\tfor (i = filter.tags.nr - 1; i >= 0; i--)\n-\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n+\t\t\tfor (i = array.nr - 1; i >= 0; i--)\n+\t\t\t\tprintf(\"%s\\n\", array.items[i]->refname);\n \t\telse\n-\t\t\tfor (i = 0; i < filter.tags.nr; i++)\n-\t\t\t\tprintf(\"%s\\n\", filter.tags.items[i].string);\n-\t\tstring_list_clear(&filter.tags, 0);\n+\t\t\tfor (i = 0; i < array.nr; i++)\n+\t\t\t\tprintf(\"%s\\n\", array.items[i]->refname);\n+\t\tref_array_clear(&array);\n \t}\n \treturn 0;\n }\n@@ -574,17 +579,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tconst char *object_ref, *tag;\n \tstruct create_tag_options opt;\n \tchar *cleanup_arg = NULL;\n-\tint annotate = 0, force = 0, lines = -1;\n \tint create_reflog = 0;\n+\tint annotate = 0, force = 0;\n \tint cmdmode = 0;\n \tconst char *msgfile = NULL, *keyid = NULL;\n \tstruct msg_arg msg = { 0, STRBUF_INIT };\n-\tstruct commit_list *with_commit = NULL;\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n+\tstruct ref_filter filter;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n-\t\t{ OPTION_INTEGER, 'n', NULL, &lines, N_(\"n\"),\n+\t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n \t\t\t\tN_(\"print <n> lines of each tag message\"),\n \t\t\t\tPARSE_OPT_OPTARG, NULL, 1 },\n \t\tOPT_CMDMODE('d', \"delete\", &cmdmode, N_(\"delete tags\"), 'd'),\n@@ -606,14 +611,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \n \t\tOPT_GROUP(N_(\"Tag listing options\")),\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n-\t\tOPT_CONTAINS(&with_commit, N_(\"print only tags that contain the commit\")),\n-\t\tOPT_WITH(&with_commit, N_(\"print only tags that contain the commit\")),\n+\t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n+\t\tOPT_WITH(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n \t\t{\n \t\t\tOPTION_CALLBACK, 0, \"sort\", &tag_sort, N_(\"type\"), N_(\"sort tags\"),\n \t\t\tPARSE_OPT_NONEG, parse_opt_sort\n \t\t},\n \t\t{\n-\t\t\tOPTION_CALLBACK, 0, \"points-at\", &points_at, N_(\"object\"),\n+\t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n \t\t},\n \t\tOPT_END()\n@@ -622,6 +627,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tgit_config(git_tag_config, NULL);\n \n \tmemset(&opt, 0, sizeof(opt));\n+\tmemset(&filter, 0, sizeof(filter));\n+\tfilter.lines = -1;\n \n \targc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);\n \n@@ -638,7 +645,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(git_tag_usage, options);\n \n \tfinalize_colopts(&colopts, -1);\n-\tif (cmdmode == 'l' && lines != -1) {\n+\tif (cmdmode == 'l' && filter.lines != -1) {\n \t\tif (explicitly_enable_column(colopts))\n \t\t\tdie(_(\"--column and -n are incompatible\"));\n \t\tcolopts = 0;\n@@ -651,18 +658,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tif (lines != -1 && tag_sort)\n+\t\tif (filter.lines != -1 && tag_sort)\n \t\t\tdie(_(\"--sort and -n are incompatible\"));\n-\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);\n+\t\tfilter.name_patterns = argv;\n+\t\tret = list_tags(&filter, tag_sort);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\n \t}\n-\tif (lines != -1)\n+\tif (filter.lines != -1)\n \t\tdie(_(\"-n option is only allowed with -l.\"));\n-\tif (with_commit)\n+\tif (filter.with_commit)\n \t\tdie(_(\"--contains option is only allowed with -l.\"));\n-\tif (points_at.nr)\n+\tif (filter.points_at.nr)\n \t\tdie(_(\"--points-at option is only allowed with -l.\"));\n \tif (cmdmode == 'd')\n \t\treturn for_each_tag_name(argv, delete_tag);\n-- \n2.5.1\n"},{"id":"269699","messageId":"1441902169-9891-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 12/14] tag.c: use 'ref-filter' APIs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T16:22:47Z","receivedAt":"2015-09-10T16:22:47Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nMake 'tag.c' use 'ref-filter' APIs for iterating through refs, sorting\nand printing of refs. This removes most of the code used in 'tag.c'\nreplacing it with calls to the 'ref-filter' library.\n\nMake 'tag.c' use the 'filter_refs()' function provided by 'ref-filter'\nto filter out tags based on the options set.\n\nFor printing tags we use 'show_ref_array_item()' function provided by\n'ref-filter'.\n\nWe improve the sorting option provided by 'tag.c' by using the sorting\noptions provided by 'ref-filter'. This causes the test 'invalid sort\nparameter on command line' in t7004 to fail, as 'ref-filter' throws an\nerror for all sorting fields which are incorrect. The test is changed\nto reflect the same.\n\nModify documentation for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-tag.txt |  16 ++-\n builtin/tag.c             | 345 ++++++----------------------------------------\n t/t7004-tag.sh            |   8 +-\n 3 files changed, 53 insertions(+), 316 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 84f6496..3ac4a96 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n-\t[--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]\n+\t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]\n 'git tag' -v <tagname>...\n \n DESCRIPTION\n@@ -94,14 +94,16 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n---sort=<type>::\n-\tSort in a specific order. Supported type is \"refname\"\n-\t(lexicographic order), \"version:refname\" or \"v:refname\" (tag\n+--sort=<key>::\n+\tSort based on the key given.  Prefix `-` to sort in\n+\tdescending order of the value. You may use the --sort=<key> option\n+\tmultiple times, in which case the last key becomes the primary\n+\tkey. Also supports \"version:refname\" or \"v:refname\" (tag\n \tnames are treated as versions). The \"version:refname\" sort\n \torder can also be affected by the\n-\t\"versionsort.prereleaseSuffix\" configuration variable. Prepend\n-\t\"-\" to reverse sort order. When this option is not given, the\n-\tsort order defaults to the value configured for the 'tag.sort'\n+\t\"versionsort.prereleaseSuffix\" configuration variable.\n+\tThe keys supported are the same as those in `git for-each-ref`.\n+\tSort order defaults to the value configured for the 'tag.sort'\n \tvariable if it exists, or lexicographic order otherwise. See\n \tlinkgit:git-config[1].\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex fe66f7b..f257d45 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,278 +28,35 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n-#define STRCMP_SORT     0\t/* must be zero */\n-#define VERCMP_SORT     1\n-#define SORT_MASK       0x7fff\n-#define REVERSE_SORT    0x8000\n-\n-static int tag_sort;\n-\n static unsigned int colopts;\n \n-static int match_pattern(const char **patterns, const char *ref)\n-{\n-\t/* no pattern means match everything */\n-\tif (!*patterns)\n-\t\treturn 1;\n-\tfor (; *patterns; patterns++)\n-\t\tif (!wildmatch(*patterns, ref, 0, NULL))\n-\t\t\treturn 1;\n-\treturn 0;\n-}\n-\n-/*\n- * This is currently duplicated in ref-filter.c, and will eventually be\n- * removed as we port tag.c to use the ref-filter APIs.\n- */\n-static const unsigned char *match_points_at(const char *refname,\n-\t\t\t\t\t    const unsigned char *sha1,\n-\t\t\t\t\t    struct sha1_array *points_at)\n-{\n-\tconst unsigned char *tagged_sha1 = NULL;\n-\tstruct object *obj;\n-\n-\tif (sha1_array_lookup(points_at, sha1) >= 0)\n-\t\treturn sha1;\n-\tobj = parse_object(sha1);\n-\tif (!obj)\n-\t\tdie(_(\"malformed object at '%s'\"), refname);\n-\tif (obj->type == OBJ_TAG)\n-\t\ttagged_sha1 = ((struct tag *)obj)->tagged->sha1;\n-\tif (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)\n-\t\treturn tagged_sha1;\n-\treturn NULL;\n-}\n-\n-static int in_commit_list(const struct commit_list *want, struct commit *c)\n-{\n-\tfor (; want; want = want->next)\n-\t\tif (!hashcmp(want->item->object.sha1, c->object.sha1))\n-\t\t\treturn 1;\n-\treturn 0;\n-}\n-\n-/*\n- * The entire code segment for supporting the --contains option has been\n- * copied over to ref-filter.{c,h}. This will be deleted evetually when\n- * we port tag.c to use ref-filter APIs.\n- */\n-enum contains_result {\n-\tCONTAINS_UNKNOWN = -1,\n-\tCONTAINS_NO = 0,\n-\tCONTAINS_YES = 1\n-};\n-\n-/*\n- * Test whether the candidate or one of its parents is contained in the list.\n- * Do not recurse to find out, though, but return -1 if inconclusive.\n- */\n-static enum contains_result contains_test(struct commit *candidate,\n-\t\t\t    const struct commit_list *want)\n-{\n-\t/* was it previously marked as containing a want commit? */\n-\tif (candidate->object.flags & TMP_MARK)\n-\t\treturn 1;\n-\t/* or marked as not possibly containing a want commit? */\n-\tif (candidate->object.flags & UNINTERESTING)\n-\t\treturn 0;\n-\t/* or are we it? */\n-\tif (in_commit_list(want, candidate)) {\n-\t\tcandidate->object.flags |= TMP_MARK;\n-\t\treturn 1;\n-\t}\n-\n-\tif (parse_commit(candidate) < 0)\n-\t\treturn 0;\n-\n-\treturn -1;\n-}\n-\n-/*\n- * Mimicking the real stack, this stack lives on the heap, avoiding stack\n- * overflows.\n- *\n- * At each recursion step, the stack items points to the commits whose\n- * ancestors are to be inspected.\n- */\n-struct stack {\n-\tint nr, alloc;\n-\tstruct stack_entry {\n-\t\tstruct commit *commit;\n-\t\tstruct commit_list *parents;\n-\t} *stack;\n-};\n-\n-static void push_to_stack(struct commit *candidate, struct stack *stack)\n-{\n-\tint index = stack->nr++;\n-\tALLOC_GROW(stack->stack, stack->nr, stack->alloc);\n-\tstack->stack[index].commit = candidate;\n-\tstack->stack[index].parents = candidate->parents;\n-}\n-\n-static enum contains_result contains(struct commit *candidate,\n-\t\tconst struct commit_list *want)\n-{\n-\tstruct stack stack = { 0, 0, NULL };\n-\tint result = contains_test(candidate, want);\n-\n-\tif (result != CONTAINS_UNKNOWN)\n-\t\treturn result;\n-\n-\tpush_to_stack(candidate, &stack);\n-\twhile (stack.nr) {\n-\t\tstruct stack_entry *entry = &stack.stack[stack.nr - 1];\n-\t\tstruct commit *commit = entry->commit;\n-\t\tstruct commit_list *parents = entry->parents;\n-\n-\t\tif (!parents) {\n-\t\t\tcommit->object.flags |= UNINTERESTING;\n-\t\t\tstack.nr--;\n-\t\t}\n-\t\t/*\n-\t\t * If we just popped the stack, parents->item has been marked,\n-\t\t * therefore contains_test will return a meaningful 0 or 1.\n-\t\t */\n-\t\telse switch (contains_test(parents->item, want)) {\n-\t\tcase CONTAINS_YES:\n-\t\t\tcommit->object.flags |= TMP_MARK;\n-\t\t\tstack.nr--;\n-\t\t\tbreak;\n-\t\tcase CONTAINS_NO:\n-\t\t\tentry->parents = parents->next;\n-\t\t\tbreak;\n-\t\tcase CONTAINS_UNKNOWN:\n-\t\t\tpush_to_stack(parents->item, &stack);\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\tfree(stack.stack);\n-\treturn contains_test(candidate, want);\n-}\n-\n-/*\n- * Currently modified and used in ref-filter as append_lines(), will\n- * eventually be removed as we port tag.c to use ref-filter APIs.\n- */\n-static void show_tag_lines(const struct object_id *oid, int lines)\n-{\n-\tint i;\n-\tunsigned long size;\n-\tenum object_type type;\n-\tchar *buf, *sp, *eol;\n-\tsize_t len;\n-\n-\tbuf = read_sha1_file(oid->hash, &type, &size);\n-\tif (!buf)\n-\t\tdie_errno(\"unable to read object %s\", oid_to_hex(oid));\n-\tif (type != OBJ_COMMIT && type != OBJ_TAG)\n-\t\tgoto free_return;\n-\tif (!size)\n-\t\tdie(\"an empty %s object %s?\",\n-\t\t    typename(type), oid_to_hex(oid));\n-\n-\t/* skip header */\n-\tsp = strstr(buf, \"\\n\\n\");\n-\tif (!sp)\n-\t\tgoto free_return;\n-\n-\t/* only take up to \"lines\" lines, and strip the signature from a tag */\n-\tif (type == OBJ_TAG)\n-\t\tsize = parse_signature(buf, size);\n-\tfor (i = 0, sp += 2; i < lines && sp < buf + size; i++) {\n-\t\tif (i)\n-\t\t\tprintf(\"\\n    \");\n-\t\teol = memchr(sp, '\\n', size - (sp - buf));\n-\t\tlen = eol ? eol - sp : size - (sp - buf);\n-\t\tfwrite(sp, len, 1, stdout);\n-\t\tif (!eol)\n-\t\t\tbreak;\n-\t\tsp = eol + 1;\n-\t}\n-free_return:\n-\tfree(buf);\n-}\n-\n-static void ref_array_append(struct ref_array *array, const char *refname)\n-{\n-\tsize_t len = strlen(refname);\n-\tstruct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);\n-\tmemcpy(ref->refname, refname, len);\n-\tref->refname[len] = '\\0';\n-\tREALLOC_ARRAY(array->items, array->nr + 1);\n-\tarray->items[array->nr++] = ref;\n-}\n-\n-static int show_reference(const char *refname, const struct object_id *oid,\n-\t\t\t  int flag, void *cb_data)\n-{\n-\tstruct ref_filter_cbdata *data = cb_data;\n-\tstruct ref_array *array = data->array;\n-\tstruct ref_filter *filter = data->filter;\n-\n-\tif (match_pattern(filter->name_patterns, refname)) {\n-\t\tif (filter->with_commit) {\n-\t\t\tstruct commit *commit;\n-\n-\t\t\tcommit = lookup_commit_reference_gently(oid->hash, 1);\n-\t\t\tif (!commit)\n-\t\t\t\treturn 0;\n-\t\t\tif (!contains(commit, filter->with_commit))\n-\t\t\t\treturn 0;\n-\t\t}\n-\n-\t\tif (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at))\n-\t\t\treturn 0;\n-\n-\t\tif (!filter->lines) {\n-\t\t\tif (tag_sort)\n-\t\t\t\tref_array_append(array, refname);\n-\t\t\telse\n-\t\t\t\tprintf(\"%s\\n\", refname);\n-\t\t\treturn 0;\n-\t\t}\n-\t\tprintf(\"%-15s \", refname);\n-\t\tshow_tag_lines(oid, filter->lines);\n-\t\tputchar('\\n');\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static int sort_by_version(const void *a_, const void *b_)\n-{\n-\tconst struct ref_array_item *a = *((struct ref_array_item **)a_);\n-\tconst struct ref_array_item *b = *((struct ref_array_item **)b_);\n-\treturn versioncmp(a->refname, b->refname);\n-}\n-\n-static int list_tags(struct ref_filter *filter, int sort)\n+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n {\n \tstruct ref_array array;\n-\tstruct ref_filter_cbdata data;\n+\tchar *format, *to_free = NULL;\n+\tint i;\n \n \tmemset(&array, 0, sizeof(array));\n-\tdata.array = &array;\n-\tdata.filter = filter;\n \n \tif (filter->lines == -1)\n \t\tfilter->lines = 0;\n \n-\tfor_each_tag_ref(show_reference, &data);\n-\tif (sort) {\n-\t\tint i;\n-\t\tif ((sort & SORT_MASK) == VERCMP_SORT)\n-\t\t\tqsort(array.items, array.nr,\n-\t\t\t      sizeof(struct ref_array_item *), sort_by_version);\n-\t\tif (sort & REVERSE_SORT)\n-\t\t\tfor (i = array.nr - 1; i >= 0; i--)\n-\t\t\t\tprintf(\"%s\\n\", array.items[i]->refname);\n-\t\telse\n-\t\t\tfor (i = 0; i < array.nr; i++)\n-\t\t\t\tprintf(\"%s\\n\", array.items[i]->refname);\n-\t\tref_array_clear(&array);\n-\t}\n+\tif (filter->lines) {\n+\t\tformat = xstrfmt(\"%s %%(contents:lines=%d)\",\n+\t\t\t\t \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n+\t\tto_free = format;\n+\t} else\n+\t\tformat = \"%(refname:short)\";\n+\n+\tverify_ref_format(format);\n+\tfilter_refs(&array, filter, FILTER_REFS_TAGS);\n+\tref_array_sort(sorting, &array);\n+\n+\tfor (i = 0; i < array.nr; i++)\n+\t\tshow_ref_array_item(array.items[i], format, 0);\n+\tref_array_clear(&array);\n+\tfree(to_free);\n+\n \treturn 0;\n }\n \n@@ -366,35 +123,26 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-/*\n- * Parse a sort string, and return 0 if parsed successfully. Will return\n- * non-zero when the sort string does not parse into a known type. If var is\n- * given, the error message becomes a warning and includes information about\n- * the configuration value.\n- */\n-static int parse_sort_string(const char *var, const char *arg, int *sort)\n+/* Parse arg given and add it the ref_sorting array */\n+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)\n {\n-\tint type = 0, flags = 0;\n-\n-\tif (skip_prefix(arg, \"-\", &arg))\n-\t\tflags |= REVERSE_SORT;\n+\tstruct ref_sorting *s;\n+\tint len;\n \n-\tif (skip_prefix(arg, \"version:\", &arg) || skip_prefix(arg, \"v:\", &arg))\n-\t\ttype = VERCMP_SORT;\n-\telse\n-\t\ttype = STRCMP_SORT;\n+\ts = xcalloc(1, sizeof(*s));\n+\ts->next = *sorting_tail;\n+\t*sorting_tail = s;\n \n-\tif (strcmp(arg, \"refname\")) {\n-\t\tif (!var)\n-\t\t\treturn error(_(\"unsupported sort specification '%s'\"), arg);\n-\t\telse {\n-\t\t\twarning(_(\"unsupported sort specification '%s' in variable '%s'\"),\n-\t\t\t\tvar, arg);\n-\t\t\treturn -1;\n-\t\t}\n+\tif (*arg == '-') {\n+\t\ts->reverse = 1;\n+\t\targ++;\n \t}\n+\tif (skip_prefix(arg, \"version:\", &arg) ||\n+\t    skip_prefix(arg, \"v:\", &arg))\n+\t\ts->version = 1;\n \n-\t*sort = (type | flags);\n+\tlen = strlen(arg);\n+\ts->atom = parse_ref_filter_atom(arg, arg+len);\n \n \treturn 0;\n }\n@@ -402,11 +150,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)\n static int git_tag_config(const char *var, const char *value, void *cb)\n {\n \tint status;\n+\tstruct ref_sorting **sorting_tail = (struct ref_sorting **)cb;\n \n \tif (!strcmp(var, \"tag.sort\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n-\t\tparse_sort_string(var, value, &tag_sort);\n+\t\tparse_sorting_string(value, sorting_tail);\n \t\treturn 0;\n \t}\n \n@@ -564,13 +313,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)\n \treturn check_refname_format(sb->buf, 0);\n }\n \n-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n-{\n-\tint *sort = opt->value;\n-\n-\treturn parse_sort_string(NULL, arg, sort);\n-}\n-\n int cmd_tag(int argc, const char **argv, const char *prefix)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -587,6 +329,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_filter filter;\n+\tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n@@ -613,10 +356,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n \t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n \t\tOPT_WITH(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n-\t\t{\n-\t\t\tOPTION_CALLBACK, 0, \"sort\", &tag_sort, N_(\"type\"), N_(\"sort tags\"),\n-\t\t\tPARSE_OPT_NONEG, parse_opt_sort\n-\t\t},\n+\t\tOPT_CALLBACK(0 , \"sort\", sorting_tail, N_(\"key\"),\n+\t\t\t     N_(\"field name to sort on\"), &parse_opt_ref_sorting),\n \t\t{\n \t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n@@ -624,7 +365,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n-\tgit_config(git_tag_config, NULL);\n+\tgit_config(git_tag_config, sorting_tail);\n \n \tmemset(&opt, 0, sizeof(opt));\n \tmemset(&filter, 0, sizeof(filter));\n@@ -650,6 +391,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"--column and -n are incompatible\"));\n \t\tcolopts = 0;\n \t}\n+\tif (!sorting)\n+\t\tsorting = ref_default_sorting();\n \tif (cmdmode == 'l') {\n \t\tint ret;\n \t\tif (column_active(colopts)) {\n@@ -658,10 +401,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tif (filter.lines != -1 && tag_sort)\n-\t\t\tdie(_(\"--sort and -n are incompatible\"));\n \t\tfilter.name_patterns = argv;\n-\t\tret = list_tags(&filter, tag_sort);\n+\t\tret = list_tags(&filter, sorting);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex d31788c..84153ef 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '\n \n test_expect_success 'invalid sort parameter in configuratoin' '\n \tgit config tag.sort \"v:notvalid\" &&\n-\tgit tag -l \"foo*\" >actual &&\n-\tcat >expect <<-\\EOF &&\n-\tfoo1.10\n-\tfoo1.3\n-\tfoo1.6\n-\tEOF\n-\ttest_cmp expect actual\n+\ttest_must_fail git tag -l \"foo*\"\n '\n \n test_expect_success 'version sort with prerelease reordering' '\n-- \n2.5.1\n"},{"id":"269700","messageId":"1441902169-9891-2-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 13/14] tag.c: implement '--format' option","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T16:22:48Z","receivedAt":"2015-09-10T16:22:48Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Implement the '--format' option provided by 'ref-filter'.\nThis lets the user list tags as per desired format similar\nto the implementation in 'git for-each-ref'.\n\nAdd tests and documentation for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-tag.txt |  8 +++++++-\n builtin/tag.c             | 24 ++++++++++++++----------\n t/t7004-tag.sh            | 12 ++++++++++++\n 3 files changed, 33 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 3ac4a96..0c7f4e6 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -13,7 +13,8 @@ SYNOPSIS\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n-\t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]\n+\t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]\n+\t[--format=<format>] [<pattern>...]\n 'git tag' -v <tagname>...\n \n DESCRIPTION\n@@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.\n \tThe object that the new tag will refer to, usually a commit.\n \tDefaults to HEAD.\n \n+<format>::\n+\tA string that interpolates `%(fieldname)` from the object\n+\tpointed at by a ref being shown.  The format is the same as\n+\tthat of linkgit:git-for-each-ref[1].  When unspecified,\n+\tdefaults to `%(refname:short)`.\n \n CONFIGURATION\n -------------\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f257d45..075d90b 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -23,17 +23,17 @@ static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]\"\n-\t\t\"\\n\\t\\t[<pattern>...]\"),\n+\t\t\"\\n\\t\\t[--format=<format>] [<pattern>...]\"),\n \tN_(\"git tag -v <tagname>...\"),\n \tNULL\n };\n \n static unsigned int colopts;\n \n-static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)\n {\n \tstruct ref_array array;\n-\tchar *format, *to_free = NULL;\n+\tchar *to_free = NULL;\n \tint i;\n \n \tmemset(&array, 0, sizeof(array));\n@@ -41,12 +41,14 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n \tif (filter->lines == -1)\n \t\tfilter->lines = 0;\n \n-\tif (filter->lines) {\n-\t\tformat = xstrfmt(\"%s %%(contents:lines=%d)\",\n-\t\t\t\t \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n-\t\tto_free = format;\n-\t} else\n-\t\tformat = \"%(refname:short)\";\n+\tif (!format) {\n+\t\tif (filter->lines) {\n+\t\t\tformat = xstrfmt(\"%s %%(contents:lines=%d)\",\n+\t\t\t\t\t \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n+\t\t\tto_free = format;\n+\t\t} else\n+\t\t\tformat = \"%(refname:short)\";\n+\t}\n \n \tverify_ref_format(format);\n \tfilter_refs(&array, filter, FILTER_REFS_TAGS);\n@@ -330,6 +332,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n+\tconst char *format = NULL;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n@@ -362,6 +365,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n \t\t},\n+\t\tOPT_STRING(  0 , \"format\", &format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END()\n \t};\n \n@@ -402,7 +406,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n \t\tfilter.name_patterns = argv;\n-\t\tret = list_tags(&filter, sorting);\n+\t\tret = list_tags(&filter, sorting, format);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 84153ef..8987fb1 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1519,4 +1519,16 @@ EOF\"\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--format should list tags as per format given' '\n+\tcat >expect <<-\\EOF &&\n+\trefname : refs/tags/foo1.10\n+\trefname : refs/tags/foo1.3\n+\trefname : refs/tags/foo1.6\n+\trefname : refs/tags/foo1.6-rc1\n+\trefname : refs/tags/foo1.6-rc2\n+\tEOF\n+\tgit tag -l --format=\"refname : %(refname)\" \"foo*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269701","messageId":"1441902169-9891-3-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T16:22:49Z","receivedAt":"2015-09-10T16:22:49Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Use 'ref-filter' APIs to implement the '--merged' and '--no-merged'\noptions into 'tag.c'. The '--merged' option lets the user to only list\ntags merged into the named commit. The '--no-merged' option lets the\nuser to only list tags not merged into the named commit.  If no object\nis provided it assumes HEAD as the object.\n\nAdd documentation and tests for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-tag.txt |  7 ++++++-\n builtin/tag.c             |  6 +++++-\n t/t7004-tag.sh            | 27 +++++++++++++++++++++++++++\n 3 files changed, 38 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 0c7f4e6..3803bf7 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n \t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]\n-\t[--format=<format>] [<pattern>...]\n+\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]\n 'git tag' -v <tagname>...\n \n DESCRIPTION\n@@ -165,6 +165,11 @@ This option is only applicable when listing tags without annotation lines.\n \tthat of linkgit:git-for-each-ref[1].  When unspecified,\n \tdefaults to `%(refname:short)`.\n \n+--[no-]merged [<commit>]::\n+\tOnly list tags whose tips are reachable, or not reachable\n+\tif '--no-merged' is used, from the specified commit ('HEAD'\n+\tif not specified).\n+\n CONFIGURATION\n -------------\n By default, 'git tag' in sign-with-default mode (-s) will use your\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 075d90b..081fe84 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -23,7 +23,7 @@ static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]\"\n-\t\t\"\\n\\t\\t[--format=<format>] [<pattern>...]\"),\n+\t\t\"\\n\\t\\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]\"),\n \tN_(\"git tag -v <tagname>...\"),\n \tNULL\n };\n@@ -359,6 +359,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n \t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n \t\tOPT_WITH(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n+\t\tOPT_MERGED(&filter, N_(\"print only tags that are merged\")),\n+\t\tOPT_NO_MERGED(&filter, N_(\"print only tags that are not merged\")),\n \t\tOPT_CALLBACK(0 , \"sort\", sorting_tail, N_(\"key\"),\n \t\t\t     N_(\"field name to sort on\"), &parse_opt_ref_sorting),\n \t\t{\n@@ -417,6 +419,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"--contains option is only allowed with -l.\"));\n \tif (filter.points_at.nr)\n \t\tdie(_(\"--points-at option is only allowed with -l.\"));\n+\tif (filter.merge_commit)\n+\t\tdie(_(\"--merged and --no-merged option are only allowed with -l\"));\n \tif (cmdmode == 'd')\n \t\treturn for_each_tag_name(argv, delete_tag);\n \tif (cmdmode == 'v')\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 8987fb1..3dd2f51 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1531,4 +1531,31 @@ test_expect_success '--format should list tags as per format given' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup --merged test tags' '\n+\tgit tag mergetest-1 HEAD~2 &&\n+\tgit tag mergetest-2 HEAD~1 &&\n+\tgit tag mergetest-3 HEAD\n+'\n+\n+test_expect_success '--merged cannot be used in non-list mode' '\n+\ttest_must_fail git tag --merged=mergetest-2 foo\n+'\n+\n+test_expect_success '--merged shows merged tags' '\n+\tcat >expect <<-\\EOF &&\n+\tmergetest-1\n+\tmergetest-2\n+\tEOF\n+\tgit tag -l --merged=mergetest-2 mergetest-* >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--no-merged show unmerged tags' '\n+\tcat >expect <<-\\EOF &&\n+\tmergetest-3\n+\tEOF\n+\tgit tag -l --no-merged=mergetest-2 mergetest-* >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269703","messageId":"vpqbndap6d1.fsf@scolette.imag.fr","threadId":"40316","inReplyTo":"1441900110-4015-6-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-10T16:49:14Z","receivedAt":"2015-09-10T16:49:14Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Introduce match_atom_name() which helps in checking if a particular\n> atom is the atom we're looking for and if it has a value attached to\n> it or not.\n>\n> Use it instead of starts_with() for checking the value of %(color:...)\n> atom. Write a test for the same.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\n> Thanks-to: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  ref-filter.c                   | 23 +++++++++++++++++++++--\n>  t/t6302-for-each-ref-filter.sh |  4 ++++\n>  2 files changed, 25 insertions(+), 2 deletions(-)\n>\n> diff --git a/ref-filter.c b/ref-filter.c\n> index a993216..70d36fe 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -189,6 +189,22 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n>  \t*stack = prev;\n>  }\n>  \n> +static int match_atom_name(const char *name, const char *atom_name, const char **val)\n> +{\n> +\tconst char *body;\n> +\n> +\tif (!skip_prefix(name, atom_name, &body))\n> +\t\treturn 0; /* doesn't even begin with \"atom_name\" */\n> +\tif (!body[0] || !body[1]) {\n> +\t\t*val = NULL; /* %(atom_name) and no customization */\n\nThe logic is still hard to follow. If I read correctly, this function\naccepts \"%(colorX)\" the same ways as \"%(color)\" here. I first thought it\nwas a bug, but it doesn't seem to be since %(colorX) would have been\nrejected earlier.\n\nIt would be a bug if colorX was actually a valid atom name though: you\nwould be returning 1 for match_atom_name(name, \"color\") when\nname==\"colorX\". So, I would say this \"we can accept one extra character\nbecause some earlier code rejected it before\" is too hard to follow for\nreviwers and too fragile.\n\nOTOH, you are now accepting %(atom:) as a synonym to %(atom), and it's\nnot clear whether this is a deliberate decition.\n\n> +\t\treturn 1;\n> +\t}\n> +\tif (body[0] != ':')\n> +\t\treturn 0; /* \"atom_namefoo\" is not \"atom_name\" or \"atom_name:...\" */\n> +\t*val = body + 1; /* \"atom_name:val\" */\n> +\treturn 1;\n> +}\n\nReversing the logic like this would be better IMHO:\n\nstatic int match_atom_name(const char *name, const char *atom_name, const char **val)\n{\n        const char *body;\n\tif (!skip_prefix(name, atom_name, &body)) {\n\t\t/* doesn't even begin with \"atom_name\" */\n\t\treturn 0;\n\t}\n\tif (!body[0]) {\n\t\t/* \"atom_name\" and no customization */\n                *val = NULL;\n                return 1;\n\t}\n\tif (body[0] != ':')\n\t\t/* \"atom_namefoo\" is not \"atom_name\" or \"atom_name:...\" */\n\t\treturn 0;\n\tif (!body[1]) {\n\t\t/* \"atom_name:\" */\n\t\t*val = NULL;\n\t\treturn 1;\n\t}\n\t/* \"atom_name:... */\n\t*val = body + 1;\n\treturn 1;\n}\n\n=> each case appears very clearly, and we check body[0] != ':' before\ntesting !body[1], so %(colorX) is rejected before noticing the '\\0'\nafter the 'X'.\n\n\"atom_name:\" appears explicitly. If we want to reject it, we know which\ncode to change.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"269704","messageId":"vpq37ymp66i.fsf@scolette.imag.fr","threadId":"40316","inReplyTo":"1441900110-4015-7-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 06/14] ref-filter: implement an `align` atom","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-10T16:53:09Z","receivedAt":"2015-09-10T16:53:09Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> +\t\t\t/*\n> +\t\t\t * TODO: Implement a function similar to strbuf_split_str()\n> +\t\t\t * which would omit the separator from the end of each value.\n> +\t\t\t */\n> +\t\t\ts = to_free = strbuf_split_str(valp, ',', 0);\n> +\n> +\t\t\talign->position = ALIGN_LEFT;\n> +\n> +\t\t\twhile (*s) {\n> +\t\t\t\tif (s[1])\n\nA comment like /* strip trailing ',' */ here would really help the\nreader IMHO.\n\n> +\t\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"269705","messageId":"xmqq37ym5i2u.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"1441900110-4015-6-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T16:56:25Z","receivedAt":"2015-09-10T16:56:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Introduce match_atom_name() which helps in checking if a particular\n> atom is the atom we're looking for and if it has a value attached to\n> it or not.\n>\n> Use it instead of starts_with() for checking the value of %(color:...)\n> atom. Write a test for the same.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\n> Thanks-to: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  ref-filter.c                   | 23 +++++++++++++++++++++--\n>  t/t6302-for-each-ref-filter.sh |  4 ++++\n>  2 files changed, 25 insertions(+), 2 deletions(-)\n>\n> diff --git a/ref-filter.c b/ref-filter.c\n> index a993216..70d36fe 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -189,6 +189,22 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n>  \t*stack = prev;\n>  }\n>  \n> +static int match_atom_name(const char *name, const char *atom_name, const char **val)\n> +{\n> +\tconst char *body;\n> +\n> +\tif (!skip_prefix(name, atom_name, &body))\n> +\t\treturn 0; /* doesn't even begin with \"atom_name\" */\n> +\tif (!body[0] || !body[1]) {\n> +\t\t*val = NULL; /* %(atom_name) and no customization */\n\nWhy do we check body[1] here?  I do not understand why you are not\nchecking !body[0] alone nothing else in this if condition.\n\nFor (atom_name=\"align\", name=\"aligna\"), should the function say that\n\"%(aligna)\" is an \"%(align)\" with no customization?\n\n> +\t\treturn 1;\n> +\t}\n> +\tif (body[0] != ':')\n> +\t\treturn 0; /* \"atom_namefoo\" is not \"atom_name\" or \"atom_name:...\" */\n> +\t*val = body + 1; /* \"atom_name:val\" */\n> +\treturn 1;\n> +}\n> +\n>  /*\n>   * In a format string, find the next occurrence of %(atom).\n>   */\n> @@ -687,6 +703,7 @@ static void populate_value(struct ref_array_item *ref)\n>  \t\tint deref = 0;\n>  \t\tconst char *refname;\n>  \t\tconst char *formatp;\n> +\t\tconst char *valp;\n>  \t\tstruct branch *branch = NULL;\n>  \n>  \t\tv->handler = append_atom;\n> @@ -721,10 +738,12 @@ static void populate_value(struct ref_array_item *ref)\n>  \t\t\trefname = branch_get_push(branch, NULL);\n>  \t\t\tif (!refname)\n>  \t\t\t\tcontinue;\n> -\t\t} else if (starts_with(name, \"color:\")) {\n> +\t\t} else if (match_atom_name(name, \"color\", &valp)) {\n\nWhy use the helper only for this one?  Aren't existing calls to\nstarts_with() in the same if/else if/... cascade all potential bugs\nthat the new helper function is meant to help fixing?  For example,\nthe very fist one in the cascade:\n\n\tif (starts_with(name, \"refname\"))\n        \trefname = ref->refname;\n\nis correct *ONLY* when name is \"refname\" or \"refname:\" followed by\nsomething, and it should skip \"refnamex\" when such a new atom is\nadded to valid_atom[] list, i.e. a bug waiting to happen.  I think\nthe new helper is designed to prevent such a bug from happening.\n\n>  \t\t\tchar color[COLOR_MAXLEN] = \"\";\n>  \n> -\t\t\tif (color_parse(name + 6, color) < 0)\n> +\t\t\tif (!valp)\n> +\t\t\t\tdie(_(\"expected format: %%(color:<color>)\"));\n> +\t\t\tif (color_parse(valp, color) < 0)\n>  \t\t\t\tdie(_(\"unable to parse format\"));\n>  \t\t\tv->s = xstrdup(color);\n>  \t\t\tcontinue;\n> diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\n> index 505a360..c4f0378 100755\n> --- a/t/t6302-for-each-ref-filter.sh\n> +++ b/t/t6302-for-each-ref-filter.sh\n> @@ -81,4 +81,8 @@ test_expect_success 'filtering with --contains' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success '%(color) must fail' '\n> +\ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n> +'\n> +\n>  test_done\n"},{"id":"269706","messageId":"vpqr3m6nrf9.fsf@scolette.imag.fr","threadId":"40316","inReplyTo":"1441900110-4015-1-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-10T16:57:14Z","receivedAt":"2015-09-10T16:57:14Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> This is part of the series of unifying the code used by\n> \"git tag -l, git branch -l, git for-each-ref\".\n>\n> The previous version can be found here (version 16):\n> article.gmane.org/gmane.comp.version-control.git/277394\n>\n> Changes in this version:\n> * The arguments of the %(align) atom are interchangeable.\n> * Small grammatical changes.\n> * Small changes in the tests to reflect changes in the align\n> atom code.\n\nClearly, we're almost there. I did a few minor remarks. I suggest\n(admitedly, Eric suggested of-list to suggest ;-) ) that you reply to\nthem by re-sending only individual patches that changed (replying to the\noriginal patch) so that we can check the new patches individually. I\nthink we can do the finishing touches for each patch in a subthread of\nthis patch.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"269707","messageId":"vpqio7inrc5.fsf@scolette.imag.fr","threadId":"40316","inReplyTo":"xmqq37ym5i2u.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-10T16:59:06Z","receivedAt":"2015-09-10T16:59:06Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> -\t\t} else if (starts_with(name, \"color:\")) {\n>> +\t\t} else if (match_atom_name(name, \"color\", &valp)) {\n>\n> Why use the helper only for this one?  Aren't existing calls to\n> starts_with() in the same if/else if/... cascade all potential bugs\n> that the new helper function is meant to help fixing?  For example,\n> the very fist one in the cascade:\n>\n> \tif (starts_with(name, \"refname\"))\n>         \trefname = ref->refname;\n>\n> is correct *ONLY* when name is \"refname\" or \"refname:\" followed by\n> something, and it should skip \"refnamex\" when such a new atom is\n> added to valid_atom[] list, i.e. a bug waiting to happen.  I think\n> the new helper is designed to prevent such a bug from happening.\n\nI fully agree, but I also think that this should be a separate topic.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"269708","messageId":"xmqqy4ge43dh.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"1441900110-4015-7-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 06/14] ref-filter: implement an `align` atom","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T16:59:22Z","receivedAt":"2015-09-10T16:59:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Implement an `align` atom which left-, middle-, or right-aligns the\n> content between %(align:...) and %(end).\n\nNicely done.\n"},{"id":"269709","messageId":"CAOLa=ZQM77+EzbHbY24nvu=hgNCa9MJ3wyz1XhFjk+3HdDd_Hg@mail.gmail.com","threadId":"40316","inReplyTo":"xmqq37ym5i2u.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T17:00:46Z","receivedAt":"2015-09-10T17:00:46Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Sep 10, 2015 at 10:26 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> Introduce match_atom_name() which helps in checking if a particular\n>> atom is the atom we're looking for and if it has a value attached to\n>> it or not.\n>>\n>> Use it instead of starts_with() for checking the value of %(color:...)\n>> atom. Write a test for the same.\n>>\n>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\n>> Thanks-to: Junio C Hamano <gitster@pobox.com>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>> ---\n>>  ref-filter.c                   | 23 +++++++++++++++++++++--\n>>  t/t6302-for-each-ref-filter.sh |  4 ++++\n>>  2 files changed, 25 insertions(+), 2 deletions(-)\n>>\n>> diff --git a/ref-filter.c b/ref-filter.c\n>> index a993216..70d36fe 100644\n>> --- a/ref-filter.c\n>> +++ b/ref-filter.c\n>> @@ -189,6 +189,22 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n>>       *stack = prev;\n>>  }\n>>\n>> +static int match_atom_name(const char *name, const char *atom_name, const char **val)\n>> +{\n>> +     const char *body;\n>> +\n>> +     if (!skip_prefix(name, atom_name, &body))\n>> +             return 0; /* doesn't even begin with \"atom_name\" */\n>> +     if (!body[0] || !body[1]) {\n>> +             *val = NULL; /* %(atom_name) and no customization */\n>\n> Why do we check body[1] here?  I do not understand why you are not\n> checking !body[0] alone nothing else in this if condition.\n>\n> For (atom_name=\"align\", name=\"aligna\"), should the function say that\n> \"%(aligna)\" is an \"%(align)\" with no customization?\n>\n\nThe check was for checking if there is anything after the colon,\nMatthieu's modified version seems better though.\n\n>\n> Why use the helper only for this one?  Aren't existing calls to\n> starts_with() in the same if/else if/... cascade all potential bugs\n> that the new helper function is meant to help fixing?  For example,\n> the very fist one in the cascade:\n>\n>         if (starts_with(name, \"refname\"))\n>                 refname = ref->refname;\n>\n> is correct *ONLY* when name is \"refname\" or \"refname:\" followed by\n> something, and it should skip \"refnamex\" when such a new atom is\n> added to valid_atom[] list, i.e. a bug waiting to happen.  I think\n> the new helper is designed to prevent such a bug from happening.\n>\n\nYes definitely, but it would work with only refname, whereas\nthe color atom had no check before this patch, I didn't want to introduce\nthose patches in this series.\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269710","messageId":"xmqqtwr242of.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"1441900110-4015-9-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 08/14] ref-filter: add support for %(contents:lines=X)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T17:14:24Z","receivedAt":"2015-09-10T17:14:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> diff --git a/ref-filter.c b/ref-filter.c\n> index 7d2732a..b098b16 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -56,6 +56,7 @@ static struct {\n>  \t{ \"color\" },\n>  \t{ \"align\" },\n>  \t{ \"end\" },\n> +\t{ \"contents:lines\" },\n\nDo we even need \"contents:lines\" and existing other \"contents:blah\"\nin this list in the first place?  If they are needed, group them\ntogether, not append at the end.\n\nI wonder how this code sensibly can parse \"%(contents:lines=6)\".\nAfter splitting the format string at %( and closing ), the code\ncalls parse_ref_filter_atom() and the rule that helper function uses\nto figure out the atom-name proper (which is to be checked against\nthe valid_atom[] array) is to find the first colon, so\n\n    %(contents:lines=6)\n\nwould cause \"contents:lines=6\" to be fed parse_ref_filter_atom(),\nit cheks if \"contents\" is in the valid_atom[] array (it is), and\nstores the whole thing in used_atom[].\n\nSo in that sense, match_atom_name() would do the right thing, but\nthat would make any reader of this code realize that she never saw\n\"contents:lines\" entry in valid_atom[] array being used during this\nprocess.\n"},{"id":"269711","messageId":"xmqqpp1q428y.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"vpqbndap6d1.fsf@scolette.imag.fr","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T17:23:41Z","receivedAt":"2015-09-10T17:23:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:\n\n> OTOH, you are now accepting %(atom:) as a synonym to %(atom), and it's\n> not clear whether this is a deliberate decition.\n\nI would say so.  When the caller wants to reject %(atom:), the\ncaller can tell it by checking val[0] == '\\0' and reject that.\n\nSo it is better if you did not do this:\n\n> \tif (!body[1]) {\n> \t\t/* \"atom_name:\" */\n> \t\t*val = NULL;\n> \t\treturn 1;\n> \t}\n\nwhich robs that information from the caller.  It should be\nsufficient to just drop the check that allows \"colorx\" when\nexpecting \"color\" without making any other change, I would think.\n"},{"id":"269712","messageId":"xmqqlhce420b.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"CAOLa=ZQM77+EzbHbY24nvu=hgNCa9MJ3wyz1XhFjk+3HdDd_Hg@mail.gmail.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T17:28:52Z","receivedAt":"2015-09-10T17:28:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> The check was for checking if there is anything after the colon,\n\nWhy do you even care?  If %(color) expects more specific\ncustomization by adding colon followed by specific data after it,\ni.e. %(color:something), %(color:) should clearly be that \"%(color)\"\nthing with no customization---if it is \"not enough customization\" or\n\"leaving everything default\" depends on each atom, match_atom_name()\nis not a good place to make that policy decision (i.e. Mattheiu's\nrewrite to clear *valp to NULL when %(color:) is seen).  Instead,\npoint *val to body + 1 just as usual, and let the caller tell\nbetween *val being NULL (not even any colon) and *val pointing at a\nNUL byte (nothing after colon) if it cares.\n"},{"id":"269714","messageId":"CAOLa=ZQXP-U+dGYYFcvxVmuG2NOSiHYLHEwm2OtvhGQrBW9hdQ@mail.gmail.com","threadId":"40316","inReplyTo":"xmqqlhce420b.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T17:35:48Z","receivedAt":"2015-09-10T17:35:48Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Sep 10, 2015 at 10:58 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> The check was for checking if there is anything after the colon,\n>\n> Why do you even care?  If %(color) expects more specific\n> customization by adding colon followed by specific data after it,\n> i.e. %(color:something), %(color:) should clearly be that \"%(color)\"\n> thing with no customization---if it is \"not enough customization\" or\n> \"leaving everything default\" depends on each atom, match_atom_name()\n> is not a good place to make that policy decision (i.e. Mattheiu's\n> rewrite to clear *valp to NULL when %(color:) is seen).  Instead,\n> point *val to body + 1 just as usual, and let the caller tell\n> between *val being NULL (not even any colon) and *val pointing at a\n> NUL byte (nothing after colon) if it cares.\n\nIt is one thing that the user can actually do the check themselves,\nbut doesn't it make more sense that when we're using colon we expect a\nvalue after it, and something like %(color:) makes no sense when color\nspecifically needs a value after the colon.\n\nHence rather than assuming that the no value is given and fallback to\nthe default\n%(color), wouldn't it be better to warn the user that there is no value?\n\nBut then again we can leave this decision to the user like you said.\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269715","messageId":"CAOLa=ZQ2e-U+=-qS71MwwtnpQyYnc1Ls3t=JdMi_UvxGBG5_3A@mail.gmail.com","threadId":"40316","inReplyTo":"xmqqtwr242of.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 08/14] ref-filter: add support for %(contents:lines=X)","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T17:37:35Z","receivedAt":"2015-09-10T17:37:35Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Sep 10, 2015 at 10:44 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> diff --git a/ref-filter.c b/ref-filter.c\n>> index 7d2732a..b098b16 100644\n>> --- a/ref-filter.c\n>> +++ b/ref-filter.c\n>> @@ -56,6 +56,7 @@ static struct {\n>>       { \"color\" },\n>>       { \"align\" },\n>>       { \"end\" },\n>> +     { \"contents:lines\" },\n>\n> Do we even need \"contents:lines\" and existing other \"contents:blah\"\n> in this list in the first place?  If they are needed, group them\n> together, not append at the end.\n>\n> I wonder how this code sensibly can parse \"%(contents:lines=6)\".\n> After splitting the format string at %( and closing ), the code\n> calls parse_ref_filter_atom() and the rule that helper function uses\n> to figure out the atom-name proper (which is to be checked against\n> the valid_atom[] array) is to find the first colon, so\n>\n>     %(contents:lines=6)\n>\n> would cause \"contents:lines=6\" to be fed parse_ref_filter_atom(),\n> it cheks if \"contents\" is in the valid_atom[] array (it is), and\n> stores the whole thing in used_atom[].\n>\n> So in that sense, match_atom_name() would do the right thing, but\n> that would make any reader of this code realize that she never saw\n> \"contents:lines\" entry in valid_atom[] array being used during this\n> process.\n\nTrue.\nWe could remove all the contents:<subvalue> atoms from this list.\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269717","messageId":"xmqqd1xq419b.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"CAOLa=ZQXP-U+dGYYFcvxVmuG2NOSiHYLHEwm2OtvhGQrBW9hdQ@mail.gmail.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T17:45:04Z","receivedAt":"2015-09-10T17:45:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> It is one thing that the user can actually do the check themselves,\n> but doesn't it make more sense that when we're using colon we expect a\n> value after it, and something like %(color:) makes no sense when color\n> specifically needs a value after the colon.\n\nIf you imagine the format being built by scripts (we are talking\nabout plumbing feature --format here), I think you will realize that\nit perfectly makes sense to allow them to say \"%(atom:$modifiation)\"\nwithout having to worry about a special case where $modification\nhappened to end up being empty.  So no, I do not agree with your\nstatement at all.\n"},{"id":"269718","messageId":"CAOLa=ZS1iVW+=8=TMnOw9Ao_bg0nnhx-4tpyesHjOiYAsTCD4Q@mail.gmail.com","threadId":"40316","inReplyTo":"xmqqd1xq419b.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T17:49:49Z","receivedAt":"2015-09-10T17:49:49Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Sep 10, 2015 at 11:15 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> It is one thing that the user can actually do the check themselves,\n>> but doesn't it make more sense that when we're using colon we expect a\n>> value after it, and something like %(color:) makes no sense when color\n>> specifically needs a value after the colon.\n>\n> If you imagine the format being built by scripts (we are talking\n> about plumbing feature --format here), I think you will realize that\n> it perfectly makes sense to allow them to say \"%(atom:$modifiation)\"\n> without having to worry about a special case where $modification\n> happened to end up being empty.  So no, I do not agree with your\n> statement at all.\n\nAh! that makes sense, thanks :)\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269719","messageId":"xmqq8u8e40l2.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"1441902169-9891-2-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 13/14] tag.c: implement '--format' option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T17:59:37Z","receivedAt":"2015-09-10T17:59:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> -static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n> +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)\n>  {\n>  \tstruct ref_array array;\n> -\tchar *format, *to_free = NULL;\n> +\tchar *to_free = NULL;\n>  \tint i;\n\nformat is const char * while to_free is non-const char * here.\n\n> @@ -41,12 +41,14 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n>  \tif (filter->lines == -1)\n>  \t\tfilter->lines = 0;\n>  \n> +\tif (!format) {\n> +\t\tif (filter->lines) {\n> +\t\t\tformat = xstrfmt(\"%s %%(contents:lines=%d)\",\n> +\t\t\t\t\t \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n\nHmmm, did this even pass tests and if so how?  What are these double\n%% doing before refname and end?  Perhaps we do not have enough test\ncoverage?\n\n> +\t\t\tto_free = format;\n\nThis assignment discards constness.\n\nTake the result of xstrfmt() to to_free (which is a non-const\npointer) and then assigning it to format (which is a const pointer)\nwould work it around.\n"},{"id":"269721","messageId":"CAOLa=ZQeFSZtLHK_sZMTHpdw_UTL_yk=Nrmu-4vC0vy2_hR7mw@mail.gmail.com","threadId":"40316","inReplyTo":"xmqq8u8e40l2.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 13/14] tag.c: implement '--format' option","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-10T18:04:42Z","receivedAt":"2015-09-10T18:04:42Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Sep 10, 2015 at 11:29 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> -static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n>> +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)\n>>  {\n>>       struct ref_array array;\n>> -     char *format, *to_free = NULL;\n>> +     char *to_free = NULL;\n>>       int i;\n>\n> format is const char * while to_free is non-const char * here.\n>\n>> @@ -41,12 +41,14 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n>>       if (filter->lines == -1)\n>>               filter->lines = 0;\n>>\n>> +     if (!format) {\n>> +             if (filter->lines) {\n>> +                     format = xstrfmt(\"%s %%(contents:lines=%d)\",\n>> +                                      \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n>\n> Hmmm, did this even pass tests and if so how?  What are these double\n> %% doing before refname and end?  Perhaps we do not have enough test\n> coverage?\n>\n\nSeems like I didn't run the tests here, will change this.\n\n>> +                     to_free = format;\n>\n> This assignment discards constness.\n>\n> Take the result of xstrfmt() to to_free (which is a non-const\n> pointer) and then assigning it to format (which is a const pointer)\n> would work it around.\n\nYeah!\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269723","messageId":"xmqq4mj23zei.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"vpqio7inrc5.fsf@scolette.imag.fr","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-10T18:25:09Z","receivedAt":"2015-09-10T18:25:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>\n>>> -\t\t} else if (starts_with(name, \"color:\")) {\n>>> +\t\t} else if (match_atom_name(name, \"color\", &valp)) {\n>>\n>> Why use the helper only for this one?  Aren't existing calls to\n>> starts_with() in the same if/else if/... cascade all potential bugs\n>> that the new helper function is meant to help fixing?  For example,\n>> the very fist one in the cascade:\n>>\n>> \tif (starts_with(name, \"refname\"))\n>>         \trefname = ref->refname;\n>>\n>> is correct *ONLY* when name is \"refname\" or \"refname:\" followed by\n>> something, and it should skip \"refnamex\" when such a new atom is\n>> added to valid_atom[] list, i.e. a bug waiting to happen.  I think\n>> the new helper is designed to prevent such a bug from happening.\n>\n> I fully agree, but I also think that this should be a separate topic.\n\nYeah, it can be a separate topic.  I am neutral (i.e. I certainly\nwould not insist that the existing one should be fixed with the\nhelper in the series, but I cannot quite say that I prefer the fix\nto be made outside this topic, either).\n\nThanks.\n"},{"id":"269729","messageId":"vpq1te6aype.fsf@scolette.imag.fr","threadId":"40316","inReplyTo":"xmqqpp1q428y.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-10T18:58:21Z","receivedAt":"2015-09-10T18:58:21Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:\n>\n>> OTOH, you are now accepting %(atom:) as a synonym to %(atom), and it's\n>> not clear whether this is a deliberate decition.\n>\n> I would say so.  When the caller wants to reject %(atom:), the\n> caller can tell it by checking val[0] == '\\0' and reject that.\n>\n> So it is better if you did not do this:\n>\n>> \tif (!body[1]) {\n>> \t\t/* \"atom_name:\" */\n>> \t\t*val = NULL;\n>> \t\treturn 1;\n>> \t}\n>\n> which robs that information from the caller.\n\nOK. Just dropping this part lets the code fall back to\n\n\t/* \"atom_name:... */\n\t*val = body + 1;\n\treturn 1;\n\nright below in my version. It also accepts it (return 1) but lets val\npoint to an empty string. Makes sense.\n\nAnd indeed, without this, my code looks a lot like Karthik's one, just\ndropping the \"|| !body[1]\" part in a condition.\n\nIn any case, I'd like to see \"atom_name:\" explicitly mentionned\nsomewhere in a comment, if only to make it clear that what is done with\nit is deliberate (e.g. avoid having someone not following this\nconversation later considering this %(atom:) thing to be a bug and try\nto fix it).\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"269760","messageId":"1441983587-32568-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-6-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 05/14] ref-filter: introduce match_atom_name()","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T14:59:47Z","receivedAt":"2015-09-11T14:59:47Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Introduce match_atom_name() which helps in checking if a particular\natom is the atom we're looking for and if it has a value attached to\nit or not.\n\nUse it instead of starts_with() for checking the value of %(color:...)\natom. Write a test for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nThanks-to: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n ref-filter.c                   | 23 +++++++++++++++++++++--\n t/t6302-for-each-ref-filter.sh |  4 ++++\n 2 files changed, 25 insertions(+), 2 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex a993216..514de34 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -189,6 +189,22 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n \t*stack = prev;\n }\n \n+static int match_atom_name(const char *name, const char *atom_name, const char **val)\n+{\n+\tconst char *body;\n+\n+\tif (!skip_prefix(name, atom_name, &body))\n+\t\treturn 0; /* doesn't even begin with \"atom_name\" */\n+\tif (!body[0]) {\n+\t\t*val = NULL; /* %(atom_name) and no customization */\n+\t\treturn 1;\n+\t}\n+\tif (body[0] != ':')\n+\t\treturn 0; /* \"atom_namefoo\" is not \"atom_name\" or \"atom_name:...\" */\n+\t*val = body + 1; /* \"atom_name:val\" */\n+\treturn 1;\n+}\n+\n /*\n  * In a format string, find the next occurrence of %(atom).\n  */\n@@ -687,6 +703,7 @@ static void populate_value(struct ref_array_item *ref)\n \t\tint deref = 0;\n \t\tconst char *refname;\n \t\tconst char *formatp;\n+\t\tconst char *valp;\n \t\tstruct branch *branch = NULL;\n \n \t\tv->handler = append_atom;\n@@ -721,10 +738,12 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\trefname = branch_get_push(branch, NULL);\n \t\t\tif (!refname)\n \t\t\t\tcontinue;\n-\t\t} else if (starts_with(name, \"color:\")) {\n+\t\t} else if (match_atom_name(name, \"color\", &valp)) {\n \t\t\tchar color[COLOR_MAXLEN] = \"\";\n \n-\t\t\tif (color_parse(name + 6, color) < 0)\n+\t\t\tif (!valp)\n+\t\t\t\tdie(_(\"expected format: %%(color:<color>)\"));\n+\t\t\tif (color_parse(valp, color) < 0)\n \t\t\t\tdie(_(\"unable to parse format\"));\n \t\t\tv->s = xstrdup(color);\n \t\t\tcontinue;\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 505a360..c4f0378 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -81,4 +81,8 @@ test_expect_success 'filtering with --contains' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '%(color) must fail' '\n+\ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269761","messageId":"1441983719-32733-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-6-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 06/14] ref-filter: implement an `align` atom","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T15:01:59Z","receivedAt":"2015-09-11T15:01:59Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Implement an `align` atom which left-, middle-, or right-aligns the\ncontent between %(align:...) and %(end).\n\nThe \"align:\" is followed by `<width>` and `<position>` in any order\nseparated by a comma, where the `<position>` is either left, right or\nmiddle, default being left and `<width>` is the total length of the\ncontent with alignment. If the contents length is more than the width\nthen no alignment is performed.  e.g. to align a refname atom to the\nmiddle with a total width of 40 we can do:\n--format=\"%(align:middle,40)%(refname)%(end)\".\n\nWe introduce an `at_end` function for each element of the stack which\nis to be called when the `end` atom is encountered. Using this we\nimplement end_align_handler() for the `align` atom, this aligns the\nfinal strbuf by calling `strbuf_utf8_align()` from utf8.c.\n\nEnsure that quote formatting is performed on the whole of\n%(align:...)...%(end) rather than individual atoms inside. We skip\nquote formatting for individual atoms when the current stack element\nis handling an %(align:...) atom and perform quote formatting at the\nend when we encounter the %(end) atom of the second element of then\nstack.\n\nAdd documentation and tests for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  11 ++++\n ref-filter.c                       | 110 ++++++++++++++++++++++++++++++++++++-\n t/t6302-for-each-ref-filter.sh     |  82 +++++++++++++++++++++++++++\n 3 files changed, 202 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex e49d578..3a271bf 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -127,6 +127,17 @@ color::\n \tChange output color.  Followed by `:<colorname>`, where names\n \tare described in `color.branch.*`.\n \n+align::\n+\tLeft-, middle-, or right-align the content between\n+\t%(align:...) and %(end). The \"align:\" is followed by `<width>`\n+\tand `<position>` in any order separated by a comma, where the\n+\t`<position>` is either left, right or middle, default being\n+\tleft and `<width>` is the total length of the content with\n+\talignment. If the contents length is more than the width then\n+\tno alignment is performed. If used with '--quote' everything\n+\tin between %(align:...) and %(end) is quoted, but if nested\n+\tthen only the topmost level performs quoting.\n+\n In addition to the above, for commit and tag objects, the header\n field names (`tree`, `parent`, `object`, `type`, and `tag`) can\n be used to specify the value in the header field.\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 514de34..c65cd60 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -10,6 +10,7 @@\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"revision.h\"\n+#include \"utf8.h\"\n \n typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;\n \n@@ -53,13 +54,22 @@ static struct {\n \t{ \"flag\" },\n \t{ \"HEAD\" },\n \t{ \"color\" },\n+\t{ \"align\" },\n+\t{ \"end\" },\n };\n \n #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n \n+struct align {\n+\talign_type position;\n+\tunsigned int width;\n+};\n+\n struct ref_formatting_stack {\n \tstruct ref_formatting_stack *prev;\n \tstruct strbuf output;\n+\tvoid (*at_end)(struct ref_formatting_stack *stack);\n+\tvoid *at_end_data;\n };\n \n struct ref_formatting_state {\n@@ -69,6 +79,9 @@ struct ref_formatting_state {\n \n struct atom_value {\n \tconst char *s;\n+\tunion {\n+\t\tstruct align align;\n+\t} u;\n \tvoid (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n };\n@@ -165,7 +178,16 @@ static void quote_formatting(struct strbuf *s, const char *str, int quote_style)\n \n static void append_atom(struct atom_value *v, struct ref_formatting_state *state)\n {\n-\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\t/*\n+\t * Quote formatting is only done when the stack has a single\n+\t * element. Otherwise quote formatting is done on the\n+\t * element's entire output strbuf when the %(end) atom is\n+\t * encountered.\n+\t */\n+\tif (!state->stack->prev)\n+\t\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\telse\n+\t\tstrbuf_addstr(&state->stack->output, v->s);\n }\n \n static void push_stack_element(struct ref_formatting_stack **stack)\n@@ -189,6 +211,48 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n \t*stack = prev;\n }\n \n+static void end_align_handler(struct ref_formatting_stack *stack)\n+{\n+\tstruct align *align = (struct align *)stack->at_end_data;\n+\tstruct strbuf s = STRBUF_INIT;\n+\n+\tstrbuf_utf8_align(&s, align->position, align->width, stack->output.buf);\n+\tstrbuf_swap(&stack->output, &s);\n+\tstrbuf_release(&s);\n+}\n+\n+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)\n+{\n+\tstruct ref_formatting_stack *new;\n+\n+\tpush_stack_element(&state->stack);\n+\tnew = state->stack;\n+\tnew->at_end = end_align_handler;\n+\tnew->at_end_data = &atomv->u.align;\n+}\n+\n+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)\n+{\n+\tstruct ref_formatting_stack *current = state->stack;\n+\tstruct strbuf s = STRBUF_INIT;\n+\n+\tif (!current->at_end)\n+\t\tdie(_(\"format: %%(end) atom used without corresponding atom\"));\n+\tcurrent->at_end(current);\n+\n+\t/*\n+\t * Perform quote formatting when the stack element is that of\n+\t * a supporting atom. If nested then perform quote formatting\n+\t * only on the topmost supporting atom.\n+\t */\n+\tif (!state->stack->prev->prev) {\n+\t\tquote_formatting(&s, current->output.buf, state->quote_style);\n+\t\tstrbuf_swap(&current->output, &s);\n+\t}\n+\tstrbuf_release(&s);\n+\tpop_stack_element(&state->stack);\n+}\n+\n static int match_atom_name(const char *name, const char *atom_name, const char **val)\n {\n \tconst char *body;\n@@ -773,6 +837,48 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\telse\n \t\t\t\tv->s = \" \";\n \t\t\tcontinue;\n+\t\t} else if (match_atom_name(name, \"align\", &valp)) {\n+\t\t\tstruct align *align = &v->u.align;\n+\t\t\tstruct strbuf **s, **to_free;\n+\t\t\tint width = -1;\n+\n+\t\t\tif (!valp)\n+\t\t\t\tdie(_(\"expected format: %%(align:<width>,<position>)\"));\n+\n+\t\t\t/*\n+\t\t\t * TODO: Implement a function similar to strbuf_split_str()\n+\t\t\t * which would omit the separator from the end of each value.\n+\t\t\t */\n+\t\t\ts = to_free = strbuf_split_str(valp, ',', 0);\n+\n+\t\t\talign->position = ALIGN_LEFT;\n+\n+\t\t\twhile (*s) {\n+\t\t\t\t/*  Strip trailing comma */\n+\t\t\t\tif (s[1])\n+\t\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n+\t\t\t\tif (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n+\t\t\t\t\t;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"left\"))\n+\t\t\t\t\talign->position = ALIGN_LEFT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"right\"))\n+\t\t\t\t\talign->position = ALIGN_RIGHT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"middle\"))\n+\t\t\t\t\talign->position = ALIGN_MIDDLE;\n+\t\t\t\telse\n+\t\t\t\t\tdie(_(\"improper format entered align:%s\"), s[0]->buf);\n+\t\t\t\ts++;\n+\t\t\t}\n+\n+\t\t\tif (width < 0)\n+\t\t\t\tdie(_(\"positive width expected with the %%(align) atom\"));\n+\t\t\talign->width = width;\n+\t\t\tstrbuf_list_free(to_free);\n+\t\t\tv->handler = align_atom_handler;\n+\t\t\tcontinue;\n+\t\t} else if (!strcmp(name, \"end\")) {\n+\t\t\tv->handler = end_atom_handler;\n+\t\t\tcontinue;\n \t\t} else\n \t\t\tcontinue;\n \n@@ -1347,6 +1453,8 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu\n \t\tresetv.s = color;\n \t\tappend_atom(&resetv, &state);\n \t}\n+\tif (state.stack->prev)\n+\t\tdie(_(\"format: %%(end) atom missing\"));\n \tfinal_buf = &state.stack->output;\n \tfwrite(final_buf->buf, 1, final_buf->len, stdout);\n \tpop_stack_element(&state.stack);\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex c4f0378..f596035 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -85,4 +85,86 @@ test_expect_success '%(color) must fail' '\n \ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n '\n \n+test_expect_success 'left alignment is default' '\n+\tcat >expect <<-\\EOF &&\n+\trefname is refs/heads/master  |refs/heads/master\n+\trefname is refs/heads/side    |refs/heads/side\n+\trefname is refs/odd/spot      |refs/odd/spot\n+\trefname is refs/tags/double-tag|refs/tags/double-tag\n+\trefname is refs/tags/four     |refs/tags/four\n+\trefname is refs/tags/one      |refs/tags/one\n+\trefname is refs/tags/signed-tag|refs/tags/signed-tag\n+\trefname is refs/tags/three    |refs/tags/three\n+\trefname is refs/tags/two      |refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"%(align:30)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'middle alignment' '\n+\tcat >expect <<-\\EOF &&\n+\t| refname is refs/heads/master |refs/heads/master\n+\t|  refname is refs/heads/side  |refs/heads/side\n+\t|   refname is refs/odd/spot   |refs/odd/spot\n+\t|refname is refs/tags/double-tag|refs/tags/double-tag\n+\t|  refname is refs/tags/four   |refs/tags/four\n+\t|   refname is refs/tags/one   |refs/tags/one\n+\t|refname is refs/tags/signed-tag|refs/tags/signed-tag\n+\t|  refname is refs/tags/three  |refs/tags/three\n+\t|   refname is refs/tags/two   |refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"|%(align:middle,30)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'right alignment' '\n+\tcat >expect <<-\\EOF &&\n+\t|  refname is refs/heads/master|refs/heads/master\n+\t|    refname is refs/heads/side|refs/heads/side\n+\t|      refname is refs/odd/spot|refs/odd/spot\n+\t|refname is refs/tags/double-tag|refs/tags/double-tag\n+\t|     refname is refs/tags/four|refs/tags/four\n+\t|      refname is refs/tags/one|refs/tags/one\n+\t|refname is refs/tags/signed-tag|refs/tags/signed-tag\n+\t|    refname is refs/tags/three|refs/tags/three\n+\t|      refname is refs/tags/two|refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"|%(align:30,right)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+# Individual atoms inside %(align:...) and %(end) must not be quoted.\n+\n+test_expect_success 'alignment with format quote' \"\n+\tcat >expect <<-\\EOF &&\n+\t|'      '\\''master| A U Thor'\\''      '|\n+\t|'       '\\''side| A U Thor'\\''       '|\n+\t|'     '\\''odd/spot| A U Thor'\\''     '|\n+\t|'        '\\''double-tag| '\\''        '|\n+\t|'       '\\''four| A U Thor'\\''       '|\n+\t|'       '\\''one| A U Thor'\\''        '|\n+\t|'        '\\''signed-tag| '\\''        '|\n+\t|'      '\\''three| A U Thor'\\''       '|\n+\t|'       '\\''two| A U Thor'\\''        '|\n+\tEOF\n+\tgit for-each-ref --shell --format=\\\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\\\" >actual &&\n+\ttest_cmp expect actual\n+\"\n+\n+test_expect_success 'nested alignment with quote formatting' \"\n+\tcat >expect <<-\\EOF &&\n+\t|'         master               '|\n+\t|'           side               '|\n+\t|'       odd/spot               '|\n+\t|'     double-tag               '|\n+\t|'           four               '|\n+\t|'            one               '|\n+\t|'     signed-tag               '|\n+\t|'          three               '|\n+\t|'            two               '|\n+\tEOF\n+\tgit for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&\n+\ttest_cmp expect actual\n+\"\n+\n test_done\n-- \n2.5.1\n"},{"id":"269762","messageId":"1441983787-407-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-7-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 06/14] ref-filter: implement an `align` atom","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T15:03:07Z","receivedAt":"2015-09-11T15:03:07Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Implement an `align` atom which left-, middle-, or right-aligns the\ncontent between %(align:...) and %(end).\n\nThe \"align:\" is followed by `<width>` and `<position>` in any order\nseparated by a comma, where the `<position>` is either left, right or\nmiddle, default being left and `<width>` is the total length of the\ncontent with alignment. If the contents length is more than the width\nthen no alignment is performed.  e.g. to align a refname atom to the\nmiddle with a total width of 40 we can do:\n--format=\"%(align:middle,40)%(refname)%(end)\".\n\nWe introduce an `at_end` function for each element of the stack which\nis to be called when the `end` atom is encountered. Using this we\nimplement end_align_handler() for the `align` atom, this aligns the\nfinal strbuf by calling `strbuf_utf8_align()` from utf8.c.\n\nEnsure that quote formatting is performed on the whole of\n%(align:...)...%(end) rather than individual atoms inside. We skip\nquote formatting for individual atoms when the current stack element\nis handling an %(align:...) atom and perform quote formatting at the\nend when we encounter the %(end) atom of the second element of then\nstack.\n\nAdd documentation and tests for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  11 ++++\n ref-filter.c                       | 110 ++++++++++++++++++++++++++++++++++++-\n t/t6302-for-each-ref-filter.sh     |  82 +++++++++++++++++++++++++++\n 3 files changed, 202 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex e49d578..3a271bf 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -127,6 +127,17 @@ color::\n \tChange output color.  Followed by `:<colorname>`, where names\n \tare described in `color.branch.*`.\n \n+align::\n+\tLeft-, middle-, or right-align the content between\n+\t%(align:...) and %(end). The \"align:\" is followed by `<width>`\n+\tand `<position>` in any order separated by a comma, where the\n+\t`<position>` is either left, right or middle, default being\n+\tleft and `<width>` is the total length of the content with\n+\talignment. If the contents length is more than the width then\n+\tno alignment is performed. If used with '--quote' everything\n+\tin between %(align:...) and %(end) is quoted, but if nested\n+\tthen only the topmost level performs quoting.\n+\n In addition to the above, for commit and tag objects, the header\n field names (`tree`, `parent`, `object`, `type`, and `tag`) can\n be used to specify the value in the header field.\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 514de34..c65cd60 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -10,6 +10,7 @@\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"revision.h\"\n+#include \"utf8.h\"\n \n typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;\n \n@@ -53,13 +54,22 @@ static struct {\n \t{ \"flag\" },\n \t{ \"HEAD\" },\n \t{ \"color\" },\n+\t{ \"align\" },\n+\t{ \"end\" },\n };\n \n #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n \n+struct align {\n+\talign_type position;\n+\tunsigned int width;\n+};\n+\n struct ref_formatting_stack {\n \tstruct ref_formatting_stack *prev;\n \tstruct strbuf output;\n+\tvoid (*at_end)(struct ref_formatting_stack *stack);\n+\tvoid *at_end_data;\n };\n \n struct ref_formatting_state {\n@@ -69,6 +79,9 @@ struct ref_formatting_state {\n \n struct atom_value {\n \tconst char *s;\n+\tunion {\n+\t\tstruct align align;\n+\t} u;\n \tvoid (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n };\n@@ -165,7 +178,16 @@ static void quote_formatting(struct strbuf *s, const char *str, int quote_style)\n \n static void append_atom(struct atom_value *v, struct ref_formatting_state *state)\n {\n-\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\t/*\n+\t * Quote formatting is only done when the stack has a single\n+\t * element. Otherwise quote formatting is done on the\n+\t * element's entire output strbuf when the %(end) atom is\n+\t * encountered.\n+\t */\n+\tif (!state->stack->prev)\n+\t\tquote_formatting(&state->stack->output, v->s, state->quote_style);\n+\telse\n+\t\tstrbuf_addstr(&state->stack->output, v->s);\n }\n \n static void push_stack_element(struct ref_formatting_stack **stack)\n@@ -189,6 +211,48 @@ static void pop_stack_element(struct ref_formatting_stack **stack)\n \t*stack = prev;\n }\n \n+static void end_align_handler(struct ref_formatting_stack *stack)\n+{\n+\tstruct align *align = (struct align *)stack->at_end_data;\n+\tstruct strbuf s = STRBUF_INIT;\n+\n+\tstrbuf_utf8_align(&s, align->position, align->width, stack->output.buf);\n+\tstrbuf_swap(&stack->output, &s);\n+\tstrbuf_release(&s);\n+}\n+\n+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)\n+{\n+\tstruct ref_formatting_stack *new;\n+\n+\tpush_stack_element(&state->stack);\n+\tnew = state->stack;\n+\tnew->at_end = end_align_handler;\n+\tnew->at_end_data = &atomv->u.align;\n+}\n+\n+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)\n+{\n+\tstruct ref_formatting_stack *current = state->stack;\n+\tstruct strbuf s = STRBUF_INIT;\n+\n+\tif (!current->at_end)\n+\t\tdie(_(\"format: %%(end) atom used without corresponding atom\"));\n+\tcurrent->at_end(current);\n+\n+\t/*\n+\t * Perform quote formatting when the stack element is that of\n+\t * a supporting atom. If nested then perform quote formatting\n+\t * only on the topmost supporting atom.\n+\t */\n+\tif (!state->stack->prev->prev) {\n+\t\tquote_formatting(&s, current->output.buf, state->quote_style);\n+\t\tstrbuf_swap(&current->output, &s);\n+\t}\n+\tstrbuf_release(&s);\n+\tpop_stack_element(&state->stack);\n+}\n+\n static int match_atom_name(const char *name, const char *atom_name, const char **val)\n {\n \tconst char *body;\n@@ -773,6 +837,48 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\telse\n \t\t\t\tv->s = \" \";\n \t\t\tcontinue;\n+\t\t} else if (match_atom_name(name, \"align\", &valp)) {\n+\t\t\tstruct align *align = &v->u.align;\n+\t\t\tstruct strbuf **s, **to_free;\n+\t\t\tint width = -1;\n+\n+\t\t\tif (!valp)\n+\t\t\t\tdie(_(\"expected format: %%(align:<width>,<position>)\"));\n+\n+\t\t\t/*\n+\t\t\t * TODO: Implement a function similar to strbuf_split_str()\n+\t\t\t * which would omit the separator from the end of each value.\n+\t\t\t */\n+\t\t\ts = to_free = strbuf_split_str(valp, ',', 0);\n+\n+\t\t\talign->position = ALIGN_LEFT;\n+\n+\t\t\twhile (*s) {\n+\t\t\t\t/*  Strip trailing comma */\n+\t\t\t\tif (s[1])\n+\t\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n+\t\t\t\tif (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n+\t\t\t\t\t;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"left\"))\n+\t\t\t\t\talign->position = ALIGN_LEFT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"right\"))\n+\t\t\t\t\talign->position = ALIGN_RIGHT;\n+\t\t\t\telse if (!strcmp(s[0]->buf, \"middle\"))\n+\t\t\t\t\talign->position = ALIGN_MIDDLE;\n+\t\t\t\telse\n+\t\t\t\t\tdie(_(\"improper format entered align:%s\"), s[0]->buf);\n+\t\t\t\ts++;\n+\t\t\t}\n+\n+\t\t\tif (width < 0)\n+\t\t\t\tdie(_(\"positive width expected with the %%(align) atom\"));\n+\t\t\talign->width = width;\n+\t\t\tstrbuf_list_free(to_free);\n+\t\t\tv->handler = align_atom_handler;\n+\t\t\tcontinue;\n+\t\t} else if (!strcmp(name, \"end\")) {\n+\t\t\tv->handler = end_atom_handler;\n+\t\t\tcontinue;\n \t\t} else\n \t\t\tcontinue;\n \n@@ -1347,6 +1453,8 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu\n \t\tresetv.s = color;\n \t\tappend_atom(&resetv, &state);\n \t}\n+\tif (state.stack->prev)\n+\t\tdie(_(\"format: %%(end) atom missing\"));\n \tfinal_buf = &state.stack->output;\n \tfwrite(final_buf->buf, 1, final_buf->len, stdout);\n \tpop_stack_element(&state.stack);\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex c4f0378..f596035 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -85,4 +85,86 @@ test_expect_success '%(color) must fail' '\n \ttest_must_fail git for-each-ref --format=\"%(color)%(refname)\"\n '\n \n+test_expect_success 'left alignment is default' '\n+\tcat >expect <<-\\EOF &&\n+\trefname is refs/heads/master  |refs/heads/master\n+\trefname is refs/heads/side    |refs/heads/side\n+\trefname is refs/odd/spot      |refs/odd/spot\n+\trefname is refs/tags/double-tag|refs/tags/double-tag\n+\trefname is refs/tags/four     |refs/tags/four\n+\trefname is refs/tags/one      |refs/tags/one\n+\trefname is refs/tags/signed-tag|refs/tags/signed-tag\n+\trefname is refs/tags/three    |refs/tags/three\n+\trefname is refs/tags/two      |refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"%(align:30)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'middle alignment' '\n+\tcat >expect <<-\\EOF &&\n+\t| refname is refs/heads/master |refs/heads/master\n+\t|  refname is refs/heads/side  |refs/heads/side\n+\t|   refname is refs/odd/spot   |refs/odd/spot\n+\t|refname is refs/tags/double-tag|refs/tags/double-tag\n+\t|  refname is refs/tags/four   |refs/tags/four\n+\t|   refname is refs/tags/one   |refs/tags/one\n+\t|refname is refs/tags/signed-tag|refs/tags/signed-tag\n+\t|  refname is refs/tags/three  |refs/tags/three\n+\t|   refname is refs/tags/two   |refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"|%(align:middle,30)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'right alignment' '\n+\tcat >expect <<-\\EOF &&\n+\t|  refname is refs/heads/master|refs/heads/master\n+\t|    refname is refs/heads/side|refs/heads/side\n+\t|      refname is refs/odd/spot|refs/odd/spot\n+\t|refname is refs/tags/double-tag|refs/tags/double-tag\n+\t|     refname is refs/tags/four|refs/tags/four\n+\t|      refname is refs/tags/one|refs/tags/one\n+\t|refname is refs/tags/signed-tag|refs/tags/signed-tag\n+\t|    refname is refs/tags/three|refs/tags/three\n+\t|      refname is refs/tags/two|refs/tags/two\n+\tEOF\n+\tgit for-each-ref --format=\"|%(align:30,right)refname is %(refname)%(end)|%(refname)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+# Individual atoms inside %(align:...) and %(end) must not be quoted.\n+\n+test_expect_success 'alignment with format quote' \"\n+\tcat >expect <<-\\EOF &&\n+\t|'      '\\''master| A U Thor'\\''      '|\n+\t|'       '\\''side| A U Thor'\\''       '|\n+\t|'     '\\''odd/spot| A U Thor'\\''     '|\n+\t|'        '\\''double-tag| '\\''        '|\n+\t|'       '\\''four| A U Thor'\\''       '|\n+\t|'       '\\''one| A U Thor'\\''        '|\n+\t|'        '\\''signed-tag| '\\''        '|\n+\t|'      '\\''three| A U Thor'\\''       '|\n+\t|'       '\\''two| A U Thor'\\''        '|\n+\tEOF\n+\tgit for-each-ref --shell --format=\\\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\\\" >actual &&\n+\ttest_cmp expect actual\n+\"\n+\n+test_expect_success 'nested alignment with quote formatting' \"\n+\tcat >expect <<-\\EOF &&\n+\t|'         master               '|\n+\t|'           side               '|\n+\t|'       odd/spot               '|\n+\t|'     double-tag               '|\n+\t|'           four               '|\n+\t|'            one               '|\n+\t|'     signed-tag               '|\n+\t|'          three               '|\n+\t|'            two               '|\n+\tEOF\n+\tgit for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&\n+\ttest_cmp expect actual\n+\"\n+\n test_done\n-- \n2.5.1\n"},{"id":"269763","messageId":"1441983856-540-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441900110-4015-9-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 08/14] ref-filter: add support for %(contents:lines=X)","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T15:04:16Z","receivedAt":"2015-09-11T15:04:16Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"In 'tag.c' we can print N lines from the annotation of the tag using\nthe '-n<num>' option. Copy code from 'tag.c' to 'ref-filter' and\nmodify it to support appending of N lines from the annotation of tags\nto the given strbuf.\n\nImplement %(contents:lines=X) where X lines of the given object are\nobtained.\n\nWhile we're at it, remove unused \"contents:<suboption>\" atoms from\nthe `valid_atom` array.\n\nAdd documentation and test for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-for-each-ref.txt |  3 ++-\n builtin/tag.c                      |  4 +++\n ref-filter.c                       | 47 +++++++++++++++++++++++++++++++---\n ref-filter.h                       |  3 ++-\n t/t6302-for-each-ref-filter.sh     | 52 ++++++++++++++++++++++++++++++++++++++\n 5 files changed, 103 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 3a271bf..324ad2c 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -150,7 +150,8 @@ The complete message in a commit and tag object is `contents`.\n Its first line is `contents:subject`, where subject is the concatenation\n of all lines of the commit message up to the first blank line.  The next\n line is 'contents:body', where body is all of the lines after the first\n-blank line.  Finally, the optional GPG signature is `contents:signature`.\n+blank line.  The optional GPG signature is `contents:signature`.  The\n+first `N` lines of the message is obtained using `contents:lines=N`.\n \n For sorting purposes, fields with numeric values sort in numeric\n order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 471d6b1..b0bc1c5 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -185,6 +185,10 @@ static enum contains_result contains(struct commit *candidate,\n \treturn contains_test(candidate, want);\n }\n \n+/*\n+ * Currently modified and used in ref-filter as append_lines(), will\n+ * eventually be removed as we port tag.c to use ref-filter APIs.\n+ */\n static void show_tag_lines(const struct object_id *oid, int lines)\n {\n \tint i;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex f046d82..32aab37 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -45,9 +45,6 @@ static struct {\n \t{ \"subject\" },\n \t{ \"body\" },\n \t{ \"contents\" },\n-\t{ \"contents:subject\" },\n-\t{ \"contents:body\" },\n-\t{ \"contents:signature\" },\n \t{ \"upstream\" },\n \t{ \"push\" },\n \t{ \"symref\" },\n@@ -65,6 +62,11 @@ struct align {\n \tunsigned int width;\n };\n \n+struct contents {\n+\tunsigned int lines;\n+\tstruct object_id oid;\n+};\n+\n struct ref_formatting_stack {\n \tstruct ref_formatting_stack *prev;\n \tstruct strbuf output;\n@@ -81,6 +83,7 @@ struct atom_value {\n \tconst char *s;\n \tunion {\n \t\tstruct align align;\n+\t\tstruct contents contents;\n \t} u;\n \tvoid (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);\n \tunsigned long ul; /* used for sorting when not FIELD_STR */\n@@ -643,6 +646,30 @@ static void find_subpos(const char *buf, unsigned long sz,\n \t*nonsiglen = *sig - buf;\n }\n \n+/*\n+ * If 'lines' is greater than 0, append that many lines from the given\n+ * 'buf' of length 'size' to the given strbuf.\n+ */\n+static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)\n+{\n+\tint i;\n+\tconst char *sp, *eol;\n+\tsize_t len;\n+\n+\tsp = buf;\n+\n+\tfor (i = 0; i < lines && sp < buf + size; i++) {\n+\t\tif (i)\n+\t\t\tstrbuf_addstr(out, \"\\n    \");\n+\t\teol = memchr(sp, '\\n', size - (sp - buf));\n+\t\tlen = eol ? eol - sp : size - (sp - buf);\n+\t\tstrbuf_add(out, sp, len);\n+\t\tif (!eol)\n+\t\t\tbreak;\n+\t\tsp = eol + 1;\n+\t}\n+}\n+\n /* See grab_values */\n static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)\n {\n@@ -653,6 +680,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj\n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i];\n \t\tstruct atom_value *v = &val[i];\n+\t\tconst char *valp = NULL;\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n@@ -662,7 +690,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj\n \t\t    strcmp(name, \"contents\") &&\n \t\t    strcmp(name, \"contents:subject\") &&\n \t\t    strcmp(name, \"contents:body\") &&\n-\t\t    strcmp(name, \"contents:signature\"))\n+\t\t    strcmp(name, \"contents:signature\") &&\n+\t\t    !starts_with(name, \"contents:lines=\"))\n \t\t\tcontinue;\n \t\tif (!subpos)\n \t\t\tfind_subpos(buf, sz,\n@@ -682,6 +711,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj\n \t\t\tv->s = xmemdupz(sigpos, siglen);\n \t\telse if (!strcmp(name, \"contents\"))\n \t\t\tv->s = xstrdup(subpos);\n+\t\telse if (skip_prefix(name, \"contents:lines=\", &valp)) {\n+\t\t\tstruct strbuf s = STRBUF_INIT;\n+\t\t\tconst char *contents_end = bodylen + bodypos - siglen;\n+\n+\t\t\tif (strtoul_ui(valp, 10, &v->u.contents.lines))\n+\t\t\t\tdie(_(\"positive value expected contents:lines=%s\"), valp);\n+\t\t\t/*  Size is the length of the message after removing the signature */\n+\t\t\tappend_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);\n+\t\t\tv->s = strbuf_detach(&s, NULL);\n+\t\t}\n \t}\n }\n \ndiff --git a/ref-filter.h b/ref-filter.h\nindex 0913ba9..ab76b22 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -59,7 +59,8 @@ struct ref_filter {\n \tstruct commit *merge_commit;\n \n \tunsigned int with_commit_tag_algo : 1;\n-\tunsigned int kind;\n+\tunsigned int kind,\n+\t\tlines;\n };\n \n struct ref_filter_cbdata {\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex f596035..bab1f28 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -167,4 +167,56 @@ test_expect_success 'nested alignment with quote formatting' \"\n \ttest_cmp expect actual\n \"\n \n+test_expect_success 'check `%(contents:lines=1)`' '\n+\tcat >expect <<-\\EOF &&\n+\tmaster |three\n+\tside |four\n+\todd/spot |three\n+\tdouble-tag |Annonated doubly\n+\tfour |four\n+\tone |one\n+\tsigned-tag |A signed tag message\n+\tthree |three\n+\ttwo |two\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname:short) |%(contents:lines=1)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check `%(contents:lines=0)`' '\n+\tcat >expect <<-\\EOF &&\n+\tmaster |\n+\tside |\n+\todd/spot |\n+\tdouble-tag |\n+\tfour |\n+\tone |\n+\tsigned-tag |\n+\tthree |\n+\ttwo |\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname:short) |%(contents:lines=0)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'check `%(contents:lines=99999)`' '\n+\tcat >expect <<-\\EOF &&\n+\tmaster |three\n+\tside |four\n+\todd/spot |three\n+\tdouble-tag |Annonated doubly\n+\tfour |four\n+\tone |one\n+\tsigned-tag |A signed tag message\n+\tthree |three\n+\ttwo |two\n+\tEOF\n+\tgit for-each-ref --format=\"%(refname:short) |%(contents:lines=99999)\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '`%(contents:lines=-1)` should fail' '\n+\ttest_must_fail git for-each-ref --format=\"%(refname:short) |%(contents:lines=-1)\"\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269764","messageId":"1441983976-680-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441902169-9891-1-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 12/14] tag.c: use 'ref-filter' APIs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T15:06:16Z","receivedAt":"2015-09-11T15:06:16Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nMake 'tag.c' use 'ref-filter' APIs for iterating through refs, sorting\nand printing of refs. This removes most of the code used in 'tag.c'\nreplacing it with calls to the 'ref-filter' library.\n\nMake 'tag.c' use the 'filter_refs()' function provided by 'ref-filter'\nto filter out tags based on the options set.\n\nFor printing tags we use 'show_ref_array_item()' function provided by\n'ref-filter'.\n\nWe improve the sorting option provided by 'tag.c' by using the sorting\noptions provided by 'ref-filter'. This causes the test 'invalid sort\nparameter on command line' in t7004 to fail, as 'ref-filter' throws an\nerror for all sorting fields which are incorrect. The test is changed\nto reflect the same.\n\nModify documentation for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-tag.txt |  16 ++-\n builtin/tag.c             | 345 ++++++----------------------------------------\n t/t7004-tag.sh            |   8 +-\n 3 files changed, 53 insertions(+), 316 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 84f6496..3ac4a96 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n-\t[--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]\n+\t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]\n 'git tag' -v <tagname>...\n \n DESCRIPTION\n@@ -94,14 +94,16 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n---sort=<type>::\n-\tSort in a specific order. Supported type is \"refname\"\n-\t(lexicographic order), \"version:refname\" or \"v:refname\" (tag\n+--sort=<key>::\n+\tSort based on the key given.  Prefix `-` to sort in\n+\tdescending order of the value. You may use the --sort=<key> option\n+\tmultiple times, in which case the last key becomes the primary\n+\tkey. Also supports \"version:refname\" or \"v:refname\" (tag\n \tnames are treated as versions). The \"version:refname\" sort\n \torder can also be affected by the\n-\t\"versionsort.prereleaseSuffix\" configuration variable. Prepend\n-\t\"-\" to reverse sort order. When this option is not given, the\n-\tsort order defaults to the value configured for the 'tag.sort'\n+\t\"versionsort.prereleaseSuffix\" configuration variable.\n+\tThe keys supported are the same as those in `git for-each-ref`.\n+\tSort order defaults to the value configured for the 'tag.sort'\n \tvariable if it exists, or lexicographic order otherwise. See\n \tlinkgit:git-config[1].\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex fe66f7b..977a18c 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -28,278 +28,35 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n-#define STRCMP_SORT     0\t/* must be zero */\n-#define VERCMP_SORT     1\n-#define SORT_MASK       0x7fff\n-#define REVERSE_SORT    0x8000\n-\n-static int tag_sort;\n-\n static unsigned int colopts;\n \n-static int match_pattern(const char **patterns, const char *ref)\n-{\n-\t/* no pattern means match everything */\n-\tif (!*patterns)\n-\t\treturn 1;\n-\tfor (; *patterns; patterns++)\n-\t\tif (!wildmatch(*patterns, ref, 0, NULL))\n-\t\t\treturn 1;\n-\treturn 0;\n-}\n-\n-/*\n- * This is currently duplicated in ref-filter.c, and will eventually be\n- * removed as we port tag.c to use the ref-filter APIs.\n- */\n-static const unsigned char *match_points_at(const char *refname,\n-\t\t\t\t\t    const unsigned char *sha1,\n-\t\t\t\t\t    struct sha1_array *points_at)\n-{\n-\tconst unsigned char *tagged_sha1 = NULL;\n-\tstruct object *obj;\n-\n-\tif (sha1_array_lookup(points_at, sha1) >= 0)\n-\t\treturn sha1;\n-\tobj = parse_object(sha1);\n-\tif (!obj)\n-\t\tdie(_(\"malformed object at '%s'\"), refname);\n-\tif (obj->type == OBJ_TAG)\n-\t\ttagged_sha1 = ((struct tag *)obj)->tagged->sha1;\n-\tif (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)\n-\t\treturn tagged_sha1;\n-\treturn NULL;\n-}\n-\n-static int in_commit_list(const struct commit_list *want, struct commit *c)\n-{\n-\tfor (; want; want = want->next)\n-\t\tif (!hashcmp(want->item->object.sha1, c->object.sha1))\n-\t\t\treturn 1;\n-\treturn 0;\n-}\n-\n-/*\n- * The entire code segment for supporting the --contains option has been\n- * copied over to ref-filter.{c,h}. This will be deleted evetually when\n- * we port tag.c to use ref-filter APIs.\n- */\n-enum contains_result {\n-\tCONTAINS_UNKNOWN = -1,\n-\tCONTAINS_NO = 0,\n-\tCONTAINS_YES = 1\n-};\n-\n-/*\n- * Test whether the candidate or one of its parents is contained in the list.\n- * Do not recurse to find out, though, but return -1 if inconclusive.\n- */\n-static enum contains_result contains_test(struct commit *candidate,\n-\t\t\t    const struct commit_list *want)\n-{\n-\t/* was it previously marked as containing a want commit? */\n-\tif (candidate->object.flags & TMP_MARK)\n-\t\treturn 1;\n-\t/* or marked as not possibly containing a want commit? */\n-\tif (candidate->object.flags & UNINTERESTING)\n-\t\treturn 0;\n-\t/* or are we it? */\n-\tif (in_commit_list(want, candidate)) {\n-\t\tcandidate->object.flags |= TMP_MARK;\n-\t\treturn 1;\n-\t}\n-\n-\tif (parse_commit(candidate) < 0)\n-\t\treturn 0;\n-\n-\treturn -1;\n-}\n-\n-/*\n- * Mimicking the real stack, this stack lives on the heap, avoiding stack\n- * overflows.\n- *\n- * At each recursion step, the stack items points to the commits whose\n- * ancestors are to be inspected.\n- */\n-struct stack {\n-\tint nr, alloc;\n-\tstruct stack_entry {\n-\t\tstruct commit *commit;\n-\t\tstruct commit_list *parents;\n-\t} *stack;\n-};\n-\n-static void push_to_stack(struct commit *candidate, struct stack *stack)\n-{\n-\tint index = stack->nr++;\n-\tALLOC_GROW(stack->stack, stack->nr, stack->alloc);\n-\tstack->stack[index].commit = candidate;\n-\tstack->stack[index].parents = candidate->parents;\n-}\n-\n-static enum contains_result contains(struct commit *candidate,\n-\t\tconst struct commit_list *want)\n-{\n-\tstruct stack stack = { 0, 0, NULL };\n-\tint result = contains_test(candidate, want);\n-\n-\tif (result != CONTAINS_UNKNOWN)\n-\t\treturn result;\n-\n-\tpush_to_stack(candidate, &stack);\n-\twhile (stack.nr) {\n-\t\tstruct stack_entry *entry = &stack.stack[stack.nr - 1];\n-\t\tstruct commit *commit = entry->commit;\n-\t\tstruct commit_list *parents = entry->parents;\n-\n-\t\tif (!parents) {\n-\t\t\tcommit->object.flags |= UNINTERESTING;\n-\t\t\tstack.nr--;\n-\t\t}\n-\t\t/*\n-\t\t * If we just popped the stack, parents->item has been marked,\n-\t\t * therefore contains_test will return a meaningful 0 or 1.\n-\t\t */\n-\t\telse switch (contains_test(parents->item, want)) {\n-\t\tcase CONTAINS_YES:\n-\t\t\tcommit->object.flags |= TMP_MARK;\n-\t\t\tstack.nr--;\n-\t\t\tbreak;\n-\t\tcase CONTAINS_NO:\n-\t\t\tentry->parents = parents->next;\n-\t\t\tbreak;\n-\t\tcase CONTAINS_UNKNOWN:\n-\t\t\tpush_to_stack(parents->item, &stack);\n-\t\t\tbreak;\n-\t\t}\n-\t}\n-\tfree(stack.stack);\n-\treturn contains_test(candidate, want);\n-}\n-\n-/*\n- * Currently modified and used in ref-filter as append_lines(), will\n- * eventually be removed as we port tag.c to use ref-filter APIs.\n- */\n-static void show_tag_lines(const struct object_id *oid, int lines)\n-{\n-\tint i;\n-\tunsigned long size;\n-\tenum object_type type;\n-\tchar *buf, *sp, *eol;\n-\tsize_t len;\n-\n-\tbuf = read_sha1_file(oid->hash, &type, &size);\n-\tif (!buf)\n-\t\tdie_errno(\"unable to read object %s\", oid_to_hex(oid));\n-\tif (type != OBJ_COMMIT && type != OBJ_TAG)\n-\t\tgoto free_return;\n-\tif (!size)\n-\t\tdie(\"an empty %s object %s?\",\n-\t\t    typename(type), oid_to_hex(oid));\n-\n-\t/* skip header */\n-\tsp = strstr(buf, \"\\n\\n\");\n-\tif (!sp)\n-\t\tgoto free_return;\n-\n-\t/* only take up to \"lines\" lines, and strip the signature from a tag */\n-\tif (type == OBJ_TAG)\n-\t\tsize = parse_signature(buf, size);\n-\tfor (i = 0, sp += 2; i < lines && sp < buf + size; i++) {\n-\t\tif (i)\n-\t\t\tprintf(\"\\n    \");\n-\t\teol = memchr(sp, '\\n', size - (sp - buf));\n-\t\tlen = eol ? eol - sp : size - (sp - buf);\n-\t\tfwrite(sp, len, 1, stdout);\n-\t\tif (!eol)\n-\t\t\tbreak;\n-\t\tsp = eol + 1;\n-\t}\n-free_return:\n-\tfree(buf);\n-}\n-\n-static void ref_array_append(struct ref_array *array, const char *refname)\n-{\n-\tsize_t len = strlen(refname);\n-\tstruct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);\n-\tmemcpy(ref->refname, refname, len);\n-\tref->refname[len] = '\\0';\n-\tREALLOC_ARRAY(array->items, array->nr + 1);\n-\tarray->items[array->nr++] = ref;\n-}\n-\n-static int show_reference(const char *refname, const struct object_id *oid,\n-\t\t\t  int flag, void *cb_data)\n-{\n-\tstruct ref_filter_cbdata *data = cb_data;\n-\tstruct ref_array *array = data->array;\n-\tstruct ref_filter *filter = data->filter;\n-\n-\tif (match_pattern(filter->name_patterns, refname)) {\n-\t\tif (filter->with_commit) {\n-\t\t\tstruct commit *commit;\n-\n-\t\t\tcommit = lookup_commit_reference_gently(oid->hash, 1);\n-\t\t\tif (!commit)\n-\t\t\t\treturn 0;\n-\t\t\tif (!contains(commit, filter->with_commit))\n-\t\t\t\treturn 0;\n-\t\t}\n-\n-\t\tif (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at))\n-\t\t\treturn 0;\n-\n-\t\tif (!filter->lines) {\n-\t\t\tif (tag_sort)\n-\t\t\t\tref_array_append(array, refname);\n-\t\t\telse\n-\t\t\t\tprintf(\"%s\\n\", refname);\n-\t\t\treturn 0;\n-\t\t}\n-\t\tprintf(\"%-15s \", refname);\n-\t\tshow_tag_lines(oid, filter->lines);\n-\t\tputchar('\\n');\n-\t}\n-\n-\treturn 0;\n-}\n-\n-static int sort_by_version(const void *a_, const void *b_)\n-{\n-\tconst struct ref_array_item *a = *((struct ref_array_item **)a_);\n-\tconst struct ref_array_item *b = *((struct ref_array_item **)b_);\n-\treturn versioncmp(a->refname, b->refname);\n-}\n-\n-static int list_tags(struct ref_filter *filter, int sort)\n+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n {\n \tstruct ref_array array;\n-\tstruct ref_filter_cbdata data;\n+\tchar *format, *to_free = NULL;\n+\tint i;\n \n \tmemset(&array, 0, sizeof(array));\n-\tdata.array = &array;\n-\tdata.filter = filter;\n \n \tif (filter->lines == -1)\n \t\tfilter->lines = 0;\n \n-\tfor_each_tag_ref(show_reference, &data);\n-\tif (sort) {\n-\t\tint i;\n-\t\tif ((sort & SORT_MASK) == VERCMP_SORT)\n-\t\t\tqsort(array.items, array.nr,\n-\t\t\t      sizeof(struct ref_array_item *), sort_by_version);\n-\t\tif (sort & REVERSE_SORT)\n-\t\t\tfor (i = array.nr - 1; i >= 0; i--)\n-\t\t\t\tprintf(\"%s\\n\", array.items[i]->refname);\n-\t\telse\n-\t\t\tfor (i = 0; i < array.nr; i++)\n-\t\t\t\tprintf(\"%s\\n\", array.items[i]->refname);\n-\t\tref_array_clear(&array);\n-\t}\n+\tif (filter->lines) {\n+\t\tto_free = xstrfmt(\"%s %%(contents:lines=%d)\",\n+\t\t\t\t \"%(align:15)%(refname:short)%(end)\", filter->lines);\n+\t\tformat = to_free;\n+\t} else\n+\t\tformat = \"%(refname:short)\";\n+\n+\tverify_ref_format(format);\n+\tfilter_refs(&array, filter, FILTER_REFS_TAGS);\n+\tref_array_sort(sorting, &array);\n+\n+\tfor (i = 0; i < array.nr; i++)\n+\t\tshow_ref_array_item(array.items[i], format, 0);\n+\tref_array_clear(&array);\n+\tfree(to_free);\n+\n \treturn 0;\n }\n \n@@ -366,35 +123,26 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-/*\n- * Parse a sort string, and return 0 if parsed successfully. Will return\n- * non-zero when the sort string does not parse into a known type. If var is\n- * given, the error message becomes a warning and includes information about\n- * the configuration value.\n- */\n-static int parse_sort_string(const char *var, const char *arg, int *sort)\n+/* Parse arg given and add it the ref_sorting array */\n+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)\n {\n-\tint type = 0, flags = 0;\n-\n-\tif (skip_prefix(arg, \"-\", &arg))\n-\t\tflags |= REVERSE_SORT;\n+\tstruct ref_sorting *s;\n+\tint len;\n \n-\tif (skip_prefix(arg, \"version:\", &arg) || skip_prefix(arg, \"v:\", &arg))\n-\t\ttype = VERCMP_SORT;\n-\telse\n-\t\ttype = STRCMP_SORT;\n+\ts = xcalloc(1, sizeof(*s));\n+\ts->next = *sorting_tail;\n+\t*sorting_tail = s;\n \n-\tif (strcmp(arg, \"refname\")) {\n-\t\tif (!var)\n-\t\t\treturn error(_(\"unsupported sort specification '%s'\"), arg);\n-\t\telse {\n-\t\t\twarning(_(\"unsupported sort specification '%s' in variable '%s'\"),\n-\t\t\t\tvar, arg);\n-\t\t\treturn -1;\n-\t\t}\n+\tif (*arg == '-') {\n+\t\ts->reverse = 1;\n+\t\targ++;\n \t}\n+\tif (skip_prefix(arg, \"version:\", &arg) ||\n+\t    skip_prefix(arg, \"v:\", &arg))\n+\t\ts->version = 1;\n \n-\t*sort = (type | flags);\n+\tlen = strlen(arg);\n+\ts->atom = parse_ref_filter_atom(arg, arg+len);\n \n \treturn 0;\n }\n@@ -402,11 +150,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)\n static int git_tag_config(const char *var, const char *value, void *cb)\n {\n \tint status;\n+\tstruct ref_sorting **sorting_tail = (struct ref_sorting **)cb;\n \n \tif (!strcmp(var, \"tag.sort\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n-\t\tparse_sort_string(var, value, &tag_sort);\n+\t\tparse_sorting_string(value, sorting_tail);\n \t\treturn 0;\n \t}\n \n@@ -564,13 +313,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)\n \treturn check_refname_format(sb->buf, 0);\n }\n \n-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)\n-{\n-\tint *sort = opt->value;\n-\n-\treturn parse_sort_string(NULL, arg, sort);\n-}\n-\n int cmd_tag(int argc, const char **argv, const char *prefix)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -587,6 +329,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_filter filter;\n+\tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n@@ -613,10 +356,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n \t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n \t\tOPT_WITH(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n-\t\t{\n-\t\t\tOPTION_CALLBACK, 0, \"sort\", &tag_sort, N_(\"type\"), N_(\"sort tags\"),\n-\t\t\tPARSE_OPT_NONEG, parse_opt_sort\n-\t\t},\n+\t\tOPT_CALLBACK(0 , \"sort\", sorting_tail, N_(\"key\"),\n+\t\t\t     N_(\"field name to sort on\"), &parse_opt_ref_sorting),\n \t\t{\n \t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n@@ -624,7 +365,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n-\tgit_config(git_tag_config, NULL);\n+\tgit_config(git_tag_config, sorting_tail);\n \n \tmemset(&opt, 0, sizeof(opt));\n \tmemset(&filter, 0, sizeof(filter));\n@@ -650,6 +391,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"--column and -n are incompatible\"));\n \t\tcolopts = 0;\n \t}\n+\tif (!sorting)\n+\t\tsorting = ref_default_sorting();\n \tif (cmdmode == 'l') {\n \t\tint ret;\n \t\tif (column_active(colopts)) {\n@@ -658,10 +401,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tcopts.padding = 2;\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n-\t\tif (filter.lines != -1 && tag_sort)\n-\t\t\tdie(_(\"--sort and -n are incompatible\"));\n \t\tfilter.name_patterns = argv;\n-\t\tret = list_tags(&filter, tag_sort);\n+\t\tret = list_tags(&filter, sorting);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex d31788c..84153ef 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '\n \n test_expect_success 'invalid sort parameter in configuratoin' '\n \tgit config tag.sort \"v:notvalid\" &&\n-\tgit tag -l \"foo*\" >actual &&\n-\tcat >expect <<-\\EOF &&\n-\tfoo1.10\n-\tfoo1.3\n-\tfoo1.6\n-\tEOF\n-\ttest_cmp expect actual\n+\ttest_must_fail git tag -l \"foo*\"\n '\n \n test_expect_success 'version sort with prerelease reordering' '\n-- \n2.5.1\n"},{"id":"269765","messageId":"1441984006-792-1-git-send-email-Karthik.188@gmail.com","threadId":"40316","inReplyTo":"1441902169-9891-2-git-send-email-Karthik.188@gmail.com","subject":"[PATCH v17 13/14] tag.c: implement '--format' option","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T15:06:46Z","receivedAt":"2015-09-11T15:06:46Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Implement the '--format' option provided by 'ref-filter'.\nThis lets the user list tags as per desired format similar\nto the implementation in 'git for-each-ref'.\n\nAdd tests and documentation for the same.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-tag.txt |  8 +++++++-\n builtin/tag.c             | 24 ++++++++++++++----------\n t/t7004-tag.sh            | 12 ++++++++++++\n 3 files changed, 33 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 3ac4a96..0c7f4e6 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -13,7 +13,8 @@ SYNOPSIS\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n-\t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]\n+\t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]\n+\t[--format=<format>] [<pattern>...]\n 'git tag' -v <tagname>...\n \n DESCRIPTION\n@@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.\n \tThe object that the new tag will refer to, usually a commit.\n \tDefaults to HEAD.\n \n+<format>::\n+\tA string that interpolates `%(fieldname)` from the object\n+\tpointed at by a ref being shown.  The format is the same as\n+\tthat of linkgit:git-for-each-ref[1].  When unspecified,\n+\tdefaults to `%(refname:short)`.\n \n CONFIGURATION\n -------------\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 977a18c..91c17b7 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -23,17 +23,17 @@ static const char * const git_tag_usage[] = {\n \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n \tN_(\"git tag -d <tagname>...\"),\n \tN_(\"git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]\"\n-\t\t\"\\n\\t\\t[<pattern>...]\"),\n+\t\t\"\\n\\t\\t[--format=<format>] [<pattern>...]\"),\n \tN_(\"git tag -v <tagname>...\"),\n \tNULL\n };\n \n static unsigned int colopts;\n \n-static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)\n {\n \tstruct ref_array array;\n-\tchar *format, *to_free = NULL;\n+\tchar *to_free = NULL;\n \tint i;\n \n \tmemset(&array, 0, sizeof(array));\n@@ -41,12 +41,14 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)\n \tif (filter->lines == -1)\n \t\tfilter->lines = 0;\n \n-\tif (filter->lines) {\n-\t\tto_free = xstrfmt(\"%s %%(contents:lines=%d)\",\n-\t\t\t\t \"%(align:15)%(refname:short)%(end)\", filter->lines);\n-\t\tformat = to_free;\n-\t} else\n-\t\tformat = \"%(refname:short)\";\n+\tif (!format) {\n+\t\tif (filter->lines) {\n+\t\t\tto_free = xstrfmt(\"%s %%(contents:lines=%d)\",\n+\t\t\t\t\t  \"%(align:15)%(refname:short)%(end)\", filter->lines);\n+\t\t\tformat = to_free;\n+\t\t} else\n+\t\t\tformat = \"%(refname:short)\";\n+\t}\n \n \tverify_ref_format(format);\n \tfilter_refs(&array, filter, FILTER_REFS_TAGS);\n@@ -330,6 +332,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct ref_filter filter;\n \tstatic struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;\n+\tconst char *format = NULL;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list tag names\"), 'l'),\n \t\t{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_(\"n\"),\n@@ -362,6 +365,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\tOPTION_CALLBACK, 0, \"points-at\", &filter.points_at, N_(\"object\"),\n \t\t\tN_(\"print only tags of the object\"), 0, parse_opt_object_name\n \t\t},\n+\t\tOPT_STRING(  0 , \"format\", &format, N_(\"format\"), N_(\"format to use for the output\")),\n \t\tOPT_END()\n \t};\n \n@@ -402,7 +406,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\t\trun_column_filter(colopts, &copts);\n \t\t}\n \t\tfilter.name_patterns = argv;\n-\t\tret = list_tags(&filter, sorting);\n+\t\tret = list_tags(&filter, sorting, format);\n \t\tif (column_active(colopts))\n \t\t\tstop_column_filter();\n \t\treturn ret;\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 84153ef..8987fb1 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -1519,4 +1519,16 @@ EOF\"\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--format should list tags as per format given' '\n+\tcat >expect <<-\\EOF &&\n+\trefname : refs/tags/foo1.10\n+\trefname : refs/tags/foo1.3\n+\trefname : refs/tags/foo1.6\n+\trefname : refs/tags/foo1.6-rc1\n+\trefname : refs/tags/foo1.6-rc2\n+\tEOF\n+\tgit tag -l --format=\"refname : %(refname)\" \"foo*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.5.1\n"},{"id":"269766","messageId":"CAOLa=ZQppSg0-kc5nCfRYfHMoD5ehTTOigKz48G01vn1Mn=FTw@mail.gmail.com","threadId":"40316","inReplyTo":"vpqr3m6nrf9.fsf@scolette.imag.fr","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T15:08:16Z","receivedAt":"2015-09-11T15:08:16Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Thu, Sep 10, 2015 at 10:27 PM, Matthieu Moy\n<Matthieu.Moy@grenoble-inp.fr> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> This is part of the series of unifying the code used by\n>> \"git tag -l, git branch -l, git for-each-ref\".\n>>\n>> The previous version can be found here (version 16):\n>> article.gmane.org/gmane.comp.version-control.git/277394\n>>\n>> Changes in this version:\n>> * The arguments of the %(align) atom are interchangeable.\n>> * Small grammatical changes.\n>> * Small changes in the tests to reflect changes in the align\n>> atom code.\n>\n> Clearly, we're almost there. I did a few minor remarks. I suggest\n> (admitedly, Eric suggested of-list to suggest ;-) ) that you reply to\n> them by re-sending only individual patches that changed (replying to the\n> original patch) so that we can check the new patches individually. I\n> think we can do the finishing touches for each patch in a subthread of\n> this patch.\n>\n\nI replied with suggested changes by you and Junio.\nLet me know if any other changes to be made :)\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269784","messageId":"CAPig+cTmnOz899k_xySb1Q-SeFctOrs6fnJJN=qc731nXmXwkw@mail.gmail.com","threadId":"40316","inReplyTo":"CAOLa=ZQppSg0-kc5nCfRYfHMoD5ehTTOigKz48G01vn1Mn=FTw@mail.gmail.com","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2015-09-11T17:30:07Z","receivedAt":"2015-09-11T17:30:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Sep 11, 2015 at 11:08 AM, Karthik Nayak <karthik.188@gmail.com> wrote:\n> On Thu, Sep 10, 2015 at 10:27 PM, Matthieu Moy\n> <Matthieu.Moy@grenoble-inp.fr> wrote:\n>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>> Changes in this version:\n>>> * The arguments of the %(align) atom are interchangeable.\n>>> * Small grammatical changes.\n>>> * Small changes in the tests to reflect changes in the align\n>>> atom code.\n>>\n>> Clearly, we're almost there. I did a few minor remarks. I suggest\n>> (admitedly, Eric suggested of-list to suggest ;-) ) that you reply to\n>> them by re-sending only individual patches that changed (replying to the\n>> original patch) so that we can check the new patches individually. I\n>> think we can do the finishing touches for each patch in a subthread of\n>> this patch.\n>\n> I replied with suggested changes by you and Junio.\n> Let me know if any other changes to be made :)\n\nHmm, but what actually changed in the re-sent patches? Without a link\nto the discussion leading up to the re-send of changed-only patches,\nand without an interdiff, the re-send is opaque and less accessible to\nthe reviewer; which is at odds with Matthieu's suggestion which was\nintended to make review easier and more streamlined.\n\nIn addition to a link to the previous round and an interdiff, it would\nbe helpful to reviewers for you to annotate each patch (in the\ncommentary are below the \"---\" line after your sign-off) with a\ndescription of the changes in that patch since the previous round in\norder to focus the reviewer's attention (where it needs to be) on the\nlatest changes.\n"},{"id":"269787","messageId":"xmqqwpvwzvaa.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"CAPig+cTmnOz899k_xySb1Q-SeFctOrs6fnJJN=qc731nXmXwkw@mail.gmail.com","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-11T18:05:17Z","receivedAt":"2015-09-11T18:05:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> In addition to a link to the previous round and an interdiff, it would\n> be helpful to reviewers for you to annotate each patch (in the\n> commentary are below the \"---\" line after your sign-off) with a\n> description of the changes in that patch since the previous round in\n> order to focus the reviewer's attention (where it needs to be) on the\n> latest changes.\n\nI may have got confused by seeing the same v17 (if they were marked\nas v18 or v17bis, it would have been easier to make sure I didn't\nmiss anything), but here is the difference between what I had last\nnight and what I queued.  The removal of !body[1] and flipping the\norder of to_free/format are not seen because I already had a local\nfix-up SQUASH??? commits queued in the yesterday's batch.\n\n\n$ git diff --stat -p kn/for-each-tag@{4.hours} kn/for-each-tag\n ref-filter.c | 5 +----\n 1 file changed, 1 insertion(+), 4 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 226e94d..fd839ac 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -47,9 +47,6 @@ static struct {\n \t{ \"subject\" },\n \t{ \"body\" },\n \t{ \"contents\" },\n-\t{ \"contents:subject\" },\n-\t{ \"contents:body\" },\n-\t{ \"contents:signature\" },\n \t{ \"upstream\" },\n \t{ \"push\" },\n \t{ \"symref\" },\n@@ -58,7 +55,6 @@ static struct {\n \t{ \"color\" },\n \t{ \"align\" },\n \t{ \"end\" },\n-\t{ \"contents:lines\" },\n };\n \n #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n@@ -899,6 +895,7 @@ static void populate_value(struct ref_array_item *ref)\n \t\t\talign->position = ALIGN_LEFT;\n \n \t\t\twhile (*s) {\n+\t\t\t\t/*  Strip trailing comma */\n \t\t\t\tif (s[1])\n \t\t\t\t\tstrbuf_setlen(s[0], s[0]->len - 1);\n \t\t\t\tif (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n"},{"id":"269788","messageId":"CAOLa=ZTZiFzLogP=OXi_cS3Dn4775bU5LqBUWLu8Sf5ocs6T=g@mail.gmail.com","threadId":"40316","inReplyTo":"xmqqwpvwzvaa.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-11T18:12:56Z","receivedAt":"2015-09-11T18:12:56Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Fri, Sep 11, 2015 at 11:00 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> Hmm, but what actually changed in the re-sent patches? Without a link\n> to the discussion leading up to the re-send of changed-only patches,\n> and without an interdiff, the re-send is opaque and less accessible to\n> the reviewer; which is at odds with Matthieu's suggestion which was\n> intended to make review easier and more streamlined.\n>\n\nShould have put an interdiff, my bad!\n\n> In addition to a link to the previous round and an interdiff, it would\n> be helpful to reviewers for you to annotate each patch (in the\n> commentary are below the \"---\" line after your sign-off) with a\n> description of the changes in that patch since the previous round in\n> order to focus the reviewer's attention (where it needs to be) on the\n> latest changes.\n\nWIll follow this next time :)\n\nOn Fri, Sep 11, 2015 at 11:35 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n>\n>> In addition to a link to the previous round and an interdiff, it would\n>> be helpful to reviewers for you to annotate each patch (in the\n>> commentary are below the \"---\" line after your sign-off) with a\n>> description of the changes in that patch since the previous round in\n>> order to focus the reviewer's attention (where it needs to be) on the\n>> latest changes.\n>\n> I may have got confused by seeing the same v17 (if they were marked\n> as v18 or v17bis, it would have been easier to make sure I didn't\n> miss anything), but here is the difference between what I had last\n> night and what I queued.  The removal of !body[1] and flipping the\n> order of to_free/format are not seen because I already had a local\n> fix-up SQUASH??? commits queued in the yesterday's batch.\n>\n>\n> $ git diff --stat -p kn/for-each-tag@{4.hours} kn/for-each-tag\n>  ref-filter.c | 5 +----\n>  1 file changed, 1 insertion(+), 4 deletions(-)\n>\n> diff --git a/ref-filter.c b/ref-filter.c\n> index 226e94d..fd839ac 100644\n> --- a/ref-filter.c\n> +++ b/ref-filter.c\n> @@ -47,9 +47,6 @@ static struct {\n>         { \"subject\" },\n>         { \"body\" },\n>         { \"contents\" },\n> -       { \"contents:subject\" },\n> -       { \"contents:body\" },\n> -       { \"contents:signature\" },\n>         { \"upstream\" },\n>         { \"push\" },\n>         { \"symref\" },\n> @@ -58,7 +55,6 @@ static struct {\n>         { \"color\" },\n>         { \"align\" },\n>         { \"end\" },\n> -       { \"contents:lines\" },\n>  };\n>\n>  #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n> @@ -899,6 +895,7 @@ static void populate_value(struct ref_array_item *ref)\n>                         align->position = ALIGN_LEFT;\n>\n>                         while (*s) {\n> +                               /*  Strip trailing comma */\n>                                 if (s[1])\n>                                         strbuf_setlen(s[0], s[0]->len - 1);\n>                                 if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n\nThe complete interdiff:\n\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 081fe84..f2f6e2d 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -43,9 +43,9 @@ static int list_tags(struct ref_filter *filter,\nstruct ref_sorting *sorting, con\n\n     if (!format) {\n         if (filter->lines) {\n-            format = xstrfmt(\"%s %%(contents:lines=%d)\",\n-                     \"%(align:15)%%(refname:short)%%(end)\", filter->lines);\n-            to_free = format;\n+            to_free = xstrfmt(\"%s %%(contents:lines=%d)\",\n+                      \"%(align:15)%(refname:short)%(end)\", filter->lines);\n+            format = to_free;\n         } else\n             format = \"%(refname:short)\";\n     }\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 59716db..fd839ac 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -47,9 +47,6 @@ static struct {\n     { \"subject\" },\n     { \"body\" },\n     { \"contents\" },\n-    { \"contents:subject\" },\n-    { \"contents:body\" },\n-    { \"contents:signature\" },\n     { \"upstream\" },\n     { \"push\" },\n     { \"symref\" },\n@@ -58,7 +55,6 @@ static struct {\n     { \"color\" },\n     { \"align\" },\n     { \"end\" },\n-    { \"contents:lines\" },\n };\n\n #define REF_FORMATTING_STATE_INIT  { 0, NULL }\n@@ -268,7 +264,7 @@ static int match_atom_name(const char *name, const\nchar *atom_name, const char *\n\n     if (!skip_prefix(name, atom_name, &body))\n         return 0; /* doesn't even begin with \"atom_name\" */\n-    if (!body[0] || !body[1]) {\n+    if (!body[0]) {\n         *val = NULL; /* %(atom_name) and no customization */\n         return 1;\n     }\n@@ -899,6 +895,7 @@ static void populate_value(struct ref_array_item *ref)\n             align->position = ALIGN_LEFT;\n\n             while (*s) {\n+                /*  Strip trailing comma */\n                 if (s[1])\n                     strbuf_setlen(s[0], s[0]->len - 1);\n                 if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))\n\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"269817","messageId":"vpq7fnwvw2c.fsf@anie.imag.fr","threadId":"40316","inReplyTo":"CAOLa=ZQppSg0-kc5nCfRYfHMoD5ehTTOigKz48G01vn1Mn=FTw@mail.gmail.com","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-12T09:14:19Z","receivedAt":"2015-09-12T09:14:19Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> On Thu, Sep 10, 2015 at 10:27 PM, Matthieu Moy\n> <Matthieu.Moy@grenoble-inp.fr> wrote:\n>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>\n>>> This is part of the series of unifying the code used by\n>>> \"git tag -l, git branch -l, git for-each-ref\".\n>>>\n>>> The previous version can be found here (version 16):\n>>> article.gmane.org/gmane.comp.version-control.git/277394\n>>>\n>>> Changes in this version:\n>>> * The arguments of the %(align) atom are interchangeable.\n>>> * Small grammatical changes.\n>>> * Small changes in the tests to reflect changes in the align\n>>> atom code.\n>>\n>> Clearly, we're almost there. I did a few minor remarks. I suggest\n>> (admitedly, Eric suggested of-list to suggest ;-) ) that you reply to\n>> them by re-sending only individual patches that changed (replying to the\n>> original patch) so that we can check the new patches individually. I\n>> think we can do the finishing touches for each patch in a subthread of\n>> this patch.\n>>\n>\n> I replied with suggested changes by you and Junio.\n> Let me know if any other changes to be made :)\n\nI went through the patches you resent, it all looks good to me.\n\nIf I read correctly, Junio already applied it to pu.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"269857","messageId":"CAOLa=ZRKuuBo9v7qB3i4Y5dV18mg9OUJtTLXTMiBHEtk-vdTmg@mail.gmail.com","threadId":"40316","inReplyTo":"vpq7fnwvw2c.fsf@anie.imag.fr","subject":"Re: [PATCH v17 00/14] port tag.c to use ref-filter APIs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-13T04:54:24Z","receivedAt":"2015-09-13T04:54:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Sat, Sep 12, 2015 at 2:44 PM, Matthieu Moy\n<Matthieu.Moy@grenoble-inp.fr> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> On Thu, Sep 10, 2015 at 10:27 PM, Matthieu Moy\n>> <Matthieu.Moy@grenoble-inp.fr> wrote:\n>>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>>\n>>>> This is part of the series of unifying the code used by\n>>>> \"git tag -l, git branch -l, git for-each-ref\".\n>>>>\n>>>> The previous version can be found here (version 16):\n>>>> article.gmane.org/gmane.comp.version-control.git/277394\n>>>>\n>>>> Changes in this version:\n>>>> * The arguments of the %(align) atom are interchangeable.\n>>>> * Small grammatical changes.\n>>>> * Small changes in the tests to reflect changes in the align\n>>>> atom code.\n>>>\n>>> Clearly, we're almost there. I did a few minor remarks. I suggest\n>>> (admitedly, Eric suggested of-list to suggest ;-) ) that you reply to\n>>> them by re-sending only individual patches that changed (replying to the\n>>> original patch) so that we can check the new patches individually. I\n>>> think we can do the finishing touches for each patch in a subthread of\n>>> this patch.\n>>>\n>>\n>> I replied with suggested changes by you and Junio.\n>> Let me know if any other changes to be made :)\n>\n> I went through the patches you resent, it all looks good to me.\n>\n> If I read correctly, Junio already applied it to pu.\n\nI'll push the next set of patches then.\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"270266","messageId":"20150917213619.GI17201@serenity.lan","threadId":"40316","inReplyTo":"1441902169-9891-3-git-send-email-Karthik.188@gmail.com","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"John Keeping","fromEmail":"john@keeping.me.uk","sentAt":"2015-09-17T21:36:19Z","receivedAt":"2015-09-17T21:36:19Z","isPatch":true,"sender":{"key":"john@keeping.me.uk","avatar":"https://avatars.githubusercontent.com/u/1702081?v=4"},"body":"On Thu, Sep 10, 2015 at 09:52:49PM +0530, Karthik Nayak wrote:\n> Use 'ref-filter' APIs to implement the '--merged' and '--no-merged'\n> options into 'tag.c'. The '--merged' option lets the user to only list\n> tags merged into the named commit. The '--no-merged' option lets the\n> user to only list tags not merged into the named commit.  If no object\n> is provided it assumes HEAD as the object.\n> \n> Add documentation and tests for the same.\n> \n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  Documentation/git-tag.txt |  7 ++++++-\n>  builtin/tag.c             |  6 +++++-\n>  t/t7004-tag.sh            | 27 +++++++++++++++++++++++++++\n>  3 files changed, 38 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\n> index 0c7f4e6..3803bf7 100644\n> --- a/Documentation/git-tag.txt\n> +++ b/Documentation/git-tag.txt\n> @@ -14,7 +14,7 @@ SYNOPSIS\n>  'git tag' -d <tagname>...\n>  'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n>  \t[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]\n> -\t[--format=<format>] [<pattern>...]\n> +\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]\n>  'git tag' -v <tagname>...\n>  \n>  DESCRIPTION\n> @@ -165,6 +165,11 @@ This option is only applicable when listing tags without annotation lines.\n>  \tthat of linkgit:git-for-each-ref[1].  When unspecified,\n>  \tdefaults to `%(refname:short)`.\n>  \n> +--[no-]merged [<commit>]::\n\nWe prefer to write --[no-]* as:\n\n\t--option::\n\t--no-option::\n\nalthough this may be the first instance that we see this combination\nwith an argument.\n\nI also found the \"[<commit>]\" syntax confusing and had to go and figure\nout what PARSE_OPT_LASTARG_DEFAULT does; I wonder if it's worth\nappending something like the following to the help for this option:\n\n\tThe `commit` may be omitted if this is the final argument.\n\n> +\tOnly list tags whose tips are reachable, or not reachable\n> +\tif '--no-merged' is used, from the specified commit ('HEAD'\n> +\tif not specified).\n> +\n>  CONFIGURATION\n>  -------------\n>  By default, 'git tag' in sign-with-default mode (-s) will use your\n> diff --git a/builtin/tag.c b/builtin/tag.c\n> index 075d90b..081fe84 100644\n> --- a/builtin/tag.c\n> +++ b/builtin/tag.c\n> @@ -23,7 +23,7 @@ static const char * const git_tag_usage[] = {\n>  \tN_(\"git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]\"),\n>  \tN_(\"git tag -d <tagname>...\"),\n>  \tN_(\"git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]\"\n> -\t\t\"\\n\\t\\t[--format=<format>] [<pattern>...]\"),\n> +\t\t\"\\n\\t\\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]\"),\n>  \tN_(\"git tag -v <tagname>...\"),\n>  \tNULL\n>  };\n> @@ -359,6 +359,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_COLUMN(0, \"column\", &colopts, N_(\"show tag list in columns\")),\n>  \t\tOPT_CONTAINS(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n>  \t\tOPT_WITH(&filter.with_commit, N_(\"print only tags that contain the commit\")),\n> +\t\tOPT_MERGED(&filter, N_(\"print only tags that are merged\")),\n> +\t\tOPT_NO_MERGED(&filter, N_(\"print only tags that are not merged\")),\n>  \t\tOPT_CALLBACK(0 , \"sort\", sorting_tail, N_(\"key\"),\n>  \t\t\t     N_(\"field name to sort on\"), &parse_opt_ref_sorting),\n>  \t\t{\n> @@ -417,6 +419,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>  \t\tdie(_(\"--contains option is only allowed with -l.\"));\n>  \tif (filter.points_at.nr)\n>  \t\tdie(_(\"--points-at option is only allowed with -l.\"));\n> +\tif (filter.merge_commit)\n> +\t\tdie(_(\"--merged and --no-merged option are only allowed with -l\"));\n>  \tif (cmdmode == 'd')\n>  \t\treturn for_each_tag_name(argv, delete_tag);\n>  \tif (cmdmode == 'v')\n> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\n> index 8987fb1..3dd2f51 100755\n> --- a/t/t7004-tag.sh\n> +++ b/t/t7004-tag.sh\n> @@ -1531,4 +1531,31 @@ test_expect_success '--format should list tags as per format given' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'setup --merged test tags' '\n> +\tgit tag mergetest-1 HEAD~2 &&\n> +\tgit tag mergetest-2 HEAD~1 &&\n> +\tgit tag mergetest-3 HEAD\n> +'\n> +\n> +test_expect_success '--merged cannot be used in non-list mode' '\n> +\ttest_must_fail git tag --merged=mergetest-2 foo\n> +'\n> +\n> +test_expect_success '--merged shows merged tags' '\n> +\tcat >expect <<-\\EOF &&\n> +\tmergetest-1\n> +\tmergetest-2\n> +\tEOF\n> +\tgit tag -l --merged=mergetest-2 mergetest-* >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success '--no-merged show unmerged tags' '\n> +\tcat >expect <<-\\EOF &&\n> +\tmergetest-3\n> +\tEOF\n> +\tgit tag -l --no-merged=mergetest-2 mergetest-* >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_done\n> -- \n> 2.5.1\n> \n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n"},{"id":"270269","messageId":"xmqq37ycitps.fsf@gitster.mtv.corp.google.com","threadId":"40316","inReplyTo":"20150917213619.GI17201@serenity.lan","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-09-17T22:09:19Z","receivedAt":"2015-09-17T22:09:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"John Keeping <john@keeping.me.uk> writes:\n\n>> +--[no-]merged [<commit>]::\n>\n> We prefer to write --[no-]* as:\n>\n> \t--option::\n> \t--no-option::\n>\n> although this may be the first instance that we see this combination\n> with an argument.\n>\n> I also found the \"[<commit>]\" syntax confusing and had to go and figure\n> out what PARSE_OPT_LASTARG_DEFAULT does; I wonder if it's worth\n> appending something like the following to the help for this option:\n>\n> \tThe `commit` may be omitted if this is the final argument.\n\n\"may be omitted\" must be followed by a description of what happens\nwhen omitted (i.e. \"defaults to ...\").\n"},{"id":"270276","messageId":"vpqoah0kxtb.fsf@anie.imag.fr","threadId":"40316","inReplyTo":"xmqq37ycitps.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-18T07:10:08Z","receivedAt":"2015-09-18T07:10:08Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> John Keeping <john@keeping.me.uk> writes:\n>\n>>> +--[no-]merged [<commit>]::\n>>\n>> We prefer to write --[no-]* as:\n>>\n>> \t--option::\n>> \t--no-option::\n>>\n>> although this may be the first instance that we see this combination\n>> with an argument.\n>>\n>> I also found the \"[<commit>]\" syntax confusing and had to go and figure\n>> out what PARSE_OPT_LASTARG_DEFAULT does; I wonder if it's worth\n>> appending something like the following to the help for this option:\n>>\n>> \tThe `commit` may be omitted if this is the final argument.\n>\n> \"may be omitted\" must be followed by a description of what happens\n> when omitted (i.e. \"defaults to ...\").\n\nThen:\n\nThe `commit` may be omitted and defaults to HEAD if this is the final\nargument.\n\nSounds good to me.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"270280","messageId":"20150918084208.GJ17201@serenity.lan","threadId":"40316","inReplyTo":"vpqoah0kxtb.fsf@anie.imag.fr","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"John Keeping","fromEmail":"john@keeping.me.uk","sentAt":"2015-09-18T08:42:08Z","receivedAt":"2015-09-18T08:42:08Z","isPatch":true,"sender":{"key":"john@keeping.me.uk","avatar":"https://avatars.githubusercontent.com/u/1702081?v=4"},"body":"On Fri, Sep 18, 2015 at 09:10:08AM +0200, Matthieu Moy wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > John Keeping <john@keeping.me.uk> writes:\n> >\n> >>> +--[no-]merged [<commit>]::\n> >>\n> >> We prefer to write --[no-]* as:\n> >>\n> >> \t--option::\n> >> \t--no-option::\n> >>\n> >> although this may be the first instance that we see this combination\n> >> with an argument.\n> >>\n> >> I also found the \"[<commit>]\" syntax confusing and had to go and figure\n> >> out what PARSE_OPT_LASTARG_DEFAULT does; I wonder if it's worth\n> >> appending something like the following to the help for this option:\n> >>\n> >> \tThe `commit` may be omitted if this is the final argument.\n> >\n> > \"may be omitted\" must be followed by a description of what happens\n> > when omitted (i.e. \"defaults to ...\").\n> \n> Then:\n> \n> The `commit` may be omitted and defaults to HEAD if this is the final\n> argument.\n\nI find that slightly confusing, although that might just be me.  It's\nslightly longer, but I would write:\n\n\tThe `commit` may be omitted if this is the final argument, in\n\twhich case it defaults to `HEAD`.\n\nI also had a look at git-branch(1), which has similar `--merged` and\n`--no-merged` options and says:\n\n\tOnly list branches whose tips are reachable from the specified\n\tcommit (HEAD if not specified).  Implies `--list`.\n\nThe two options are listed separately in that case.\n"},{"id":"270283","messageId":"vpqeghwks3a.fsf@anie.imag.fr","threadId":"40316","inReplyTo":"20150918084208.GJ17201@serenity.lan","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-18T09:13:45Z","receivedAt":"2015-09-18T09:13:45Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"John Keeping <john@keeping.me.uk> writes:\n\n> On Fri, Sep 18, 2015 at 09:10:08AM +0200, Matthieu Moy wrote:\n>\n>> The `commit` may be omitted and defaults to HEAD if this is the final\n>> argument.\n>\n> I find that slightly confusing, although that might just be me.  It's\n> slightly longer, but I would write:\n>\n> \tThe `commit` may be omitted if this is the final argument, in\n> \twhich case it defaults to `HEAD`.\n\nProbably clearer indeed.\n\n> I also had a look at git-branch(1), which has similar `--merged` and\n> `--no-merged` options and says:\n>\n> \tOnly list branches whose tips are reachable from the specified\n> \tcommit (HEAD if not specified).  Implies `--list`.\n\nI think this lacks the \"may be omitted if this is the final argument\",\nwhich is actually the most confusing (see the other thread \"git status\n-u is mildly astonishing\" for an example of similar confusion).\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"270293","messageId":"CAOLa=ZQmGD2vo1B2K21-f_2RZRXduFt4VNKq2Pp9B_5bO=8=+w@mail.gmail.com","threadId":"40316","inReplyTo":"20150918084208.GJ17201@serenity.lan","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-18T15:10:35Z","receivedAt":"2015-09-18T15:10:35Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Fri, Sep 18, 2015 at 2:12 PM, John Keeping <john@keeping.me.uk> wrote:\n> On Fri, Sep 18, 2015 at 09:10:08AM +0200, Matthieu Moy wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>>\n>> > John Keeping <john@keeping.me.uk> writes:\n>> >\n>> >>> +--[no-]merged [<commit>]::\n>> >>\n>> >> We prefer to write --[no-]* as:\n>> >>\n>> >>    --option::\n>> >>    --no-option::\n>> >>\n>> >> although this may be the first instance that we see this combination\n>> >> with an argument.\n>> >>\n>> >> I also found the \"[<commit>]\" syntax confusing and had to go and figure\n>> >> out what PARSE_OPT_LASTARG_DEFAULT does; I wonder if it's worth\n>> >> appending something like the following to the help for this option:\n>> >>\n>> >>    The `commit` may be omitted if this is the final argument.\n>> >\n>> > \"may be omitted\" must be followed by a description of what happens\n>> > when omitted (i.e. \"defaults to ...\").\n>>\n>> Then:\n>>\n>> The `commit` may be omitted and defaults to HEAD if this is the final\n>> argument.\n>\n> I find that slightly confusing, although that might just be me.  It's\n> slightly longer, but I would write:\n>\n>         The `commit` may be omitted if this is the final argument, in\n>         which case it defaults to `HEAD`.\n>\n\nThis seems good to be included.\n\n> I also had a look at git-branch(1), which has similar `--merged` and\n> `--no-merged` options and says:\n>\n>         Only list branches whose tips are reachable from the specified\n>         commit (HEAD if not specified).  Implies `--list`.\n>\n> The two options are listed separately in that case.\n\nNot sure this is much of a problem with regards to \"--[no-]merged\"\nI mean isn't the square brackets self-explanatory?\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"270297","messageId":"vpq8u83kb5d.fsf@anie.imag.fr","threadId":"40316","inReplyTo":"CAOLa=ZQmGD2vo1B2K21-f_2RZRXduFt4VNKq2Pp9B_5bO=8=+w@mail.gmail.com","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-18T15:19:42Z","receivedAt":"2015-09-18T15:19:42Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Not sure this is much of a problem with regards to \"--[no-]merged\"\n> I mean isn't the square brackets self-explanatory?\n\nWell, usually --no-foo means \"cancel the effect of --foo\", ie. \"git\ncommand --foo --no-foo\" is equivalent to \"git command\".\n\nHere, --no-merged=some-ref does not _cancel_ the effect but introduce a\nnew behavior that was not the default. So it may make sense to explain\nthis more clearly.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"270298","messageId":"CAOLa=ZRPwoNJv9kG7GfG4ZyjHAoPa7h2PDXErZbV1_B1_ipZ7g@mail.gmail.com","threadId":"40316","inReplyTo":"vpq8u83kb5d.fsf@anie.imag.fr","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-18T15:22:37Z","receivedAt":"2015-09-18T15:22:37Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Fri, Sep 18, 2015 at 8:49 PM, Matthieu Moy\n<Matthieu.Moy@grenoble-inp.fr> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> Not sure this is much of a problem with regards to \"--[no-]merged\"\n>> I mean isn't the square brackets self-explanatory?\n>\n> Well, usually --no-foo means \"cancel the effect of --foo\", ie. \"git\n> command --foo --no-foo\" is equivalent to \"git command\".\n>\n> Here, --no-merged=some-ref does not _cancel_ the effect but introduce a\n> new behavior that was not the default. So it may make sense to explain\n> this more clearly.\n\nmaybe we should s/no/not ? makes not sense to me\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"270300","messageId":"vpqr3lviw38.fsf@anie.imag.fr","threadId":"40316","inReplyTo":"CAOLa=ZRPwoNJv9kG7GfG4ZyjHAoPa7h2PDXErZbV1_B1_ipZ7g@mail.gmail.com","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-18T15:30:19Z","receivedAt":"2015-09-18T15:30:19Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> On Fri, Sep 18, 2015 at 8:49 PM, Matthieu Moy\n> <Matthieu.Moy@grenoble-inp.fr> wrote:\n>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>\n>>> Not sure this is much of a problem with regards to \"--[no-]merged\"\n>>> I mean isn't the square brackets self-explanatory?\n>>\n>> Well, usually --no-foo means \"cancel the effect of --foo\", ie. \"git\n>> command --foo --no-foo\" is equivalent to \"git command\".\n>>\n>> Here, --no-merged=some-ref does not _cancel_ the effect but introduce a\n>> new behavior that was not the default. So it may make sense to explain\n>> this more clearly.\n>\n> maybe we should s/no/not ? makes not sense to me\n\nIt's too late to rename the option without a transition plan (people's\nfinger and scripts use --no-merged already).\n\nBut that would be a good idea to introduce --not-merged as a synonym to\n--no-merged, and deprecate --no-merged (it can remain a deprecated alias\nforever if needed, but we should promote --not-merged in the docs).\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"270301","messageId":"CAOLa=ZQbrE9vuES=DwO0wArdt8F_LUdFdzxggEkHFz4BB=O1OQ@mail.gmail.com","threadId":"40316","inReplyTo":"vpqr3lviw38.fsf@anie.imag.fr","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2015-09-18T15:38:42Z","receivedAt":"2015-09-18T15:38:42Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"On Fri, Sep 18, 2015 at 9:00 PM, Matthieu Moy\n<Matthieu.Moy@grenoble-inp.fr> wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> On Fri, Sep 18, 2015 at 8:49 PM, Matthieu Moy\n>> <Matthieu.Moy@grenoble-inp.fr> wrote:\n>>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>>\n>>>> Not sure this is much of a problem with regards to \"--[no-]merged\"\n>>>> I mean isn't the square brackets self-explanatory?\n>>>\n>>> Well, usually --no-foo means \"cancel the effect of --foo\", ie. \"git\n>>> command --foo --no-foo\" is equivalent to \"git command\".\n>>>\n>>> Here, --no-merged=some-ref does not _cancel_ the effect but introduce a\n>>> new behavior that was not the default. So it may make sense to explain\n>>> this more clearly.\n>>\n>> maybe we should s/no/not ? makes not sense to me\n>\n> It's too late to rename the option without a transition plan (people's\n> finger and scripts use --no-merged already).\n>\n> But that would be a good idea to introduce --not-merged as a synonym to\n> --no-merged, and deprecate --no-merged (it can remain a deprecated alias\n> forever if needed, but we should promote --not-merged in the docs).\n>\n\nEither ways, I'll add it to my personal list of TODO.\nAlso having a look around, even the \"--contains\" option is\nsimilarly documented:\n\n--contains [<commit>]::\n    Only list tags which contain the specified commit (HEAD if not\n    specified).\n\nAbout the issue at hand, we should probably squash this in\n\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 3803bf7..19ef640 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n        [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]\n-       [--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]\n+       [--format=<format>] [(--merged | --no-merged) [<commit>]] [<pattern>...]\n 'git tag' -v <tagname>...\n\n DESCRIPTION\n@@ -165,10 +165,15 @@ This option is only applicable when listing tags\nwithout annotation lines.\n        that of linkgit:git-for-each-ref[1].  When unspecified,\n        defaults to `%(refname:short)`.\n\n---[no-]merged [<commit>]::\n-       Only list tags whose tips are reachable, or not reachable\n-       if '--no-merged' is used, from the specified commit ('HEAD'\n-       if not specified).\n+--merged [<commit>]::\n+       Only list tags whose tips are reachable from the specified\n+       commit (The `commit` may be omitted if this is the final\n+       argument, in which case it defaults to `HEAD`).\n+\n+--no-merged [<commit>]::\n+       Only list tags whose tips are not reachable from the specified\n+       commit (The `commit` may be omitted if this is the final\n+       argument, in which case it defaults to `HEAD`).\n\n CONFIGURATION\n -------------\n\n-- \nRegards,\nKarthik Nayak\n"},{"id":"270302","messageId":"vpqmvwjivf0.fsf@anie.imag.fr","threadId":"40316","inReplyTo":"CAOLa=ZQbrE9vuES=DwO0wArdt8F_LUdFdzxggEkHFz4BB=O1OQ@mail.gmail.com","subject":"Re: [PATCH v17 14/14] tag.c: implement '--merged' and '--no-merged' options","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2015-09-18T15:44:51Z","receivedAt":"2015-09-18T15:44:51Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> About the issue at hand, we should probably squash this in\n\nLooks good to me.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"}]}