threads / patch / 40155

v13, 12 partsport tag.c to use ref-filter APIs

Subject: [PATCH v13 00/12] port tag.c to use ref-filter APIs

## tl;dr

60 messages between Aug 22, 2015 and Aug 27, 2015. Diffs are folded; open one to read it.

replies: 59people: 4as markdown or json

Karthik Nayak· Aug 22, 2015, 03:39 UTC · lore

Part of my GSoC project to port tag.c to use ref-filter APIs. This is a follow up to porting for-each-ref to use ref-filter APIs.

Version 12 can be found here: thread.gmane.org/gmane.comp.version-control.git/276133

Changes since v12:
* %(align)...%(end) now quote formats everything in between the atoms
even if they are string literals. 
* Changet the structure of ref_formatting_state to hold ref_formatting_stack
as the stack and quote_value, this ensures that we do not need to copy the 
quote_value to each element of the stack.
* While checking for %(align) atom, use strbuf_split_str().
* In tag.c support usage of --format with -n<num>.
Karthik Nayak (12):
  ref-filter: move `struct atom_value` to ref-filter.c
  ref-filter: introduce ref_formatting_state and ref_formatting_stack
  utf8: add function to align a string into given strbuf
  ref-filter: implement an `align` atom
  ref-filter: add option to filter out tags, branches and remotes
  ref-filter: support printing N lines from tag annotation
  ref-filter: add support to sort by version
  ref-filter: add option to match literal pattern
  tag.c: use 'ref-filter' data structures
  tag.c: use 'ref-filter' APIs
  tag.c: implement '--format' option
  tag.c: implement '--merged' and '--no-merged' options
 Documentation/git-for-each-ref.txt |  12 ++
 Documentation/git-tag.txt          |  27 ++-
 builtin/for-each-ref.c             |   3 +-
 builtin/tag.c                      | 365 +++++++------------------------------
 ref-filter.c                       | 350 +++++++++++++++++++++++++++++++----
 ref-filter.h                       |  32 +++-
 refs.c                             |   9 +
 refs.h                             |   1 +
 t/t6302-for-each-ref-filter.sh     | 105 +++++++++++
 t/t7004-tag.sh                     |  47 ++++-
 utf8.c                             |  21 +++
 utf8.h                             |  15 ++
 12 files changed, 626 insertions(+), 361 deletions(-)
Interdiff:
Show changes to 6 files +124 −73

Documentation/git-for-each-ref.txt, Documentation/git-tag.txt, ref-filter.c, t/t6302-for-each-ref-filter.sh, t/t7004-tag.sh, builtin/tag.c

diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 1997657..06d468e 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -133,7 +133,8 @@ align::
 	`<position>` is either left, right or middle and `<width>` is
 	the total length of the content with alignment. If the
 	contents length is more than the width then no alignment is
-	performed.
+	performed. If used with '--quote' everything in between %(align:..)
+	and %(end) is quoted.
 
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index c2785d9..3803bf7 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -161,22 +161,15 @@ This option is only applicable when listing tags without annotation lines.
 
 <format>::
 	A string that interpolates `%(fieldname)` from the object
-	pointed at by a ref being shown.  If `fieldname` is prefixed
-	with an asterisk (`*`) and the ref points at a tag object, the
-	value for the field in the object tag refers is used.  When
-	unspecified, defaults to `%(refname:short)`.  It also
-	interpolates `%%` to `%`, and `%xx` where `xx` are hex digits
-	interpolates to character with hex code `xx`; for example
-	`%00` interpolates to `\0` (NUL), `%09` to `\t` (TAB) and
-	`%0a` to `\n` (LF).  The fields are same as those in `git
-	for-each-ref`.
+	pointed at by a ref being shown.  The format is the same as
+	that of linkgit:git-for-each-ref[1].  When unspecified,
+	defaults to `%(refname:short)`.
 
 --[no-]merged [<commit>]::
 	Only list tags whose tips are reachable, or not reachable
 	if '--no-merged' is used, from the specified commit ('HEAD'
 	if not specified).
 
-
 CONFIGURATION
 -------------
 By default, 'git tag' in sign-with-default mode (-s) will use your
diff --git a/ref-filter.c b/ref-filter.c
index 665221b..f8b8fb7 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -65,18 +65,24 @@ struct align {
 	unsigned int width;
 };
 
-struct ref_formatting_state {
-	struct ref_formatting_state *prev;
+#define REF_FORMATTING_STATE_INIT  { 0, NULL }
+
+struct ref_formatting_stack {
+	struct ref_formatting_stack *prev;
 	struct strbuf output;
-	void (*at_end)(struct ref_formatting_state *state);
+	void (*at_end)(struct ref_formatting_stack *stack);
 	void *cb_data;
+};
+
+struct ref_formatting_state {
 	int quote_style;
+	struct ref_formatting_stack *stack;
 };
 
 struct atom_value {
 	const char *s;
 	struct align *align;
-	void (*handler)(struct atom_value *atomv, struct ref_formatting_state **state);
+	void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
 	unsigned long ul; /* used for sorting when not FIELD_STR */
 };
 
@@ -149,19 +155,19 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
 	return at;
 }
 
-static void push_new_state(struct ref_formatting_state **stack)
+static void push_new_stack_element(struct ref_formatting_stack **stack)
 {
-	struct ref_formatting_state *s = xcalloc(1, sizeof(struct ref_formatting_state));
+	struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));
 
 	strbuf_init(&s->output, 0);
 	s->prev = *stack;
 	*stack = s;
 }
 
-static void pop_state(struct ref_formatting_state **stack)
+static void pop_stack_element(struct ref_formatting_stack **stack)
 {
-	struct ref_formatting_state *current = *stack;
-	struct ref_formatting_state *prev = current->prev;
+	struct ref_formatting_stack *current = *stack;
+	struct ref_formatting_stack *prev = current->prev;
 
 	if (prev)
 		strbuf_addbuf(&prev->output, &current->output);
@@ -640,34 +646,49 @@ static inline char *copy_advance(char *dst, const char *src)
 	return dst;
 }
 
-static void align_handler(struct ref_formatting_state *state)
+static void align_handler(struct ref_formatting_stack *stack)
 {
-	struct align *align = (struct align *)state->cb_data;
+	struct align *align = (struct align *)stack->cb_data;
 	struct strbuf s = STRBUF_INIT;
 
-	strbuf_utf8_align(&s, align->position, align->width, state->output.buf);
-	strbuf_reset(&state->output);
-	strbuf_addbuf(&state->output, &s);
+	strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
+	strbuf_swap(&stack->output, &s);
+	strbuf_release(&s);
 	free(align);
 }
 
-static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state **state)
+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 {
-	struct ref_formatting_state *new;
+	struct ref_formatting_stack *new;
 
-	push_new_state(state);
-	new = *state;
+	push_new_stack_element(&state->stack);
+	new = state->stack;
 	new->at_end = align_handler;
 	new->cb_data = atomv->align;
 }
 
-static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state **state)
+static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style);
+
+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 {
-	struct ref_formatting_state *current = *state;
+	struct ref_formatting_stack *current = state->stack;
+	struct strbuf s = STRBUF_INIT;
+
 	if (!current->at_end)
 		die(_("format: `end` atom used without a supporting atom"));
 	current->at_end(current);
-	pop_state(state);
+	/*
+	 * Whenever we have more than one stack element that means we
+	 * are using a certain modifier atom. In that case we need to
+	 * perform quote formatting.
+	 */
+	if (!state->stack->prev->prev) {
+		perform_quote_formatting(&s, current->output.buf, state->quote_style);
+		strbuf_reset(&current->output);
+		strbuf_addbuf(&current->output, &s);
+	}
+	strbuf_release(&s);
+	pop_stack_element(&state->stack);
 }
 
 /*
@@ -766,25 +787,26 @@ static void populate_value(struct ref_array_item *ref)
 			continue;
 		} else if (skip_prefix(name, "align:", &valp)) {
 			struct align *align = xmalloc(sizeof(struct align));
-			char *ep = strchr(valp, ',');
+			struct strbuf **s = strbuf_split_str(valp, ',', 0);
 
-			if (ep)
-				*ep = '\0';
+			/* If the position is given trim the ',' from the first strbuf */
+			if (s[1])
+				strbuf_remove(s[0], s[0]->len - 1, 1);
 
-			if (strtoul_ui(valp, 10, &align->width))
-				die(_("positive width expected align:%s"), valp);
+			if (strtoul_ui(s[0]->buf, 10, &align->width))
+				die(_("positive width expected align:%s"), s[0]->buf);
 
-			if (!ep || !strcmp(ep + 1, "left"))
+			/* If no position is given, default to ALIGN_LEFT */
+			if (!s[1] || !strcmp(s[1]->buf, "left"))
 				align->position = ALIGN_LEFT;
-			else if (!strcmp(ep + 1, "right"))
+			else if (!strcmp(s[1]->buf, "right"))
 				align->position = ALIGN_RIGHT;
-			else if (!strcmp(ep + 1, "middle"))
+			else if (!strcmp(s[1]->buf, "middle"))
 				align->position = ALIGN_MIDDLE;
 			else
-				die(_("improper format entered align:%s"), ep + 1);
+				die(_("improper format entered align:%s"), s[1]->buf);
 
-			if (ep)
-				*ep = ',';
+			strbuf_list_free(s);
 
 			v->align = align;
 			v->handler = align_atom_handler;
@@ -1378,27 +1400,33 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
 	qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
 }
 
-static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style)
 {
-	switch (state->quote_style) {
+	switch (quote_style) {
 	case QUOTE_NONE:
-		strbuf_addstr(&state->output, v->s);
+		strbuf_addstr(s, str);
 		break;
 	case QUOTE_SHELL:
-		sq_quote_buf(&state->output, v->s);
+		sq_quote_buf(s, str);
 		break;
 	case QUOTE_PERL:
-		perl_quote_buf(&state->output, v->s);
+		perl_quote_buf(s, str);
 		break;
 	case QUOTE_PYTHON:
-		python_quote_buf(&state->output, v->s);
+		python_quote_buf(s, str);
 		break;
 	case QUOTE_TCL:
-		tcl_quote_buf(&state->output, v->s);
+		tcl_quote_buf(s, str);
 		break;
 	}
 }
 
+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+{
+	struct strbuf *s = &state->stack->output;
+	perform_quote_formatting(s, v->s, state->quote_style);
+}
+
 static int hex1(char ch)
 {
 	if ('0' <= ch && ch <= '9')
@@ -1419,6 +1447,8 @@ static int hex2(const char *cp)
 
 static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
 {
+	struct strbuf *s = &state->stack->output;
+
 	while (*cp && (!ep || cp < ep)) {
 		if (*cp == '%') {
 			if (cp[1] == '%')
@@ -1426,13 +1456,13 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
 			else {
 				int ch = hex2(cp + 1);
 				if (0 <= ch) {
-					strbuf_addch(&state->output, ch);
+					strbuf_addch(s, ch);
 					cp += 3;
 					continue;
 				}
 			}
 		}
-		strbuf_addch(&state->output, *cp);
+		strbuf_addch(s, *cp);
 		cp++;
 	}
 }
@@ -1485,25 +1515,34 @@ void show_ref_array_item(struct ref_array_item *info, const char *format,
 {
 	const char *cp, *sp, *ep;
 	struct strbuf *final_buf;
-	struct ref_formatting_state *state = NULL;
+	struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
 
-	push_new_state(&state);
-	state->quote_style = quote_style;
+	state.quote_style = quote_style;
+	push_new_stack_element(&state.stack);
 
 	for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
 		struct atom_value *atomv;
 
 		ep = strchr(sp, ')');
 		if (cp < sp)
-			append_literal(cp, sp, state);
+			append_literal(cp, sp, &state);
 		get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
+		/*
+		 * If the atom is a modifier atom, then call the handler function.
+		 * Else, if this is the first element on the stack, then we need to
+		 * format the atom as per the given quote. Else we just add the atom value
+		 * to the current stack element and handle quote formatting at the end.
+		 */
 		if (atomv->handler)
 			atomv->handler(atomv, &state);
-		append_atom(atomv, state);
+		else if (!state.stack->prev)
+			append_atom(atomv, &state);
+		else
+			strbuf_addstr(&state.stack->output, atomv->s);
 	}
 	if (*cp) {
 		sp = cp + strlen(cp);
-		append_literal(cp, sp, state);
+		append_literal(cp, sp, &state);
 	}
 	if (need_color_reset_at_eol) {
 		struct atom_value resetv;
@@ -1512,12 +1551,13 @@ void show_ref_array_item(struct ref_array_item *info, const char *format,
 		if (color_parse("reset", color) < 0)
 			die("BUG: couldn't parse 'reset' as a color");
 		resetv.s = color;
-		append_atom(&resetv, state);
+		append_atom(&resetv, &state);
 	}
-	final_buf = &state->output;
+	if (state.stack->prev)
+		die(_("format: `end` atom missing"));
+	final_buf = &state.stack->output;
 	fwrite(final_buf->buf, 1, final_buf->len, stdout);
-	pop_state(&state);
-
+	pop_stack_element(&state.stack);
 	if (lines > 0) {
 		struct object_id oid;
 		hashcpy(oid.hash, info->objectname);
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 1c56879..38c99c9 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -129,6 +129,27 @@ test_expect_success 'right alignment' '
 	test_cmp expect actual
 '
 
+# Everything in between the %(align)...%(end) atom must be quoted, hence we test this by
+# introducing single quote's in %(align)...%(end), which must not be escaped.
+
+sq="'"
+
+test_expect_success 'alignment with format quote' '
+	cat >expect <<-EOF &&
+	refname is ${sq}           ${sq}\\${sq}${sq}master${sq}\\${sq}${sq}           ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}side${sq}\\${sq}${sq}            ${sq}|
+	refname is ${sq}          ${sq}\\${sq}${sq}odd/spot${sq}\\${sq}${sq}          ${sq}|
+	refname is ${sq}         ${sq}\\${sq}${sq}double-tag${sq}\\${sq}${sq}         ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}four${sq}\\${sq}${sq}            ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}one${sq}\\${sq}${sq}             ${sq}|
+	refname is ${sq}         ${sq}\\${sq}${sq}signed-tag${sq}\\${sq}${sq}         ${sq}|
+	refname is ${sq}           ${sq}\\${sq}${sq}three${sq}\\${sq}${sq}            ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}two${sq}\\${sq}${sq}             ${sq}|
+	EOF
+	git for-each-ref --shell --format="refname is %(align:30,middle)${sq}%(refname:short)${sq}%(end)|" >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'setup for version sort' '
 	test_commit foo1.3 &&
 	test_commit foo1.6 &&
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 335396e..3dd2f51 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1519,10 +1519,6 @@ EOF"
 	test_cmp expect actual
 '
 
-test_expect_success '--format cannot be used with -n' '
-	test_must_fail git tag -l -n4 --format="%(refname)"
-'
-
 test_expect_success '--format should list tags as per format given' '
 	cat >expect <<-\EOF &&
 	refname : refs/tags/foo1.10

diff --git a/builtin/tag.c b/builtin/tag.c
index 781d3e5..bbbcaed 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -23,7 +23,7 @@ static const char * const git_tag_usage[] = {
        N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
        N_("git tag -d <tagname>..."),
        N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-               "\n\t\t[--[no-]merged [<commit>]] [<pattern>...]"),
+               "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
        N_("git tag -v <tagname>..."),
        NULL
 };
@@ -40,10 +40,12 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con
        if (filter->lines == -1)
                filter->lines = 0;
 
-       if (filter->lines)
-               format = "%(align:16,left)%(refname:short)%(end)";
-       else if (!format)
-               format = "%(refname:short)";
+       if (!format) {
+               if (filter->lines)
+                       format = "%(align:16,left)%(refname:short)%(end)";
+               else
+                       format = "%(refname:short)";
+       }
 
        verify_ref_format(format);
        filter_refs(&array, filter, FILTER_REFS_TAGS);
@@ -401,8 +403,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                        copts.padding = 2;
                        run_column_filter(colopts, &copts);
                }
-               if (format && (filter.lines != -1))
-                       die(_("--format and -n are incompatible"));
                filter.name_patterns = argv;
                ret = list_tags(&filter, sorting, format);
                if (column_active(colopts))
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 01/12] ref-filter: move `struct atom_value` to ref-filter.c

Since atom_value is only required for the internal working of ref-filter it doesn't belong in the public header.

Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 ref-filter.c | 5 +++++
 ref-filter.h | 5 +----
 2 files changed, 6 insertions(+), 4 deletions(-)
Show changes to 2 files +6 −4

ref-filter.c, ref-filter.h

diff --git a/ref-filter.c b/ref-filter.c
index 46963a5..e53c77e 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -55,6 +55,11 @@ static struct {
 	{ "color" },
 };
 
+struct atom_value {
+	const char *s;
+	unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
 /*
  * An atom is a valid field atom listed above, possibly prefixed with
  * a "*" to denote deref_tag().
diff --git a/ref-filter.h b/ref-filter.h
index 6bf27d8..45026d0 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -16,10 +16,7 @@
 #define FILTER_REFS_INCLUDE_BROKEN 0x1
 #define FILTER_REFS_ALL 0x2
 
-struct atom_value {
-	const char *s;
-	unsigned long ul; /* used for sorting when not FIELD_STR */
-};
+struct atom_value;
 
 struct ref_sorting {
 	struct ref_sorting *next;
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 02/12] ref-filter: introduce ref_formatting_state and ref_formatting_stack

Introduce ref_formatting_state which will hold the formatted output strbuf instead of directly printing to stdout. This will help us in creating modifier atoms which modify the format specified before printing to stdout.

Implement a stack machinery for ref_formatting_state, this allows us to push and pop elements onto the stack. Whenever we pop an element from the stack, the strbuf from that element is appended to the strbuf of the next element on the stack, this will allow us to support nesting of modifier atoms.

Rename some functions to reflect the changes made: print_value() -> append_atom() emit() -> append_literal()

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 ref-filter.c | 78 +++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 59 insertions(+), 19 deletions(-)
Show changes to ref-filter.c +59 −19
diff --git a/ref-filter.c b/ref-filter.c
index e53c77e..d0d8df0 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -55,6 +55,18 @@ static struct {
 	{ "color" },
 };
 
+#define REF_FORMATTING_STATE_INIT  { 0, NULL }
+
+struct ref_formatting_stack {
+	struct ref_formatting_stack *prev;
+	struct strbuf output;
+};
+
+struct ref_formatting_state {
+	int quote_style;
+	struct ref_formatting_stack *stack;
+};
+
 struct atom_value {
 	const char *s;
 	unsigned long ul; /* used for sorting when not FIELD_STR */
@@ -129,6 +141,27 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
 	return at;
 }
 
+static void push_new_stack_element(struct ref_formatting_stack **stack)
+{
+	struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack));
+
+	strbuf_init(&s->output, 0);
+	s->prev = *stack;
+	*stack = s;
+}
+
+static void pop_stack_element(struct ref_formatting_stack **stack)
+{
+	struct ref_formatting_stack *current = *stack;
+	struct ref_formatting_stack *prev = current->prev;
+
+	if (prev)
+		strbuf_addbuf(&prev->output, &current->output);
+	strbuf_release(&current->output);
+	free(current);
+	*stack = prev;
+}
+
 /*
  * In a format string, find the next occurrence of %(atom).
  */
@@ -1195,30 +1228,27 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
 	qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
 }
 
-static void print_value(struct atom_value *v, int quote_style)
+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
 {
-	struct strbuf sb = STRBUF_INIT;
-	switch (quote_style) {
+	struct strbuf *s = &state->stack->output;
+
+	switch (state->quote_style) {
 	case QUOTE_NONE:
-		fputs(v->s, stdout);
+		strbuf_addstr(s, v->s);
 		break;
 	case QUOTE_SHELL:
-		sq_quote_buf(&sb, v->s);
+		sq_quote_buf(s, v->s);
 		break;
 	case QUOTE_PERL:
-		perl_quote_buf(&sb, v->s);
+		perl_quote_buf(s, v->s);
 		break;
 	case QUOTE_PYTHON:
-		python_quote_buf(&sb, v->s);
+		python_quote_buf(s, v->s);
 		break;
 	case QUOTE_TCL:
-		tcl_quote_buf(&sb, v->s);
+		tcl_quote_buf(s, v->s);
 		break;
 	}
-	if (quote_style != QUOTE_NONE) {
-		fputs(sb.buf, stdout);
-		strbuf_release(&sb);
-	}
 }
 
 static int hex1(char ch)
@@ -1239,8 +1269,10 @@ static int hex2(const char *cp)
 		return -1;
 }
 
-static void emit(const char *cp, const char *ep)
+static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)
 {
+	struct strbuf *s = &state->stack->output;
+
 	while (*cp && (!ep || cp < ep)) {
 		if (*cp == '%') {
 			if (cp[1] == '%')
@@ -1248,13 +1280,13 @@ static void emit(const char *cp, const char *ep)
 			else {
 				int ch = hex2(cp + 1);
 				if (0 <= ch) {
-					putchar(ch);
+					strbuf_addch(s, ch);
 					cp += 3;
 					continue;
 				}
 			}
 		}
-		putchar(*cp);
+		strbuf_addch(s, *cp);
 		cp++;
 	}
 }
@@ -1262,19 +1294,24 @@ static void emit(const char *cp, const char *ep)
 void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
 {
 	const char *cp, *sp, *ep;
+	struct strbuf *final_buf;
+	struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
+
+	state.quote_style = quote_style;
+	push_new_stack_element(&state.stack);
 
 	for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
 		struct atom_value *atomv;
 
 		ep = strchr(sp, ')');
 		if (cp < sp)
-			emit(cp, sp);
+			append_literal(cp, sp, &state);
 		get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
-		print_value(atomv, quote_style);
+		append_atom(atomv, &state);
 	}
 	if (*cp) {
 		sp = cp + strlen(cp);
-		emit(cp, sp);
+		append_literal(cp, sp, &state);
 	}
 	if (need_color_reset_at_eol) {
 		struct atom_value resetv;
@@ -1283,8 +1320,11 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 		if (color_parse("reset", color) < 0)
 			die("BUG: couldn't parse 'reset' as a color");
 		resetv.s = color;
-		print_value(&resetv, quote_style);
+		append_atom(&resetv, &state);
 	}
+	final_buf = &state.stack->output;
+	fwrite(final_buf->buf, 1, final_buf->len, stdout);
+	pop_stack_element(&state.stack);
 	putchar('\n');
 }
 
-- 
2.5.0
Junio C Hamano· Aug 24, 2015, 21:56 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 02/12] ref-filter: introduce ref_formatting_state and ref_formatting_stack

Karthik Nayak <karthik.188@gmail.com> writes:
> +static void push_new_stack_element(struct ref_formatting_stack **stack)
> +{

Micronit. Perhaps s/_new//;, as you do not call the other function pop_old_stack_element().

The remainder of this step looks pretty straight-forward and was a pleasant read.

Thanks.
Karthik Nayak· Aug 25, 2015, 10:26 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 02/12] ref-filter: introduce ref_formatting_state and ref_formatting_stack

On Tue, Aug 25, 2015 at 3:26 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> +static void push_new_stack_element(struct ref_formatting_stack **stack)
>> +{
>
> Micronit.  Perhaps s/_new//;, as you do not call the other function
> pop_old_stack_element().
>
> The remainder of this step looks pretty straight-forward and was a
> pleasant read.
>
> Thanks.
Will do, thanks for reviewing :)
-- 
Regards,
Karthik Nayak
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 03/12] utf8: add function to align a string into given strbuf

Add strbuf_utf8_align() which will align a given string into a strbuf as per given align_type and width. If the width is greater than the string length then no alignment is performed.

Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 utf8.c | 21 +++++++++++++++++++++
 utf8.h | 15 +++++++++++++++
 2 files changed, 36 insertions(+)
Show changes to 2 files +36 −0

utf8.c, utf8.h

diff --git a/utf8.c b/utf8.c
index 28e6d76..00e10c8 100644
--- a/utf8.c
+++ b/utf8.c
@@ -644,3 +644,24 @@ int skip_utf8_bom(char **text, size_t len)
 	*text += strlen(utf8_bom);
 	return 1;
 }
+
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+		       const char *s)
+{
+	int slen = strlen(s);
+	int display_len = utf8_strnwidth(s, slen, 0);
+	int utf8_compensation = slen - display_len;
+
+	if (display_len >= width) {
+		strbuf_addstr(buf, s);
+		return;
+	}
+
+	if (position == ALIGN_LEFT)
+		strbuf_addf(buf, "%-*s", width + utf8_compensation, s);
+	else if (position == ALIGN_MIDDLE) {
+		int left = (width - display_len) / 2;
+		strbuf_addf(buf, "%*s%-*s", left, "", width - left + utf8_compensation, s);
+	} else if (position == ALIGN_RIGHT)
+		strbuf_addf(buf, "%*s", width + utf8_compensation, s);
+}
diff --git a/utf8.h b/utf8.h
index 5a9e94b..7930b44 100644
--- a/utf8.h
+++ b/utf8.h
@@ -55,4 +55,19 @@ int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
  */
 int is_hfs_dotgit(const char *path);
 
+typedef enum {
+	ALIGN_LEFT,
+	ALIGN_MIDDLE,
+	ALIGN_RIGHT
+} align_type;
+
+/*
+ * Align the string given and store it into a strbuf as per the
+ * 'position' and 'width'. If the given string length is larger than
+ * 'width' than then the input string is not truncated and no
+ * alignment is done.
+ */
+void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+		       const char *s);
+
 #endif
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 04/12] ref-filter: implement an `align` atom

Implement an `align` atom which left-, middle-, or right-aligns the content between %(align:..) and %(end).

It is followed by `:<width>,<position>`, where the `<position>` is either left, right or middle and `<width>` is the size of the area into which the content will be placed. If the content between %(align:) and %(end) is more than the width then no alignment is performed. e.g. to align a refname atom to the middle with a total width of 40 we can do: --format="%(align:middle,40)%(refname)%(end)".

We now have a `handler()` for each atom_value which will be called when that atom_value is being parsed, and similarly an `at_end` function for each element of the stack which is to be called when the `end` atom is encountered. Using this we implement the `align` atom which aligns the given strbuf by calling `strbuf_utf8_align()` from utf8.c.

Extract perform_quote_formatting() from append_atom(). Given a string a quote_value and a strbuf, perform_quote_formatting() formats the string based on the quote_value and stores it into the strbuf.

Ensure that quote formatting is performed on the whole of %(align)...%(end) rather than individual atoms. We do this by skipping individual quote formatting for atoms whenever the stack has more than one element, and performing formatting for the entire stack element when the `%(end)` atoms is encountered.

Add documentation and tests for the same.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 Documentation/git-for-each-ref.txt |   9 +++
 ref-filter.c                       | 124 ++++++++++++++++++++++++++++++++++---
 t/t6302-for-each-ref-filter.sh     |  69 +++++++++++++++++++++
 3 files changed, 192 insertions(+), 10 deletions(-)
Show changes to 3 files +192 −10

Documentation/git-for-each-ref.txt, ref-filter.c, t/t6302-for-each-ref-filter.sh

diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index e49d578..943975d 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -127,6 +127,15 @@ color::
 	Change output color.  Followed by `:<colorname>`, where names
 	are described in `color.branch.*`.
 
+align::
+	Left-, middle-, or right-align the content between %(align:..)
+	and %(end). Followed by `:<width>,<position>`, where the
+	`<position>` is either left, right or middle and `<width>` is
+	the total length of the content with alignment. If the
+	contents length is more than the width then no alignment is
+	performed. If used with '--quote' everything in between %(align:..)
+	and %(end) is quoted.
+
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
diff --git a/ref-filter.c b/ref-filter.c
index d0d8df0..ffec10a 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -10,6 +10,7 @@
 #include "quote.h"
 #include "ref-filter.h"
 #include "revision.h"
+#include "utf8.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -53,6 +54,13 @@ static struct {
 	{ "flag" },
 	{ "HEAD" },
 	{ "color" },
+	{ "align" },
+	{ "end" },
+};
+
+struct align {
+	align_type position;
+	unsigned int width;
 };
 
 #define REF_FORMATTING_STATE_INIT  { 0, NULL }
@@ -60,6 +68,8 @@ static struct {
 struct ref_formatting_stack {
 	struct ref_formatting_stack *prev;
 	struct strbuf output;
+	void (*at_end)(struct ref_formatting_stack *stack);
+	void *cb_data;
 };
 
 struct ref_formatting_state {
@@ -69,6 +79,8 @@ struct ref_formatting_state {
 
 struct atom_value {
 	const char *s;
+	struct align *align;
+	void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
 	unsigned long ul; /* used for sorting when not FIELD_STR */
 };
 
@@ -632,6 +644,51 @@ static inline char *copy_advance(char *dst, const char *src)
 	return dst;
 }
 
+static void align_handler(struct ref_formatting_stack *stack)
+{
+	struct align *align = (struct align *)stack->cb_data;
+	struct strbuf s = STRBUF_INIT;
+
+	strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
+	strbuf_swap(&stack->output, &s);
+	strbuf_release(&s);
+	free(align);
+}
+
+static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+	struct ref_formatting_stack *new;
+
+	push_new_stack_element(&state->stack);
+	new = state->stack;
+	new->at_end = align_handler;
+	new->cb_data = atomv->align;
+}
+
+static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style);
+
+static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
+{
+	struct ref_formatting_stack *current = state->stack;
+	struct strbuf s = STRBUF_INIT;
+
+	if (!current->at_end)
+		die(_("format: `end` atom used without a supporting atom"));
+	current->at_end(current);
+	/*
+	 * Whenever we have more than one stack element that means we
+	 * are using a certain modifier atom. In that case we need to
+	 * perform quote formatting.
+	 */
+	if (!state->stack->prev->prev) {
+		perform_quote_formatting(&s, current->output.buf, state->quote_style);
+		strbuf_reset(&current->output);
+		strbuf_addbuf(&current->output, &s);
+	}
+	strbuf_release(&s);
+	pop_stack_element(&state->stack);
+}
+
 /*
  * Parse the object referred by ref, and grab needed value.
  */
@@ -660,6 +717,7 @@ static void populate_value(struct ref_array_item *ref)
 		int deref = 0;
 		const char *refname;
 		const char *formatp;
+		const char *valp;
 		struct branch *branch = NULL;
 
 		if (*name == '*') {
@@ -725,6 +783,35 @@ static void populate_value(struct ref_array_item *ref)
 			else
 				v->s = " ";
 			continue;
+		} else if (skip_prefix(name, "align:", &valp)) {
+			struct align *align = xmalloc(sizeof(struct align));
+			struct strbuf **s = strbuf_split_str(valp, ',', 0);
+
+			/* If the position is given trim the ',' from the first strbuf */
+			if (s[1])
+				strbuf_remove(s[0], s[0]->len - 1, 1);
+
+			if (strtoul_ui(s[0]->buf, 10, &align->width))
+				die(_("positive width expected align:%s"), s[0]->buf);
+
+			/* If no position is given, default to ALIGN_LEFT */
+			if (!s[1] || !strcmp(s[1]->buf, "left"))
+				align->position = ALIGN_LEFT;
+			else if (!strcmp(s[1]->buf, "right"))
+				align->position = ALIGN_RIGHT;
+			else if (!strcmp(s[1]->buf, "middle"))
+				align->position = ALIGN_MIDDLE;
+			else
+				die(_("improper format entered align:%s"), s[1]->buf);
+
+			strbuf_list_free(s);
+
+			v->align = align;
+			v->handler = align_atom_handler;
+			continue;
+		} else if (!strcmp(name, "end")) {
+			v->handler = end_atom_handler;
+			continue;
 		} else
 			continue;
 
@@ -1228,29 +1315,33 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
 	qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
 }
 
-static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style)
 {
-	struct strbuf *s = &state->stack->output;
-
-	switch (state->quote_style) {
+	switch (quote_style) {
 	case QUOTE_NONE:
-		strbuf_addstr(s, v->s);
+		strbuf_addstr(s, str);
 		break;
 	case QUOTE_SHELL:
-		sq_quote_buf(s, v->s);
+		sq_quote_buf(s, str);
 		break;
 	case QUOTE_PERL:
-		perl_quote_buf(s, v->s);
+		perl_quote_buf(s, str);
 		break;
 	case QUOTE_PYTHON:
-		python_quote_buf(s, v->s);
+		python_quote_buf(s, str);
 		break;
 	case QUOTE_TCL:
-		tcl_quote_buf(s, v->s);
+		tcl_quote_buf(s, str);
 		break;
 	}
 }
 
+static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
+{
+	struct strbuf *s = &state->stack->output;
+	perform_quote_formatting(s, v->s, state->quote_style);
+}
+
 static int hex1(char ch)
 {
 	if ('0' <= ch && ch <= '9')
@@ -1307,7 +1398,18 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 		if (cp < sp)
 			append_literal(cp, sp, &state);
 		get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
-		append_atom(atomv, &state);
+		/*
+		 * If the atom is a modifier atom, then call the handler function.
+		 * Else, if this is the first element on the stack, then we need to
+		 * format the atom as per the given quote. Else we just add the atom value
+		 * to the current stack element and handle quote formatting at the end.
+		 */
+		if (atomv->handler)
+			atomv->handler(atomv, &state);
+		else if (!state.stack->prev)
+			append_atom(atomv, &state);
+		else
+			strbuf_addstr(&state.stack->output, atomv->s);
 	}
 	if (*cp) {
 		sp = cp + strlen(cp);
@@ -1322,6 +1424,8 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 		resetv.s = color;
 		append_atom(&resetv, &state);
 	}
+	if (state.stack->prev)
+		die(_("format: `end` atom missing"));
 	final_buf = &state.stack->output;
 	fwrite(final_buf->buf, 1, final_buf->len, stdout);
 	pop_stack_element(&state.stack);
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 505a360..227992b 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -81,4 +81,73 @@ test_expect_success 'filtering with --contains' '
 	test_cmp expect actual
 '
 
+test_expect_success 'left alignment' '
+	cat >expect <<-\EOF &&
+	refname is refs/heads/master  |refs/heads/master
+	refname is refs/heads/side    |refs/heads/side
+	refname is refs/odd/spot      |refs/odd/spot
+	refname is refs/tags/double-tag|refs/tags/double-tag
+	refname is refs/tags/four     |refs/tags/four
+	refname is refs/tags/one      |refs/tags/one
+	refname is refs/tags/signed-tag|refs/tags/signed-tag
+	refname is refs/tags/three    |refs/tags/three
+	refname is refs/tags/two      |refs/tags/two
+	EOF
+	git for-each-ref --format="%(align:30,left)refname is %(refname)%(end)|%(refname)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'middle alignment' '
+	cat >expect <<-\EOF &&
+	| refname is refs/heads/master |refs/heads/master
+	|  refname is refs/heads/side  |refs/heads/side
+	|   refname is refs/odd/spot   |refs/odd/spot
+	|refname is refs/tags/double-tag|refs/tags/double-tag
+	|  refname is refs/tags/four   |refs/tags/four
+	|   refname is refs/tags/one   |refs/tags/one
+	|refname is refs/tags/signed-tag|refs/tags/signed-tag
+	|  refname is refs/tags/three  |refs/tags/three
+	|   refname is refs/tags/two   |refs/tags/two
+	EOF
+	git for-each-ref --format="|%(align:30,middle)refname is %(refname)%(end)|%(refname)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'right alignment' '
+	cat >expect <<-\EOF &&
+	|  refname is refs/heads/master|refs/heads/master
+	|    refname is refs/heads/side|refs/heads/side
+	|      refname is refs/odd/spot|refs/odd/spot
+	|refname is refs/tags/double-tag|refs/tags/double-tag
+	|     refname is refs/tags/four|refs/tags/four
+	|      refname is refs/tags/one|refs/tags/one
+	|refname is refs/tags/signed-tag|refs/tags/signed-tag
+	|    refname is refs/tags/three|refs/tags/three
+	|      refname is refs/tags/two|refs/tags/two
+	EOF
+	git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual &&
+	test_cmp expect actual
+'
+
+# Everything in between the %(align)...%(end) atom must be quoted, hence we test this by
+# introducing single quote's in %(align)...%(end), which must not be escaped.
+
+sq="'"
+
+test_expect_success 'alignment with format quote' '
+	cat >expect <<-EOF &&
+	refname is ${sq}           ${sq}\\${sq}${sq}master${sq}\\${sq}${sq}           ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}side${sq}\\${sq}${sq}            ${sq}|
+	refname is ${sq}          ${sq}\\${sq}${sq}odd/spot${sq}\\${sq}${sq}          ${sq}|
+	refname is ${sq}         ${sq}\\${sq}${sq}double-tag${sq}\\${sq}${sq}         ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}four${sq}\\${sq}${sq}            ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}one${sq}\\${sq}${sq}             ${sq}|
+	refname is ${sq}         ${sq}\\${sq}${sq}signed-tag${sq}\\${sq}${sq}         ${sq}|
+	refname is ${sq}           ${sq}\\${sq}${sq}three${sq}\\${sq}${sq}            ${sq}|
+	refname is ${sq}            ${sq}\\${sq}${sq}two${sq}\\${sq}${sq}             ${sq}|
+	EOF
+	git for-each-ref --shell --format="refname is %(align:30,middle)${sq}%(refname:short)${sq}%(end)|" >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.5.0
Junio C Hamano· Aug 24, 2015, 22:13 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

Karthik Nayak <karthik.188@gmail.com> writes:
Show 16 quoted lines
> +static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style);
> +
> +static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
> +{
> +	struct ref_formatting_stack *current = state->stack;
> +	struct strbuf s = STRBUF_INIT;
> +
> +	if (!current->at_end)
> +		die(_("format: `end` atom used without a supporting atom"));
> +	current->at_end(current);
> +	/*
> +	 * Whenever we have more than one stack element that means we
> +	 * are using a certain modifier atom. In that case we need to
> +	 * perform quote formatting.
> +	 */
> +	if (!state->stack->prev->prev) {

The comment and the condition seem to be saying opposite things. The code says "If the stack only has one prev that is the very initial one, then we do the quoting, i.e. the result of expanding the enclosed string in %(start-something)...%(end) is quoted only when that appears at the top level", which feels more correct than the comment that says "if we are about to pop after seeing the first %(end) in %(start)...%(another)...%(end)...%(end) sequence, we quote what is between %(another)...%(end)".

Show 8 quoted lines
> +		perform_quote_formatting(&s, current->output.buf, state->quote_style);
> +		strbuf_reset(&current->output);
> +		strbuf_addbuf(&current->output, &s);
> +	}
> +	strbuf_release(&s);
> +	pop_stack_element(&state->stack);
> +}
> +
Show 38 quoted lines
> @@ -1228,29 +1315,33 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
>  	qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
>  }
>  
> -static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
> +static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style)
>  {
> -	struct strbuf *s = &state->stack->output;
> -
> -	switch (state->quote_style) {
> +	switch (quote_style) {
>  	case QUOTE_NONE:
> -		strbuf_addstr(s, v->s);
> +		strbuf_addstr(s, str);
>  		break;
>  	case QUOTE_SHELL:
> -		sq_quote_buf(s, v->s);
> +		sq_quote_buf(s, str);
>  		break;
>  	case QUOTE_PERL:
> -		perl_quote_buf(s, v->s);
> +		perl_quote_buf(s, str);
>  		break;
>  	case QUOTE_PYTHON:
> -		python_quote_buf(s, v->s);
> +		python_quote_buf(s, str);
>  		break;
>  	case QUOTE_TCL:
> -		tcl_quote_buf(s, v->s);
> +		tcl_quote_buf(s, str);
>  		break;
>  	}
>  }
>  
> +static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
> +{
> +	struct strbuf *s = &state->stack->output;
> +	perform_quote_formatting(s, v->s, state->quote_style);

Hmmm, do we want to unconditionally do the quote here, or only when we are not being captured by upcoming %(end) to be consistent with the behaviour of end_atom_handler() above?

Show 17 quoted lines
> @@ -1307,7 +1398,18 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
>  		if (cp < sp)
>  			append_literal(cp, sp, &state);
>  		get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
> -		append_atom(atomv, &state);
> +		/*
> +		 * If the atom is a modifier atom, then call the handler function.
> +		 * Else, if this is the first element on the stack, then we need to
> +		 * format the atom as per the given quote. Else we just add the atom value
> +		 * to the current stack element and handle quote formatting at the end.
> +		 */
> +		if (atomv->handler)
> +			atomv->handler(atomv, &state);
> +		else if (!state.stack->prev)
> +			append_atom(atomv, &state);
> +		else
> +			strbuf_addstr(&state.stack->output, atomv->s);

Ahh, this explains why you are not doing it above, but I do not think if this is a good division of labor.

You can see that I expected that "if !state.stack->prev" check to be inside append_atom(), and I would imagine future readers would have the same expectation when reading this code. I.e.

	append_atom(struct atom_value *v, struct ref_f_s *state)
        {
		if (state->stack->prev)
			strbuf_addstr(&state->stack->output, v->s);
		else
                	quote_format(&state->stack->output, v->s, state->quote_style);
	}

The end result may be the same, but I do think "append_atom is to always quote, so we do an unquoted appending by hand" is a bad way to do this.

Moreover, notice that the function signature of append_atom() is exactly the same as atomv->handler's. I wonder if it would be easier to understand if you made append_atom() the handler for a non-magic atoms, which would let you do the above without any if/else and just a single unconditional

	atomv->handler(atomv, &state);
Junio C Hamano· Aug 24, 2015, 22:15 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

Junio C Hamano <gitster@pobox.com> writes:
Show 23 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> +static void end_atom_handler(struct atom_value *atomv, struct
>> ref_formatting_state *state)
>> +{
>> +	struct ref_formatting_stack *current = state->stack;
>> +	struct strbuf s = STRBUF_INIT;
>> +
>> +	if (!current->at_end)
>> +		die(_("format: `end` atom used without a supporting atom"));
>> +	current->at_end(current);
>> +	/*
>> +	 * Whenever we have more than one stack element that means we
>> +	 * are using a certain modifier atom. In that case we need to
>> +	 * perform quote formatting.
>> +	 */
>> +	if (!state->stack->prev->prev) {
>
> The comment and the condition seem to be saying opposite things.
> The code says "If the stack only has one prev that is the very
> initial one, then we do the quoting, i.e. the result of expanding
> the enclosed string in %(start-something)...%(end) is quoted only
> when that appears at the top level", which feels more correct...

As this already allows us to use nested construct, I think we would want to have test that uses

    %(align:30,left)%(align:20,right)%(refname:short)%(end)%(end)
or something like that ;-)
Very nice.
Karthik Nayak· Aug 25, 2015, 13:28 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

On Tue, Aug 25, 2015 at 3:45 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 34 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>>> +static void end_atom_handler(struct atom_value *atomv, struct
>>> ref_formatting_state *state)
>>> +{
>>> +    struct ref_formatting_stack *current = state->stack;
>>> +    struct strbuf s = STRBUF_INIT;
>>> +
>>> +    if (!current->at_end)
>>> +            die(_("format: `end` atom used without a supporting atom"));
>>> +    current->at_end(current);
>>> +    /*
>>> +     * Whenever we have more than one stack element that means we
>>> +     * are using a certain modifier atom. In that case we need to
>>> +     * perform quote formatting.
>>> +     */
>>> +    if (!state->stack->prev->prev) {
>>
>> The comment and the condition seem to be saying opposite things.
>> The code says "If the stack only has one prev that is the very
>> initial one, then we do the quoting, i.e. the result of expanding
>> the enclosed string in %(start-something)...%(end) is quoted only
>> when that appears at the top level", which feels more correct...
>
> As this already allows us to use nested construct, I think we would
> want to have test that uses
>
>     %(align:30,left)%(align:20,right)%(refname:short)%(end)%(end)
>
> or something like that ;-)
>
> Very nice.
Ok, will do that :)
-- 
Regards,
Karthik Nayak
Matthieu Moy· Aug 25, 2015, 06:47 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

Junio C Hamano <gitster@pobox.com> writes:
Show 13 quoted lines
> You can see that I expected that "if !state.stack->prev" check to be
> inside append_atom(), and I would imagine future readers would have
> the same expectation when reading this code.  I.e.
>
> 	append_atom(struct atom_value *v, struct ref_f_s *state)
>         {
> 		if (state->stack->prev)
> 			strbuf_addstr(&state->stack->output, v->s);
> 		else
>                 	quote_format(&state->stack->output, v->s, state->quote_style);
> 	}
>
> The end result may be the same,

There's another call to append_atom() when inserting the "reset color at end of line if needed", so moving this if inside append_atom means we would do the check also for the reset color. It would not change the behavior (by construction, we insert it only when the stack has only the initial element), so it's OK.

I agree that this is a good thing to do.
Show 5 quoted lines
> Moreover, notice that the function signature of append_atom() is
> exactly the same as atomv->handler's.  I wonder if it would be
> easier to understand if you made append_atom() the handler for a
> non-magic atoms, which would let you do the above without any if/else
> and just a single unconditional

I can't decide between "ah, very elegant" and "no, too much magic" ;-). I lean towards the former.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Karthik Nayak· Aug 25, 2015, 13:30 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

On Tue, Aug 25, 2015 at 12:17 PM, Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> wrote:

Show 33 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> You can see that I expected that "if !state.stack->prev" check to be
>> inside append_atom(), and I would imagine future readers would have
>> the same expectation when reading this code.  I.e.
>>
>>       append_atom(struct atom_value *v, struct ref_f_s *state)
>>         {
>>               if (state->stack->prev)
>>                       strbuf_addstr(&state->stack->output, v->s);
>>               else
>>                       quote_format(&state->stack->output, v->s, state->quote_style);
>>       }
>>
>> The end result may be the same,
>
> There's another call to append_atom() when inserting the "reset color at
> end of line if needed", so moving this if inside append_atom means we
> would do the check also for the reset color. It would not change the
> behavior (by construction, we insert it only when the stack has only the
> initial element), so it's OK.
>
> I agree that this is a good thing to do.
>
>> Moreover, notice that the function signature of append_atom() is
>> exactly the same as atomv->handler's.  I wonder if it would be
>> easier to understand if you made append_atom() the handler for a
>> non-magic atoms, which would let you do the above without any if/else
>> and just a single unconditional
>
> I can't decide between "ah, very elegant" and "no, too much magic" ;-).
> I lean towards the former.
>

I like the idea of using atomv->handler() a lot, mostly cause this would eventually help us clean up populate_atom() which currently seems like a huge dump of code.

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 25, 2015, 17:56 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

Karthik Nayak <karthik.188@gmail.com> writes:
> I like the idea of using atomv->handler() a lot, mostly cause this
> would eventually
> help us clean up populate_atom() which currently seems like a huge dump of code.
I think you already said that last time we had this discussion ;-)
http://thread.gmane.org/gmane.comp.version-control.git/275537/focus=275778
Karthik Nayak· Aug 26, 2015, 06:41 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

On Tue, Aug 25, 2015 at 11:26 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 10 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> I like the idea of using atomv->handler() a lot, mostly cause this
>> would eventually
>> help us clean up populate_atom() which currently seems like a huge dump of code.
>
> I think you already said that last time we had this discussion ;-)
>
> http://thread.gmane.org/gmane.comp.version-control.git/275537/focus=275778
>

Yes :) I really want to clean that up eventually, every since Duy mentioned about it.

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 25, 2015, 17:52 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
Show 21 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> You can see that I expected that "if !state.stack->prev" check to be
>> inside append_atom(), and I would imagine future readers would have
>> the same expectation when reading this code.  I.e.
>>
>> 	append_atom(struct atom_value *v, struct ref_f_s *state)
>>         {
>> 		if (state->stack->prev)
>> 			strbuf_addstr(&state->stack->output, v->s);
>> 		else
>>                 	quote_format(&state->stack->output, v->s, state->quote_style);
>> 	}
>>
>> The end result may be the same,
>
> There's another call to append_atom() when inserting the "reset color at
> end of line if needed",  so moving this if inside append_atom means we
> would do the check also for the reset color. It would not change the
> behavior (by construction, we insert it only when the stack has only the
> initial element), so it's OK.

Thanks for checking---I did overlook that other callsite and did not check if the suggested change was sensible there.

Karthik Nayak· Aug 25, 2015, 13:28 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 04/12] ref-filter: implement an `align` atom

On Tue, Aug 25, 2015 at 3:43 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 28 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> +static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style);
>> +
>> +static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
>> +{
>> +     struct ref_formatting_stack *current = state->stack;
>> +     struct strbuf s = STRBUF_INIT;
>> +
>> +     if (!current->at_end)
>> +             die(_("format: `end` atom used without a supporting atom"));
>> +     current->at_end(current);
>> +     /*
>> +      * Whenever we have more than one stack element that means we
>> +      * are using a certain modifier atom. In that case we need to
>> +      * perform quote formatting.
>> +      */
>> +     if (!state->stack->prev->prev) {
>
> The comment and the condition seem to be saying opposite things.
> The code says "If the stack only has one prev that is the very
> initial one, then we do the quoting, i.e. the result of expanding
> the enclosed string in %(start-something)...%(end) is quoted only
> when that appears at the top level", which feels more correct than
> the comment that says "if we are about to pop after seeing the
> first %(end) in %(start)...%(another)...%(end)...%(end) sequence,
> we quote what is between %(another)...%(end)".
>
That sounds misleading indeed will need to change that.
Show 96 quoted lines
>> +             perform_quote_formatting(&s, current->output.buf, state->quote_style);
>> +             strbuf_reset(&current->output);
>> +             strbuf_addbuf(&current->output, &s);
>> +     }
>> +     strbuf_release(&s);
>> +     pop_stack_element(&state->stack);
>> +}
>> +
>
>> @@ -1228,29 +1315,33 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
>>       qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs);
>>  }
>>
>> -static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
>> +static void perform_quote_formatting(struct strbuf *s, const char *str, int quote_style)
>>  {
>> -     struct strbuf *s = &state->stack->output;
>> -
>> -     switch (state->quote_style) {
>> +     switch (quote_style) {
>>       case QUOTE_NONE:
>> -             strbuf_addstr(s, v->s);
>> +             strbuf_addstr(s, str);
>>               break;
>>       case QUOTE_SHELL:
>> -             sq_quote_buf(s, v->s);
>> +             sq_quote_buf(s, str);
>>               break;
>>       case QUOTE_PERL:
>> -             perl_quote_buf(s, v->s);
>> +             perl_quote_buf(s, str);
>>               break;
>>       case QUOTE_PYTHON:
>> -             python_quote_buf(s, v->s);
>> +             python_quote_buf(s, str);
>>               break;
>>       case QUOTE_TCL:
>> -             tcl_quote_buf(s, v->s);
>> +             tcl_quote_buf(s, str);
>>               break;
>>       }
>>  }
>>
>> +static void append_atom(struct atom_value *v, struct ref_formatting_state *state)
>> +{
>> +     struct strbuf *s = &state->stack->output;
>> +     perform_quote_formatting(s, v->s, state->quote_style);
>
> Hmmm, do we want to unconditionally do the quote here, or only when
> we are not being captured by upcoming %(end) to be consistent with
> the behaviour of end_atom_handler() above?
>
>> @@ -1307,7 +1398,18 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
>>               if (cp < sp)
>>                       append_literal(cp, sp, &state);
>>               get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
>> -             append_atom(atomv, &state);
>> +             /*
>> +              * If the atom is a modifier atom, then call the handler function.
>> +              * Else, if this is the first element on the stack, then we need to
>> +              * format the atom as per the given quote. Else we just add the atom value
>> +              * to the current stack element and handle quote formatting at the end.
>> +              */
>> +             if (atomv->handler)
>> +                     atomv->handler(atomv, &state);
>> +             else if (!state.stack->prev)
>> +                     append_atom(atomv, &state);
>> +             else
>> +                     strbuf_addstr(&state.stack->output, atomv->s);
>
> Ahh, this explains why you are not doing it above, but I do not
> think if this is a good division of labor.
>
> You can see that I expected that "if !state.stack->prev" check to be
> inside append_atom(), and I would imagine future readers would have
> the same expectation when reading this code.  I.e.
>
>         append_atom(struct atom_value *v, struct ref_f_s *state)
>         {
>                 if (state->stack->prev)
>                         strbuf_addstr(&state->stack->output, v->s);
>                 else
>                         quote_format(&state->stack->output, v->s, state->quote_style);
>         }
>
> The end result may be the same, but I do think "append_atom is to
> always quote, so we do an unquoted appending by hand" is a bad way
> to do this.
>
> Moreover, notice that the function signature of append_atom() is
> exactly the same as atomv->handler's.  I wonder if it would be
> easier to understand if you made append_atom() the handler for a
> non-magic atoms, which would let you do the above without any if/else
> and just a single unconditional
>
>         atomv->handler(atomv, &state);
I like the atomv->handler() idea I think I'll work on that now.
-- 
Regards,
Karthik Nayak
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

From: Karthik Nayak <karthik.188@gmail.com>

Add a function called 'for_each_reftype_fullpath()' to refs.{c,h} which iterates through each ref for the given path without trimming the path and also accounting for broken refs, if mentioned.

Add 'filter_ref_kind()' in ref-filter.c to check the kind of ref being handled and return the kind to 'ref_filter_handler()', where we discard refs which we do not need and assign the kind to needed refs.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 ref-filter.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 ref-filter.h | 12 ++++++++++--
 refs.c       |  9 +++++++++
 refs.h       |  1 +
 4 files changed, 74 insertions(+), 7 deletions(-)
Show changes to 4 files +74 −7

ref-filter.c, ref-filter.h, refs.c, refs.h

diff --git a/ref-filter.c b/ref-filter.c
index ffec10a..d5fae1a 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1123,6 +1123,36 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
 	return ref;
 }
 
+static int filter_ref_kind(struct ref_filter *filter, const char *refname)
+{
+	unsigned int i;
+
+	static struct {
+		const char *prefix;
+		unsigned int kind;
+	} ref_kind[] = {
+		{ "refs/heads/" , FILTER_REFS_BRANCHES },
+		{ "refs/remotes/" , FILTER_REFS_REMOTES },
+		{ "refs/tags/", FILTER_REFS_TAGS}
+	};
+
+	if (filter->kind == FILTER_REFS_BRANCHES)
+		return FILTER_REFS_BRANCHES;
+	else if (filter->kind == FILTER_REFS_REMOTES)
+		return FILTER_REFS_REMOTES;
+	else if (filter->kind == FILTER_REFS_TAGS)
+		return FILTER_REFS_TAGS;
+	else if (!strcmp(refname, "HEAD"))
+		return FILTER_REFS_DETACHED_HEAD;
+
+	for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+		if (starts_with(refname, ref_kind[i].prefix))
+			return ref_kind[i].kind;
+	}
+
+	return FILTER_REFS_OTHERS;
+}
+
 /*
  * A call-back given to for_each_ref().  Filter refs and keep them for
  * later object processing.
@@ -1133,6 +1163,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
 	struct ref_filter *filter = ref_cbdata->filter;
 	struct ref_array_item *ref;
 	struct commit *commit = NULL;
+	unsigned int kind;
 
 	if (flag & REF_BAD_NAME) {
 		warning("ignoring ref with broken name %s", refname);
@@ -1144,6 +1175,10 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
 		return 0;
 	}
 
+	kind = filter_ref_kind(filter, refname);
+	if (!(kind & filter->kind))
+		return 0;
+
 	if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
 		return 0;
 
@@ -1175,6 +1210,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
 
 	REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
 	ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
+	ref->kind = kind;
 	return 0;
 }
 
@@ -1251,16 +1287,29 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
 {
 	struct ref_filter_cbdata ref_cbdata;
 	int ret = 0;
+	unsigned int broken = 0;
 
 	ref_cbdata.array = array;
 	ref_cbdata.filter = filter;
 
 	/*  Simple per-ref filtering */
-	if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
-		ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
-	else if (type & FILTER_REFS_ALL)
-		ret = for_each_ref(ref_filter_handler, &ref_cbdata);
-	else if (type)
+	if (type & FILTER_REFS_INCLUDE_BROKEN) {
+		type &= ~FILTER_REFS_INCLUDE_BROKEN;
+		broken = 1;
+	}
+
+	filter->kind = type;
+	if (type == FILTER_REFS_BRANCHES)
+		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
+	else if (type == FILTER_REFS_REMOTES)
+		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
+	else if (type == FILTER_REFS_TAGS)
+		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
+	else if (type & FILTER_REFS_ALL) {
+		ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
+		if (type & FILTER_REFS_DETACHED_HEAD)
+			head_ref(ref_filter_handler, &ref_cbdata);
+	} else
 		die("filter_refs: invalid type");
 
 	/*  Filters that need revision walking */
diff --git a/ref-filter.h b/ref-filter.h
index 45026d0..99f081b 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -13,8 +13,14 @@
 #define QUOTE_PYTHON 4
 #define QUOTE_TCL 8
 
-#define FILTER_REFS_INCLUDE_BROKEN 0x1
-#define FILTER_REFS_ALL 0x2
+#define FILTER_REFS_INCLUDE_BROKEN 0x0001
+#define FILTER_REFS_TAGS           0x0002
+#define FILTER_REFS_BRANCHES       0x0004
+#define FILTER_REFS_REMOTES        0x0008
+#define FILTER_REFS_OTHERS         0x0010
+#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
+				    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
+#define FILTER_REFS_DETACHED_HEAD  0x0020
 
 struct atom_value;
 
@@ -27,6 +33,7 @@ struct ref_sorting {
 struct ref_array_item {
 	unsigned char objectname[20];
 	int flag;
+	unsigned int kind;
 	const char *symref;
 	struct commit *commit;
 	struct atom_value *value;
@@ -51,6 +58,7 @@ struct ref_filter {
 	struct commit *merge_commit;
 
 	unsigned int with_commit_tag_algo : 1;
+	unsigned int kind;
 };
 
 struct ref_filter_cbdata {
diff --git a/refs.c b/refs.c
index 4e15f60..3266617 100644
--- a/refs.c
+++ b/refs.c
@@ -2150,6 +2150,15 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 			       strlen(git_replace_ref_base), 0, cb_data);
 }
 
+int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)
+{
+	unsigned int flag = 0;
+
+	if (broken)
+		flag = DO_FOR_EACH_INCLUDE_BROKEN;
+	return do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);
+}
+
 int head_ref_namespaced(each_ref_fn fn, void *cb_data)
 {
 	struct strbuf buf = STRBUF_INIT;
diff --git a/refs.h b/refs.h
index e9a5f32..6e913ee 100644
--- a/refs.h
+++ b/refs.h
@@ -179,6 +179,7 @@ extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
 extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
 extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
+extern int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data);
 
 extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
-- 
2.5.0
Junio C Hamano· Aug 24, 2015, 22:24 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

Karthik Nayak <karthik.188@gmail.com> writes:
Show 5 quoted lines
> From: Karthik Nayak <karthik.188@gmail.com>
>
> Add a function called 'for_each_reftype_fullpath()' to refs.{c,h}
> which iterates through each ref for the given path without trimming
> the path and also accounting for broken refs, if mentioned.

For this part, I think you would want to borrow an extra pair of eyes from Michael.

Show 72 quoted lines
>
> Add 'filter_ref_kind()' in ref-filter.c to check the kind of ref being
> handled and return the kind to 'ref_filter_handler()', where we
> discard refs which we do not need and assign the kind to needed refs.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
>  ref-filter.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
>  ref-filter.h | 12 ++++++++++--
>  refs.c       |  9 +++++++++
>  refs.h       |  1 +
>  4 files changed, 74 insertions(+), 7 deletions(-)
>
> diff --git a/ref-filter.c b/ref-filter.c
> index ffec10a..d5fae1a 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -1123,6 +1123,36 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
>  	return ref;
>  }
>  
> +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
> +{
> +	unsigned int i;
> +
> +	static struct {
> +		const char *prefix;
> +		unsigned int kind;
> +	} ref_kind[] = {
> +		{ "refs/heads/" , FILTER_REFS_BRANCHES },
> +		{ "refs/remotes/" , FILTER_REFS_REMOTES },
> +		{ "refs/tags/", FILTER_REFS_TAGS}
> +	};
> +
> +	if (filter->kind == FILTER_REFS_BRANCHES)
> +		return FILTER_REFS_BRANCHES;
> +	else if (filter->kind == FILTER_REFS_REMOTES)
> +		return FILTER_REFS_REMOTES;
> +	else if (filter->kind == FILTER_REFS_TAGS)
> +		return FILTER_REFS_TAGS;
> +	else if (!strcmp(refname, "HEAD"))
> +		return FILTER_REFS_DETACHED_HEAD;
> +
> +	for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
> +		if (starts_with(refname, ref_kind[i].prefix))
> +			return ref_kind[i].kind;
> +	}
> +
> +	return FILTER_REFS_OTHERS;
> +}
> +
>  /*
>   * A call-back given to for_each_ref().  Filter refs and keep them for
>   * later object processing.
> @@ -1133,6 +1163,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>  	struct ref_filter *filter = ref_cbdata->filter;
>  	struct ref_array_item *ref;
>  	struct commit *commit = NULL;
> +	unsigned int kind;
>  
>  	if (flag & REF_BAD_NAME) {
>  		warning("ignoring ref with broken name %s", refname);
> @@ -1144,6 +1175,10 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>  		return 0;
>  	}
>  
> +	kind = filter_ref_kind(filter, refname);
> +	if (!(kind & filter->kind))
> +		return 0;
> +

When filter->kind is set to some single-bit thing (e.g. FILTER_REFS_BRANCHES) by the caller of ref_filter_handler(), then this call of filter_ref_kind() will just parrot that without even looking at refname. And then the if statement says "yes, they have common bit(s)". Even when refname is "refs/tags/v1.0" or "HEAD".

And if this code knows that "refs/tags/v1.0" or "HEAD" will never come when filter->kind is exactly FILTER_REFS_BRANCHES, then I do not see the point of calling filter_ref_kind().

I am not sure what this is trying to do. Can you clarify the thinking behind this as a comment to filter_ref_kind()?

Show 112 quoted lines
> @@ -1175,6 +1210,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>  
>  	REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
>  	ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
> +	ref->kind = kind;
>  	return 0;
>  }
>  
> @@ -1251,16 +1287,29 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
>  {
>  	struct ref_filter_cbdata ref_cbdata;
>  	int ret = 0;
> +	unsigned int broken = 0;
>  
>  	ref_cbdata.array = array;
>  	ref_cbdata.filter = filter;
>  
>  	/*  Simple per-ref filtering */
> -	if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
> -		ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
> -	else if (type & FILTER_REFS_ALL)
> -		ret = for_each_ref(ref_filter_handler, &ref_cbdata);
> -	else if (type)
> +	if (type & FILTER_REFS_INCLUDE_BROKEN) {
> +		type &= ~FILTER_REFS_INCLUDE_BROKEN;
> +		broken = 1;
> +	}
> +
> +	filter->kind = type;
> +	if (type == FILTER_REFS_BRANCHES)
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
> +	else if (type == FILTER_REFS_REMOTES)
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
> +	else if (type == FILTER_REFS_TAGS)
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
> +	else if (type & FILTER_REFS_ALL) {
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
> +		if (type & FILTER_REFS_DETACHED_HEAD)
> +			head_ref(ref_filter_handler, &ref_cbdata);
> +	} else
>  		die("filter_refs: invalid type");
>  
>  	/*  Filters that need revision walking */
> diff --git a/ref-filter.h b/ref-filter.h
> index 45026d0..99f081b 100644
> --- a/ref-filter.h
> +++ b/ref-filter.h
> @@ -13,8 +13,14 @@
>  #define QUOTE_PYTHON 4
>  #define QUOTE_TCL 8
>  
> -#define FILTER_REFS_INCLUDE_BROKEN 0x1
> -#define FILTER_REFS_ALL 0x2
> +#define FILTER_REFS_INCLUDE_BROKEN 0x0001
> +#define FILTER_REFS_TAGS           0x0002
> +#define FILTER_REFS_BRANCHES       0x0004
> +#define FILTER_REFS_REMOTES        0x0008
> +#define FILTER_REFS_OTHERS         0x0010
> +#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
> +				    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
> +#define FILTER_REFS_DETACHED_HEAD  0x0020
>  
>  struct atom_value;
>  
> @@ -27,6 +33,7 @@ struct ref_sorting {
>  struct ref_array_item {
>  	unsigned char objectname[20];
>  	int flag;
> +	unsigned int kind;
>  	const char *symref;
>  	struct commit *commit;
>  	struct atom_value *value;
> @@ -51,6 +58,7 @@ struct ref_filter {
>  	struct commit *merge_commit;
>  
>  	unsigned int with_commit_tag_algo : 1;
> +	unsigned int kind;
>  };
>  
>  struct ref_filter_cbdata {
> diff --git a/refs.c b/refs.c
> index 4e15f60..3266617 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2150,6 +2150,15 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
>  			       strlen(git_replace_ref_base), 0, cb_data);
>  }
>  
> +int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)
> +{
> +	unsigned int flag = 0;
> +
> +	if (broken)
> +		flag = DO_FOR_EACH_INCLUDE_BROKEN;
> +	return do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);
> +}
> +
>  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
>  {
>  	struct strbuf buf = STRBUF_INIT;
> diff --git a/refs.h b/refs.h
> index e9a5f32..6e913ee 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -179,6 +179,7 @@ extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
>  extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
>  extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
>  extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
> +extern int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data);
>  
>  extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
>  extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
Karthik Nayak· Aug 25, 2015, 13:07 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

On Tue, Aug 25, 2015 at 3:54 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 90 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> Add a function called 'for_each_reftype_fullpath()' to refs.{c,h}
>> which iterates through each ref for the given path without trimming
>> the path and also accounting for broken refs, if mentioned.
>
> For this part, I think you would want to borrow an extra pair of
> eyes from Michael.
>
>>
>> Add 'filter_ref_kind()' in ref-filter.c to check the kind of ref being
>> handled and return the kind to 'ref_filter_handler()', where we
>> discard refs which we do not need and assign the kind to needed refs.
>>
>> Mentored-by: Christian Couder <christian.couder@gmail.com>
>> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
>> ---
>>  ref-filter.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
>>  ref-filter.h | 12 ++++++++++--
>>  refs.c       |  9 +++++++++
>>  refs.h       |  1 +
>>  4 files changed, 74 insertions(+), 7 deletions(-)
>>
>> diff --git a/ref-filter.c b/ref-filter.c
>> index ffec10a..d5fae1a 100644
>> --- a/ref-filter.c
>> +++ b/ref-filter.c
>> @@ -1123,6 +1123,36 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
>>       return ref;
>>  }
>>
>> +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
>> +{
>> +     unsigned int i;
>> +
>> +     static struct {
>> +             const char *prefix;
>> +             unsigned int kind;
>> +     } ref_kind[] = {
>> +             { "refs/heads/" , FILTER_REFS_BRANCHES },
>> +             { "refs/remotes/" , FILTER_REFS_REMOTES },
>> +             { "refs/tags/", FILTER_REFS_TAGS}
>> +     };
>> +
>> +     if (filter->kind == FILTER_REFS_BRANCHES)
>> +             return FILTER_REFS_BRANCHES;
>> +     else if (filter->kind == FILTER_REFS_REMOTES)
>> +             return FILTER_REFS_REMOTES;
>> +     else if (filter->kind == FILTER_REFS_TAGS)
>> +             return FILTER_REFS_TAGS;
>> +     else if (!strcmp(refname, "HEAD"))
>> +             return FILTER_REFS_DETACHED_HEAD;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
>> +             if (starts_with(refname, ref_kind[i].prefix))
>> +                     return ref_kind[i].kind;
>> +     }
>> +
>> +     return FILTER_REFS_OTHERS;
>> +}
>> +
>>  /*
>>   * A call-back given to for_each_ref().  Filter refs and keep them for
>>   * later object processing.
>> @@ -1133,6 +1163,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>>       struct ref_filter *filter = ref_cbdata->filter;
>>       struct ref_array_item *ref;
>>       struct commit *commit = NULL;
>> +     unsigned int kind;
>>
>>       if (flag & REF_BAD_NAME) {
>>               warning("ignoring ref with broken name %s", refname);
>> @@ -1144,6 +1175,10 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>>               return 0;
>>       }
>>
>> +     kind = filter_ref_kind(filter, refname);
>> +     if (!(kind & filter->kind))
>> +             return 0;
>> +
>
> When filter->kind is set to some single-bit thing (e.g.
> FILTER_REFS_BRANCHES) by the caller of ref_filter_handler(), then
> this call of filter_ref_kind() will just parrot that without even
> looking at refname.  And then the if statement says "yes, they have
> common bit(s)".  Even when refname is "refs/tags/v1.0" or "HEAD".
>

This happens for branches remotes and tags, and this is intentional so as to skip the uncessary checking to be done, if you see filter_refs() if it's called with a single bit (branches, tags or remotes) then it calls for_each_reftype_fullpath() with the corresponding path, which would only give us the refs which we needed hence checking them is not really needed. But the call to filter_ref_kind() would just give us the kind of the ref.

> And if this code knows that "refs/tags/v1.0" or "HEAD" will never
> come when filter->kind is exactly FILTER_REFS_BRANCHES, then I do
> not see the point of calling filter_ref_kind().
>

The point is to have a common function which returns the current ref kind, Even if we know that the current ref kind is the same as the filter->kind.

> I am not sure what this is trying to do.  Can you clarify the
> thinking behind this as a comment to filter_ref_kind()?
I should added a comment here, I will do so now.
Show 113 quoted lines
>
>> @@ -1175,6 +1210,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>>
>>       REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
>>       ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
>> +     ref->kind = kind;
>>       return 0;
>>  }
>>
>> @@ -1251,16 +1287,29 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
>>  {
>>       struct ref_filter_cbdata ref_cbdata;
>>       int ret = 0;
>> +     unsigned int broken = 0;
>>
>>       ref_cbdata.array = array;
>>       ref_cbdata.filter = filter;
>>
>>       /*  Simple per-ref filtering */
>> -     if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
>> -             ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
>> -     else if (type & FILTER_REFS_ALL)
>> -             ret = for_each_ref(ref_filter_handler, &ref_cbdata);
>> -     else if (type)
>> +     if (type & FILTER_REFS_INCLUDE_BROKEN) {
>> +             type &= ~FILTER_REFS_INCLUDE_BROKEN;
>> +             broken = 1;
>> +     }
>> +
>> +     filter->kind = type;
>> +     if (type == FILTER_REFS_BRANCHES)
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
>> +     else if (type == FILTER_REFS_REMOTES)
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
>> +     else if (type == FILTER_REFS_TAGS)
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
>> +     else if (type & FILTER_REFS_ALL) {
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>> +             if (type & FILTER_REFS_DETACHED_HEAD)
>> +                     head_ref(ref_filter_handler, &ref_cbdata);
>> +     } else
>>               die("filter_refs: invalid type");
>>
>>       /*  Filters that need revision walking */
>> diff --git a/ref-filter.h b/ref-filter.h
>> index 45026d0..99f081b 100644
>> --- a/ref-filter.h
>> +++ b/ref-filter.h
>> @@ -13,8 +13,14 @@
>>  #define QUOTE_PYTHON 4
>>  #define QUOTE_TCL 8
>>
>> -#define FILTER_REFS_INCLUDE_BROKEN 0x1
>> -#define FILTER_REFS_ALL 0x2
>> +#define FILTER_REFS_INCLUDE_BROKEN 0x0001
>> +#define FILTER_REFS_TAGS           0x0002
>> +#define FILTER_REFS_BRANCHES       0x0004
>> +#define FILTER_REFS_REMOTES        0x0008
>> +#define FILTER_REFS_OTHERS         0x0010
>> +#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
>> +                                 FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
>> +#define FILTER_REFS_DETACHED_HEAD  0x0020
>>
>>  struct atom_value;
>>
>> @@ -27,6 +33,7 @@ struct ref_sorting {
>>  struct ref_array_item {
>>       unsigned char objectname[20];
>>       int flag;
>> +     unsigned int kind;
>>       const char *symref;
>>       struct commit *commit;
>>       struct atom_value *value;
>> @@ -51,6 +58,7 @@ struct ref_filter {
>>       struct commit *merge_commit;
>>
>>       unsigned int with_commit_tag_algo : 1;
>> +     unsigned int kind;
>>  };
>>
>>  struct ref_filter_cbdata {
>> diff --git a/refs.c b/refs.c
>> index 4e15f60..3266617 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -2150,6 +2150,15 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
>>                              strlen(git_replace_ref_base), 0, cb_data);
>>  }
>>
>> +int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)
>> +{
>> +     unsigned int flag = 0;
>> +
>> +     if (broken)
>> +             flag = DO_FOR_EACH_INCLUDE_BROKEN;
>> +     return do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);
>> +}
>> +
>>  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
>>  {
>>       struct strbuf buf = STRBUF_INIT;
>> diff --git a/refs.h b/refs.h
>> index e9a5f32..6e913ee 100644
>> --- a/refs.h
>> +++ b/refs.h
>> @@ -179,6 +179,7 @@ extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
>>  extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
>>  extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
>>  extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
>> +extern int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data);
>>
>>  extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
>>  extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
Thanks for the review.
-- 
Regards,
Karthik Nayak
Michael Haggerty· Aug 26, 2015, 16:10 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

Comments inline.
On 08/22/2015 05:39 AM, Karthik Nayak wrote:
Show 49 quoted lines
> From: Karthik Nayak <karthik.188@gmail.com>
> 
> Add a function called 'for_each_reftype_fullpath()' to refs.{c,h}
> which iterates through each ref for the given path without trimming
> the path and also accounting for broken refs, if mentioned.
> 
> Add 'filter_ref_kind()' in ref-filter.c to check the kind of ref being
> handled and return the kind to 'ref_filter_handler()', where we
> discard refs which we do not need and assign the kind to needed refs.
> 
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
> ---
>  ref-filter.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
>  ref-filter.h | 12 ++++++++++--
>  refs.c       |  9 +++++++++
>  refs.h       |  1 +
>  4 files changed, 74 insertions(+), 7 deletions(-)
> 
> diff --git a/ref-filter.c b/ref-filter.c
> index ffec10a..d5fae1a 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -1123,6 +1123,36 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
>  	return ref;
>  }
>  
> +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
> +{
> +	unsigned int i;
> +
> +	static struct {
> +		const char *prefix;
> +		unsigned int kind;
> +	} ref_kind[] = {
> +		{ "refs/heads/" , FILTER_REFS_BRANCHES },
> +		{ "refs/remotes/" , FILTER_REFS_REMOTES },
> +		{ "refs/tags/", FILTER_REFS_TAGS}
> +	};
> +
> +	if (filter->kind == FILTER_REFS_BRANCHES)
> +		return FILTER_REFS_BRANCHES;
> +	else if (filter->kind == FILTER_REFS_REMOTES)
> +		return FILTER_REFS_REMOTES;
> +	else if (filter->kind == FILTER_REFS_TAGS)
> +		return FILTER_REFS_TAGS;
> +	else if (!strcmp(refname, "HEAD"))
> +		return FILTER_REFS_DETACHED_HEAD;
I think this would be clearer written like so:
    if (filter->kind == FILTER_REFS_BRANCHES ||
        filter->kind == FILTER_REFS_REMOTES ||
        filter->kind == FILTER_REFS_TAGS)
            return filter->kind;
    if (!strcmp(refname, "HEAD"))
            return FILTER_REFS_DETACHED_HEAD;

Or, even better, if you can set filter->kind to zero if it is not one of these constants, then you could simplify this to

    if (filter->kind)
            return filter->kind;
    if (!strcmp(refname, "HEAD"))
            return FILTER_REFS_DETACHED_HEAD;
Show 58 quoted lines
> +
> +	for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
> +		if (starts_with(refname, ref_kind[i].prefix))
> +			return ref_kind[i].kind;
> +	}
> +
> +	return FILTER_REFS_OTHERS;
> +}
> +
>  /*
>   * A call-back given to for_each_ref().  Filter refs and keep them for
>   * later object processing.
> @@ -1133,6 +1163,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>  	struct ref_filter *filter = ref_cbdata->filter;
>  	struct ref_array_item *ref;
>  	struct commit *commit = NULL;
> +	unsigned int kind;
>  
>  	if (flag & REF_BAD_NAME) {
>  		warning("ignoring ref with broken name %s", refname);
> @@ -1144,6 +1175,10 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>  		return 0;
>  	}
>  
> +	kind = filter_ref_kind(filter, refname);
> +	if (!(kind & filter->kind))
> +		return 0;
> +
>  	if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
>  		return 0;
>  
> @@ -1175,6 +1210,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>  
>  	REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
>  	ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
> +	ref->kind = kind;
>  	return 0;
>  }
>  
> @@ -1251,16 +1287,29 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
>  {
>  	struct ref_filter_cbdata ref_cbdata;
>  	int ret = 0;
> +	unsigned int broken = 0;
>  
>  	ref_cbdata.array = array;
>  	ref_cbdata.filter = filter;
>  
>  	/*  Simple per-ref filtering */
> -	if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
> -		ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
> -	else if (type & FILTER_REFS_ALL)
> -		ret = for_each_ref(ref_filter_handler, &ref_cbdata);
> -	else if (type)
> +	if (type & FILTER_REFS_INCLUDE_BROKEN) {
> +		type &= ~FILTER_REFS_INCLUDE_BROKEN;
> +		broken = 1;
> +	}

I wouldn't mask out the FILTER_REFS_INCLUDE_BROKEN bit here. Instead I would write

> +
> +	filter->kind = type;
as
        filter->kind = type & FILTER_REFS_KIND_MASK;

where FILTER_REFS_KIND_MASK is the OR of all of the kind bits. The advantage is that this approach would remain correct if more bits are added to type. Then in the following if statements, test filter->kind instead of type.

Show 10 quoted lines
> +	if (type == FILTER_REFS_BRANCHES)
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
> +	else if (type == FILTER_REFS_REMOTES)
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
> +	else if (type == FILTER_REFS_TAGS)
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
> +	else if (type & FILTER_REFS_ALL) {
> +		ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
> +		if (type & FILTER_REFS_DETACHED_HEAD)
> +			head_ref(ref_filter_handler, &ref_cbdata);

The usual promise of the for_each_ref functions is that they stop iterating if the function ever returns a nonzero value. So the above should be

                if (! ret && (type & FILTER_REFS_DETACHED_HEAD))
                        ret = head_ref(ref_filter_handler, &ref_cbdata);

Also, these functions usually iterate in lexicographic order, so I think you should process HEAD before the others.

But there's another problem here. It seems like FILTER_REFS_DETACHED_HEAD is only processed if (type & FILTER_REFS_ALL) is nonzero. But shouldn't it be allowed to process *only* HEAD?

So, finally, I think this code should look like
        else if (!filter->kind)
                die("filter_refs: invalid type");
        else {
                if (filter->kind & FILTER_REFS_DETACHED_HEAD)
                        ret = head_ref(ref_filter_handler, &ref_cbdata);
                if (! ret && (filter->kind & FILTER_REFS_ALL))
                        ret =
for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
        }
Show 22 quoted lines
> +	} else
>  		die("filter_refs: invalid type");
>  
>  	/*  Filters that need revision walking */
> diff --git a/ref-filter.h b/ref-filter.h
> index 45026d0..99f081b 100644
> --- a/ref-filter.h
> +++ b/ref-filter.h
> @@ -13,8 +13,14 @@
>  #define QUOTE_PYTHON 4
>  #define QUOTE_TCL 8
>  
> -#define FILTER_REFS_INCLUDE_BROKEN 0x1
> -#define FILTER_REFS_ALL 0x2
> +#define FILTER_REFS_INCLUDE_BROKEN 0x0001
> +#define FILTER_REFS_TAGS           0x0002
> +#define FILTER_REFS_BRANCHES       0x0004
> +#define FILTER_REFS_REMOTES        0x0008
> +#define FILTER_REFS_OTHERS         0x0010
> +#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
> +				    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
> +#define FILTER_REFS_DETACHED_HEAD  0x0020

Do you expect it to be useful to OR these bits together, or will (in practice) all callers want to iterate over one or the other type of reference, or all references? I ask because allowing an arbitrary bitmask complicates the code a bit (otherwise filter_ref_kind() wouldn't be needed).

If there is code that wants to iterate over, say, branches *and* tags, then maybe it is an acceptable imposition for it to make two function calls,

    for_each_reftype_fullpath(fn, "refs/heads/", broken, cb_data) ||
    for_each_reftype_fullpath(fn, "refs/tags/", broken, cb_data);

which might even be a bit more efficient because it only has to iterate over those two namespaces rather than all references.

But the more flexible bitmask code is not a lot of extra overhead, so if you think it will have a use case then it is fine to keep this interface.

Show 48 quoted lines
>  
>  struct atom_value;
>  
> @@ -27,6 +33,7 @@ struct ref_sorting {
>  struct ref_array_item {
>  	unsigned char objectname[20];
>  	int flag;
> +	unsigned int kind;
>  	const char *symref;
>  	struct commit *commit;
>  	struct atom_value *value;
> @@ -51,6 +58,7 @@ struct ref_filter {
>  	struct commit *merge_commit;
>  
>  	unsigned int with_commit_tag_algo : 1;
> +	unsigned int kind;
>  };
>  
>  struct ref_filter_cbdata {
> diff --git a/refs.c b/refs.c
> index 4e15f60..3266617 100644
> --- a/refs.c
> +++ b/refs.c
> @@ -2150,6 +2150,15 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
>  			       strlen(git_replace_ref_base), 0, cb_data);
>  }
>  
> +int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)
> +{
> +	unsigned int flag = 0;
> +
> +	if (broken)
> +		flag = DO_FOR_EACH_INCLUDE_BROKEN;
> +	return do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);
> +}
> +
>  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
>  {
>  	struct strbuf buf = STRBUF_INIT;
> diff --git a/refs.h b/refs.h
> index e9a5f32..6e913ee 100644
> --- a/refs.h
> +++ b/refs.h
> @@ -179,6 +179,7 @@ extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
>  extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
>  extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
>  extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
> +extern int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data);

This function is most like for_each_ref_in(prefix, fn, cb_data). Therefore, I suggest that you rename the "type" parameter" to "prefix", maybe reorder its arguments, and maybe rename it (to for_each_fullref_in()?) for consistency, and maybe put its declaration next to that function's. (I see that the argument orders among these functions are already pretty inconsistent, but it seems best to be consistent with the function that is most similar to it.)

I think the "type"/"prefix" argument can be "const char *".
>  
>  extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
>  extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
> 
Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
Karthik Nayak· Aug 27, 2015, 12:42 UTC · re: Michael Haggerty · lore

Re: [PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

On Wed, Aug 26, 2015 at 9:40 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
Show 62 quoted lines
> Comments inline.
>
> On 08/22/2015 05:39 AM, Karthik Nayak wrote:
>> From: Karthik Nayak <karthik.188@gmail.com>
>>
>> Add a function called 'for_each_reftype_fullpath()' to refs.{c,h}
>> which iterates through each ref for the given path without trimming
>> the path and also accounting for broken refs, if mentioned.
>>
>> Add 'filter_ref_kind()' in ref-filter.c to check the kind of ref being
>> handled and return the kind to 'ref_filter_handler()', where we
>> discard refs which we do not need and assign the kind to needed refs.
>>
>> Mentored-by: Christian Couder <christian.couder@gmail.com>
>> Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
>> ---
>>  ref-filter.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
>>  ref-filter.h | 12 ++++++++++--
>>  refs.c       |  9 +++++++++
>>  refs.h       |  1 +
>>  4 files changed, 74 insertions(+), 7 deletions(-)
>>
>> diff --git a/ref-filter.c b/ref-filter.c
>> index ffec10a..d5fae1a 100644
>> --- a/ref-filter.c
>> +++ b/ref-filter.c
>> @@ -1123,6 +1123,36 @@ static struct ref_array_item *new_ref_array_item(const char *refname,
>>       return ref;
>>  }
>>
>> +static int filter_ref_kind(struct ref_filter *filter, const char *refname)
>> +{
>> +     unsigned int i;
>> +
>> +     static struct {
>> +             const char *prefix;
>> +             unsigned int kind;
>> +     } ref_kind[] = {
>> +             { "refs/heads/" , FILTER_REFS_BRANCHES },
>> +             { "refs/remotes/" , FILTER_REFS_REMOTES },
>> +             { "refs/tags/", FILTER_REFS_TAGS}
>> +     };
>> +
>> +     if (filter->kind == FILTER_REFS_BRANCHES)
>> +             return FILTER_REFS_BRANCHES;
>> +     else if (filter->kind == FILTER_REFS_REMOTES)
>> +             return FILTER_REFS_REMOTES;
>> +     else if (filter->kind == FILTER_REFS_TAGS)
>> +             return FILTER_REFS_TAGS;
>> +     else if (!strcmp(refname, "HEAD"))
>> +             return FILTER_REFS_DETACHED_HEAD;
>
> I think this would be clearer written like so:
>
>     if (filter->kind == FILTER_REFS_BRANCHES ||
>         filter->kind == FILTER_REFS_REMOTES ||
>         filter->kind == FILTER_REFS_TAGS)
>             return filter->kind;
>     if (!strcmp(refname, "HEAD"))
>             return FILTER_REFS_DETACHED_HEAD;
>
This looks good.
Show 8 quoted lines
> Or, even better, if you can set filter->kind to zero if it is not one of
> these constants, then you could simplify this to
>
>     if (filter->kind)
>             return filter->kind;
>     if (!strcmp(refname, "HEAD"))
>             return FILTER_REFS_DETACHED_HEAD;
>

But then that would mean we would loose the ability of having each ref_array_item know its kind, as we return the kind and set it in ref_filter_handler().

This would work in the current scenario. This is particularly needed in the future for branch.c using ref-filter where we might have to append "remotes/" for printing remotes via "git branch - a".

Show 74 quoted lines
>> +
>> +     for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
>> +             if (starts_with(refname, ref_kind[i].prefix))
>> +                     return ref_kind[i].kind;
>> +     }
>> +
>> +     return FILTER_REFS_OTHERS;
>> +}
>> +
>>  /*
>>   * A call-back given to for_each_ref().  Filter refs and keep them for
>>   * later object processing.
>> @@ -1133,6 +1163,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>>       struct ref_filter *filter = ref_cbdata->filter;
>>       struct ref_array_item *ref;
>>       struct commit *commit = NULL;
>> +     unsigned int kind;
>>
>>       if (flag & REF_BAD_NAME) {
>>               warning("ignoring ref with broken name %s", refname);
>> @@ -1144,6 +1175,10 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>>               return 0;
>>       }
>>
>> +     kind = filter_ref_kind(filter, refname);
>> +     if (!(kind & filter->kind))
>> +             return 0;
>> +
>>       if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
>>               return 0;
>>
>> @@ -1175,6 +1210,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
>>
>>       REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
>>       ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
>> +     ref->kind = kind;
>>       return 0;
>>  }
>>
>> @@ -1251,16 +1287,29 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
>>  {
>>       struct ref_filter_cbdata ref_cbdata;
>>       int ret = 0;
>> +     unsigned int broken = 0;
>>
>>       ref_cbdata.array = array;
>>       ref_cbdata.filter = filter;
>>
>>       /*  Simple per-ref filtering */
>> -     if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
>> -             ret = for_each_rawref(ref_filter_handler, &ref_cbdata);
>> -     else if (type & FILTER_REFS_ALL)
>> -             ret = for_each_ref(ref_filter_handler, &ref_cbdata);
>> -     else if (type)
>> +     if (type & FILTER_REFS_INCLUDE_BROKEN) {
>> +             type &= ~FILTER_REFS_INCLUDE_BROKEN;
>> +             broken = 1;
>> +     }
>
> I wouldn't mask out the FILTER_REFS_INCLUDE_BROKEN bit here. Instead I
> would write
>
>> +
>> +     filter->kind = type;
>
> as
>
>         filter->kind = type & FILTER_REFS_KIND_MASK;
>
> where FILTER_REFS_KIND_MASK is the OR of all of the kind bits. The
> advantage is that this approach would remain correct if more bits are
> added to type. Then in the following if statements, test filter->kind
> instead of type.
>
Thats brilliant.
Show 20 quoted lines
>> +     if (type == FILTER_REFS_BRANCHES)
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
>> +     else if (type == FILTER_REFS_REMOTES)
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
>> +     else if (type == FILTER_REFS_TAGS)
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
>> +     else if (type & FILTER_REFS_ALL) {
>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>> +             if (type & FILTER_REFS_DETACHED_HEAD)
>> +                     head_ref(ref_filter_handler, &ref_cbdata);
>
> The usual promise of the for_each_ref functions is that they stop
> iterating if the function ever returns a nonzero value. So the above
> should be
>
>                 if (! ret && (type & FILTER_REFS_DETACHED_HEAD))
>                         ret = head_ref(ref_filter_handler, &ref_cbdata);
>
> Also, these functions usually iterate in lexicographic order, so I think
> you should process HEAD before the others.

This is done on purpose, cause we need to print the HEAD ref separately so we print the last ref_array_item in the ref_array, free that memory and sort and print the rest, hence HEAD ref is attached to the last.

Show 17 quoted lines
>
> But there's another problem here. It seems like
> FILTER_REFS_DETACHED_HEAD is only processed if (type & FILTER_REFS_ALL)
> is nonzero. But shouldn't it be allowed to process *only* HEAD?
>
> So, finally, I think this code should look like
>
>         else if (!filter->kind)
>                 die("filter_refs: invalid type");
>         else {
>                 if (filter->kind & FILTER_REFS_DETACHED_HEAD)
>                         ret = head_ref(ref_filter_handler, &ref_cbdata);
>                 if (! ret && (filter->kind & FILTER_REFS_ALL))
>                         ret =
> for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>         }
>
So finally something like this perhaps
    if (!filter->kind)
        die("filter_refs: invalid type");
    else {
        if (filter->kind == FILTER_REFS_BRANCHES)
            ret = for_each_reftype_fullpath(ref_filter_handler,
"refs/heads/", broken, &ref_cbdata);
        else if (filter->kind == FILTER_REFS_REMOTES)
            ret = for_each_reftype_fullpath(ref_filter_handler,
"refs/remotes/", broken, &ref_cbdata);
        else if (filter->kind == FILTER_REFS_TAGS)
            ret = for_each_reftype_fullpath(ref_filter_handler,
"refs/tags/", broken, &ref_cbdata);
        else if (filter->kind & FILTER_REFS_ALL)
            ret = for_each_reftype_fullpath(ref_filter_handler, "",
broken, &ref_cbdata);
        if (filter->kind & FILTER_REFS_DETACHED_HEAD)
            head_ref(ref_filter_handler, &ref_cbdata);
    }
Show 41 quoted lines
>> +     } else
>>               die("filter_refs: invalid type");
>>
>>       /*  Filters that need revision walking */
>> diff --git a/ref-filter.h b/ref-filter.h
>> index 45026d0..99f081b 100644
>> --- a/ref-filter.h
>> +++ b/ref-filter.h
>> @@ -13,8 +13,14 @@
>>  #define QUOTE_PYTHON 4
>>  #define QUOTE_TCL 8
>>
>> -#define FILTER_REFS_INCLUDE_BROKEN 0x1
>> -#define FILTER_REFS_ALL 0x2
>> +#define FILTER_REFS_INCLUDE_BROKEN 0x0001
>> +#define FILTER_REFS_TAGS           0x0002
>> +#define FILTER_REFS_BRANCHES       0x0004
>> +#define FILTER_REFS_REMOTES        0x0008
>> +#define FILTER_REFS_OTHERS         0x0010
>> +#define FILTER_REFS_ALL            (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \
>> +                                 FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)
>> +#define FILTER_REFS_DETACHED_HEAD  0x0020
>
> Do you expect it to be useful to OR these bits together, or will (in
> practice) all callers want to iterate over one or the other type of
> reference, or all references? I ask because allowing an arbitrary
> bitmask complicates the code a bit (otherwise filter_ref_kind() wouldn't
> be needed).
>
> If there is code that wants to iterate over, say, branches *and* tags,
> then maybe it is an acceptable imposition for it to make two function calls,
>
>     for_each_reftype_fullpath(fn, "refs/heads/", broken, cb_data) ||
>     for_each_reftype_fullpath(fn, "refs/tags/", broken, cb_data);
>
> which might even be a bit more efficient because it only has to iterate
> over those two namespaces rather than all references.
>
> But the more flexible bitmask code is not a lot of extra overhead, so if
> you think it will have a use case then it is fine to keep this interface.
>

Yeah, 'git branch -l' has '-a' option which iterates over remotes, branches and also the head ref. Hence the option. Thats also why if only a single type is needed we only iterate through those refs.

Show 59 quoted lines
>>
>>  struct atom_value;
>>
>> @@ -27,6 +33,7 @@ struct ref_sorting {
>>  struct ref_array_item {
>>       unsigned char objectname[20];
>>       int flag;
>> +     unsigned int kind;
>>       const char *symref;
>>       struct commit *commit;
>>       struct atom_value *value;
>> @@ -51,6 +58,7 @@ struct ref_filter {
>>       struct commit *merge_commit;
>>
>>       unsigned int with_commit_tag_algo : 1;
>> +     unsigned int kind;
>>  };
>>
>>  struct ref_filter_cbdata {
>> diff --git a/refs.c b/refs.c
>> index 4e15f60..3266617 100644
>> --- a/refs.c
>> +++ b/refs.c
>> @@ -2150,6 +2150,15 @@ int for_each_replace_ref(each_ref_fn fn, void *cb_data)
>>                              strlen(git_replace_ref_base), 0, cb_data);
>>  }
>>
>> +int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data)
>> +{
>> +     unsigned int flag = 0;
>> +
>> +     if (broken)
>> +             flag = DO_FOR_EACH_INCLUDE_BROKEN;
>> +     return do_for_each_ref(&ref_cache, type, fn, 0, flag, cb_data);
>> +}
>> +
>>  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
>>  {
>>       struct strbuf buf = STRBUF_INIT;
>> diff --git a/refs.h b/refs.h
>> index e9a5f32..6e913ee 100644
>> --- a/refs.h
>> +++ b/refs.h
>> @@ -179,6 +179,7 @@ extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
>>  extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
>>  extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
>>  extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
>> +extern int for_each_reftype_fullpath(each_ref_fn fn, char *type, unsigned int broken, void *cb_data);
>
> This function is most like for_each_ref_in(prefix, fn, cb_data).
> Therefore, I suggest that you rename the "type" parameter" to "prefix",
> maybe reorder its arguments, and maybe rename it (to
> for_each_fullref_in()?) for consistency, and maybe put its declaration
> next to that function's. (I see that the argument orders among these
> functions are already pretty inconsistent, but it seems best to be
> consistent with the function that is most similar to it.)
>
> I think the "type"/"prefix" argument can be "const char *".
>
Agree with everything said here, will do.
Show 11 quoted lines
>>
>>  extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
>>  extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
>>
>
> Michael
>
> --
> Michael Haggerty
> mhagger@alum.mit.edu
>
Thanks for the review.
-- 
Regards,
Karthik Nayak
Michael Haggerty· Aug 27, 2015, 15:24 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

On 08/27/2015 02:42 PM, Karthik Nayak wrote:
Show 27 quoted lines
> On Wed, Aug 26, 2015 at 9:40 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
>> On 08/22/2015 05:39 AM, Karthik Nayak wrote:
>>> [...]
>>> +     if (type == FILTER_REFS_BRANCHES)
>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
>>> +     else if (type == FILTER_REFS_REMOTES)
>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
>>> +     else if (type == FILTER_REFS_TAGS)
>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
>>> +     else if (type & FILTER_REFS_ALL) {
>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>>> +             if (type & FILTER_REFS_DETACHED_HEAD)
>>> +                     head_ref(ref_filter_handler, &ref_cbdata);
>>
>> The usual promise of the for_each_ref functions is that they stop
>> iterating if the function ever returns a nonzero value. So the above
>> should be
>>
>>                 if (! ret && (type & FILTER_REFS_DETACHED_HEAD))
>>                         ret = head_ref(ref_filter_handler, &ref_cbdata);
>>
>> Also, these functions usually iterate in lexicographic order, so I think
>> you should process HEAD before the others.
> 
> This is done on purpose, cause we need to print the HEAD ref separately
> so we print the last ref_array_item in the ref_array, free that memory and
> sort and print the rest, hence HEAD ref is attached to the last.

Without having looked at the other patches, this makes me wonder whether it makes sense to store HEAD in the ref_array at all or whether it should be handled separately.

Show 37 quoted lines
>> But there's another problem here. It seems like
>> FILTER_REFS_DETACHED_HEAD is only processed if (type & FILTER_REFS_ALL)
>> is nonzero. But shouldn't it be allowed to process *only* HEAD?
>>
>> So, finally, I think this code should look like
>>
>>         else if (!filter->kind)
>>                 die("filter_refs: invalid type");
>>         else {
>>                 if (filter->kind & FILTER_REFS_DETACHED_HEAD)
>>                         ret = head_ref(ref_filter_handler, &ref_cbdata);
>>                 if (! ret && (filter->kind & FILTER_REFS_ALL))
>>                         ret =
>> for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>>         }
>>
> 
> So finally something like this perhaps
> 
>     if (!filter->kind)
>         die("filter_refs: invalid type");
>     else {
>         if (filter->kind == FILTER_REFS_BRANCHES)
>             ret = for_each_reftype_fullpath(ref_filter_handler,
> "refs/heads/", broken, &ref_cbdata);
>         else if (filter->kind == FILTER_REFS_REMOTES)
>             ret = for_each_reftype_fullpath(ref_filter_handler,
> "refs/remotes/", broken, &ref_cbdata);
>         else if (filter->kind == FILTER_REFS_TAGS)
>             ret = for_each_reftype_fullpath(ref_filter_handler,
> "refs/tags/", broken, &ref_cbdata);
>         else if (filter->kind & FILTER_REFS_ALL)
>             ret = for_each_reftype_fullpath(ref_filter_handler, "",
> broken, &ref_cbdata);
>         if (filter->kind & FILTER_REFS_DETACHED_HEAD)
>             head_ref(ref_filter_handler, &ref_cbdata);
>     }
Yes, but the last test should be
        if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))

unless you have a reason not to follow the usual convention that a nonzero return value from fn means that the iteration should be aborted.

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
Karthik Nayak· Aug 27, 2015, 15:35 UTC · re: Michael Haggerty · lore

Re: [PATCH v13 05/12] ref-filter: add option to filter out tags, branches and remotes

On Thu, Aug 27, 2015 at 8:54 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
Show 33 quoted lines
> On 08/27/2015 02:42 PM, Karthik Nayak wrote:
>> On Wed, Aug 26, 2015 at 9:40 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
>>> On 08/22/2015 05:39 AM, Karthik Nayak wrote:
>>>> [...]
>>>> +     if (type == FILTER_REFS_BRANCHES)
>>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/heads/", broken, &ref_cbdata);
>>>> +     else if (type == FILTER_REFS_REMOTES)
>>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/remotes/", broken, &ref_cbdata);
>>>> +     else if (type == FILTER_REFS_TAGS)
>>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "refs/tags/", broken, &ref_cbdata);
>>>> +     else if (type & FILTER_REFS_ALL) {
>>>> +             ret = for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>>>> +             if (type & FILTER_REFS_DETACHED_HEAD)
>>>> +                     head_ref(ref_filter_handler, &ref_cbdata);
>>>
>>> The usual promise of the for_each_ref functions is that they stop
>>> iterating if the function ever returns a nonzero value. So the above
>>> should be
>>>
>>>                 if (! ret && (type & FILTER_REFS_DETACHED_HEAD))
>>>                         ret = head_ref(ref_filter_handler, &ref_cbdata);
>>>
>>> Also, these functions usually iterate in lexicographic order, so I think
>>> you should process HEAD before the others.
>>
>> This is done on purpose, cause we need to print the HEAD ref separately
>> so we print the last ref_array_item in the ref_array, free that memory and
>> sort and print the rest, hence HEAD ref is attached to the last.
>
> Without having looked at the other patches, this makes me wonder whether
> it makes sense to store HEAD in the ref_array at all or whether it
> should be handled separately.
>

Well then we'd need another ref_array just for that, that also could be an option. But apart from printing it first, everything else is the same for all the refs.

Show 45 quoted lines
>>> But there's another problem here. It seems like
>>> FILTER_REFS_DETACHED_HEAD is only processed if (type & FILTER_REFS_ALL)
>>> is nonzero. But shouldn't it be allowed to process *only* HEAD?
>>>
>>> So, finally, I think this code should look like
>>>
>>>         else if (!filter->kind)
>>>                 die("filter_refs: invalid type");
>>>         else {
>>>                 if (filter->kind & FILTER_REFS_DETACHED_HEAD)
>>>                         ret = head_ref(ref_filter_handler, &ref_cbdata);
>>>                 if (! ret && (filter->kind & FILTER_REFS_ALL))
>>>                         ret =
>>> for_each_reftype_fullpath(ref_filter_handler, "", broken, &ref_cbdata);
>>>         }
>>>
>>
>> So finally something like this perhaps
>>
>>     if (!filter->kind)
>>         die("filter_refs: invalid type");
>>     else {
>>         if (filter->kind == FILTER_REFS_BRANCHES)
>>             ret = for_each_reftype_fullpath(ref_filter_handler,
>> "refs/heads/", broken, &ref_cbdata);
>>         else if (filter->kind == FILTER_REFS_REMOTES)
>>             ret = for_each_reftype_fullpath(ref_filter_handler,
>> "refs/remotes/", broken, &ref_cbdata);
>>         else if (filter->kind == FILTER_REFS_TAGS)
>>             ret = for_each_reftype_fullpath(ref_filter_handler,
>> "refs/tags/", broken, &ref_cbdata);
>>         else if (filter->kind & FILTER_REFS_ALL)
>>             ret = for_each_reftype_fullpath(ref_filter_handler, "",
>> broken, &ref_cbdata);
>>         if (filter->kind & FILTER_REFS_DETACHED_HEAD)
>>             head_ref(ref_filter_handler, &ref_cbdata);
>>     }
>
> Yes, but the last test should be
>
>         if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
>
> unless you have a reason not to follow the usual convention that a
> nonzero return value from fn means that the iteration should be aborted.
>
No, of course, I missed that while typing here.
-- 
Regards,
Karthik Nayak
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 06/12] ref-filter: support printing N lines from tag annotation

From: Karthik Nayak <karthik.188@gmail.com>

In 'tag.c' we can print N lines from the annotation of the tag using the '-n<num>' option. Copy code from 'tag.c' to 'ref-filter' and modify 'ref-filter' to support printing of N lines from the annotation of tags.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 builtin/for-each-ref.c |  2 +-
 builtin/tag.c          |  4 ++++
 ref-filter.c           | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-
 ref-filter.h           | 11 ++++++++---
 4 files changed, 63 insertions(+), 5 deletions(-)
Show changes to 4 files +63 −5

builtin/for-each-ref.c, builtin/tag.c, ref-filter.c, ref-filter.h

diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 40f343b..e4a4f8a 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -74,7 +74,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 	if (!maxcount || array.nr < maxcount)
 		maxcount = array.nr;
 	for (i = 0; i < maxcount; i++)
-		show_ref_array_item(array.items[i], format, quote_style);
+		show_ref_array_item(array.items[i], format, quote_style, 0);
 	ref_array_clear(&array);
 	return 0;
 }
diff --git a/builtin/tag.c b/builtin/tag.c
index 471d6b1..0fc7557 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -185,6 +185,10 @@ static enum contains_result contains(struct commit *candidate,
 	return contains_test(candidate, want);
 }
 
+/*
+ * Currently duplicated in ref-filter, will eventually be removed as
+ * we port tag.c to use ref-filter APIs.
+ */
 static void show_tag_lines(const struct object_id *oid, int lines)
 {
 	int i;
diff --git a/ref-filter.c b/ref-filter.c
index d5fae1a..515147b 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1431,7 +1431,51 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting
 	}
 }
 
-void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
+/*
+ * If 'lines' is greater than 0, print that many lines from the given
+ * object_id 'oid'.
+ */
+static void show_tag_lines(const struct object_id *oid, int lines)
+{
+	int i;
+	unsigned long size;
+	enum object_type type;
+	char *buf, *sp, *eol;
+	size_t len;
+
+	buf = read_sha1_file(oid->hash, &type, &size);
+	if (!buf)
+		die_errno("unable to read object %s", oid_to_hex(oid));
+	if (type != OBJ_COMMIT && type != OBJ_TAG)
+		goto free_return;
+	if (!size)
+		die("an empty %s object %s?",
+		    typename(type), oid_to_hex(oid));
+
+	/* skip header */
+	sp = strstr(buf, "\n\n");
+	if (!sp)
+		goto free_return;
+
+	/* only take up to "lines" lines, and strip the signature from a tag */
+	if (type == OBJ_TAG)
+		size = parse_signature(buf, size);
+	for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
+		if (i)
+			printf("\n    ");
+		eol = memchr(sp, '\n', size - (sp - buf));
+		len = eol ? eol - sp : size - (sp - buf);
+		fwrite(sp, len, 1, stdout);
+		if (!eol)
+			break;
+		sp = eol + 1;
+	}
+free_return:
+	free(buf);
+}
+
+void show_ref_array_item(struct ref_array_item *info, const char *format,
+			 int quote_style, unsigned int lines)
 {
 	const char *cp, *sp, *ep;
 	struct strbuf *final_buf;
@@ -1478,6 +1522,11 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu
 	final_buf = &state.stack->output;
 	fwrite(final_buf->buf, 1, final_buf->len, stdout);
 	pop_stack_element(&state.stack);
+	if (lines > 0) {
+		struct object_id oid;
+		hashcpy(oid.hash, info->objectname);
+		show_tag_lines(&oid, lines);
+	}
 	putchar('\n');
 }
 
diff --git a/ref-filter.h b/ref-filter.h
index 99f081b..c599ea2 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -58,7 +58,8 @@ struct ref_filter {
 	struct commit *merge_commit;
 
 	unsigned int with_commit_tag_algo : 1;
-	unsigned int kind;
+	unsigned int kind,
+		lines;
 };
 
 struct ref_filter_cbdata {
@@ -90,8 +91,12 @@ int parse_ref_filter_atom(const char *atom, const char *ep);
 int verify_ref_format(const char *format);
 /*  Sort the given ref_array as per the ref_sorting provided */
 void ref_array_sort(struct ref_sorting *sort, struct ref_array *array);
-/*  Print the ref using the given format and quote_style */
-void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style);
+/*
+ * Print the ref using the given format and quote_style. If 'lines' > 0,
+ * print that many lines of the the given ref.
+ */
+void show_ref_array_item(struct ref_array_item *info, const char *format,
+			 int quote_style, unsigned int lines);
 /*  Callback function for parsing the sort option */
 int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset);
 /*  Default sort option based on refname */
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 07/12] ref-filter: add support to sort by version

From: Karthik Nayak <karthik.188@gmail.com>

Add support to sort by version using the "v:refname" and "version:refname" option. This is achieved by using the 'versioncmp()' function as the comparing function for qsort.

This option is included to support sorting by versions in `git tag -l` which will eventually be ported to use ref-filter APIs.

Add documentation and tests for the same.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 Documentation/git-for-each-ref.txt |  3 +++
 ref-filter.c                       | 15 ++++++++++-----
 ref-filter.h                       |  3 ++-
 t/t6302-for-each-ref-filter.sh     | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 51 insertions(+), 6 deletions(-)
Show changes to 4 files +51 −6

Documentation/git-for-each-ref.txt, ref-filter.c, ref-filter.h, t/t6302-for-each-ref-filter.sh

diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 943975d..06d468e 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -154,6 +154,9 @@ For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
 All other fields are used to sort in their byte-value order.
 
+There is also an option to sort by versions, this can be done by using
+the fieldname `version:refname` or its alias `v:refname`.
+
 In any case, a field name that refers to a field inapplicable to
 the object referred by the ref does not cause an error.  It
 returns an empty string instead.
diff --git a/ref-filter.c b/ref-filter.c
index 515147b..b4a7d72 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -11,6 +11,8 @@
 #include "ref-filter.h"
 #include "revision.h"
 #include "utf8.h"
+#include "git-compat-util.h"
+#include "version.h"
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -1327,19 +1329,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
 
 	get_ref_atom_value(a, s->atom, &va);
 	get_ref_atom_value(b, s->atom, &vb);
-	switch (cmp_type) {
-	case FIELD_STR:
+	if (s->version)
+		cmp = versioncmp(va->s, vb->s);
+	else if (cmp_type == FIELD_STR)
 		cmp = strcmp(va->s, vb->s);
-		break;
-	default:
+	else {
 		if (va->ul < vb->ul)
 			cmp = -1;
 		else if (va->ul == vb->ul)
 			cmp = 0;
 		else
 			cmp = 1;
-		break;
 	}
+
 	return (s->reverse) ? -cmp : cmp;
 }
 
@@ -1559,6 +1561,9 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
 		s->reverse = 1;
 		arg++;
 	}
+	if (skip_prefix(arg, "version:", &arg) ||
+	    skip_prefix(arg, "v:", &arg))
+		s->version = 1;
 	len = strlen(arg);
 	s->atom = parse_ref_filter_atom(arg, arg+len);
 	return 0;
diff --git a/ref-filter.h b/ref-filter.h
index c599ea2..5aa2f40 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -27,7 +27,8 @@ struct atom_value;
 struct ref_sorting {
 	struct ref_sorting *next;
 	int atom; /* index into used_atom array (internal) */
-	unsigned reverse : 1;
+	unsigned reverse : 1,
+		version : 1;
 };
 
 struct ref_array_item {
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 227992b..38c99c9 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -150,4 +150,40 @@ test_expect_success 'alignment with format quote' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setup for version sort' '
+	test_commit foo1.3 &&
+	test_commit foo1.6 &&
+	test_commit foo1.10
+'
+
+test_expect_success 'version sort' '
+	git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+	cat >expect <<-\EOF &&
+	foo1.3
+	foo1.6
+	foo1.10
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'version sort (shortened)' '
+	git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+	cat >expect <<-\EOF &&
+	foo1.3
+	foo1.6
+	foo1.10
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'reverse version sort' '
+	git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual &&
+	cat >expect <<-\EOF &&
+	foo1.10
+	foo1.6
+	foo1.3
+	EOF
+	test_cmp expect actual
+'
+
 test_done
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 08/12] ref-filter: add option to match literal pattern

From: Karthik Nayak <karthik.188@gmail.com>

Since 'ref-filter' only has an option to match path names add an option for plain fnmatch pattern-matching.

This is to support the pattern matching options which are used in `git tag -l` and `git branch -l` where we can match patterns like `git tag -l foo*` which would match all tags which has a "foo*" pattern.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 builtin/for-each-ref.c |  1 +
 ref-filter.c           | 40 +++++++++++++++++++++++++++++++++++++---
 ref-filter.h           |  3 ++-
 3 files changed, 40 insertions(+), 4 deletions(-)
Show changes to 3 files +40 −4

builtin/for-each-ref.c, ref-filter.c, ref-filter.h

diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index e4a4f8a..3ad6a64 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -68,6 +68,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 	git_config(git_default_config, NULL);
 
 	filter.name_patterns = argv;
+	filter.match_as_path = 1;
 	filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
 	ref_array_sort(sorting, &array);
 
diff --git a/ref-filter.c b/ref-filter.c
index b4a7d72..f8b8fb7 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1055,9 +1055,33 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)
 
 /*
  * Return 1 if the refname matches one of the patterns, otherwise 0.
+ * A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
+ * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref
+ * matches "refs/heads/mas*", too).
+ */
+static int match_pattern(const char **patterns, const char *refname)
+{
+	/*
+	 * When no '--format' option is given we need to skip the prefix
+	 * for matching refs of tags and branches.
+	 */
+	(void)(skip_prefix(refname, "refs/tags/", &refname) ||
+	       skip_prefix(refname, "refs/heads/", &refname) ||
+	       skip_prefix(refname, "refs/remotes/", &refname) ||
+	       skip_prefix(refname, "refs/", &refname));
+
+	for (; *patterns; patterns++) {
+		if (!wildmatch(*patterns, refname, 0, NULL))
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * Return 1 if the refname matches one of the patterns, otherwise 0.
  * A pattern can be path prefix (e.g. a refname "refs/heads/master"
- * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref
- * matches "refs/heads/m*",too).
+ * matches a pattern "refs/heads/" but not "refs/heads/m") or a
+ * wildcard (e.g. the same ref matches "refs/heads/m*", too).
  */
 static int match_name_as_path(const char **pattern, const char *refname)
 {
@@ -1078,6 +1102,16 @@ static int match_name_as_path(const char **pattern, const char *refname)
 	return 0;
 }
 
+/* Return 1 if the refname matches one of the patterns, otherwise 0. */
+static int filter_pattern_match(struct ref_filter *filter, const char *refname)
+{
+	if (!*filter->name_patterns)
+		return 1; /* No pattern always matches */
+	if (filter->match_as_path)
+		return match_name_as_path(filter->name_patterns, refname);
+	return match_pattern(filter->name_patterns, refname);
+}
+
 /*
  * Given a ref (sha1, refname), check if the ref belongs to the array
  * of sha1s. If the given ref is a tag, check if the given tag points
@@ -1181,7 +1215,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
 	if (!(kind & filter->kind))
 		return 0;
 
-	if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
+	if (!filter_pattern_match(filter, refname))
 		return 0;
 
 	if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
diff --git a/ref-filter.h b/ref-filter.h
index 5aa2f40..8241066 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -58,7 +58,8 @@ struct ref_filter {
 	} merge;
 	struct commit *merge_commit;
 
-	unsigned int with_commit_tag_algo : 1;
+	unsigned int with_commit_tag_algo : 1,
+		match_as_path : 1;
 	unsigned int kind,
 		lines;
 };
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 09/12] tag.c: use 'ref-filter' data structures

From: Karthik Nayak <karthik.188@gmail.com>

Make 'tag.c' use 'ref-filter' data structures and make changes to support the new data structures. This is a part of the process of porting 'tag.c' to use 'ref-filter' APIs.

This is a temporary step before porting 'tag.c' to use 'ref-filter' completely. As this is a temporary step, most of the code introduced here will be removed when 'tag.c' is ported over to use 'ref-filter' APIs.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 builtin/tag.c | 106 +++++++++++++++++++++++++++++++---------------------------
 1 file changed, 57 insertions(+), 49 deletions(-)
Show changes to builtin/tag.c +57 −49
diff --git a/builtin/tag.c b/builtin/tag.c
index 0fc7557..e96bae2 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -17,6 +17,7 @@
 #include "gpg-interface.h"
 #include "sha1-array.h"
 #include "column.h"
+#include "ref-filter.h"
 
 static const char * const git_tag_usage[] = {
 	N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
@@ -34,15 +35,6 @@ static const char * const git_tag_usage[] = {
 
 static int tag_sort;
 
-struct tag_filter {
-	const char **patterns;
-	int lines;
-	int sort;
-	struct string_list tags;
-	struct commit_list *with_commit;
-};
-
-static struct sha1_array points_at;
 static unsigned int colopts;
 
 static int match_pattern(const char **patterns, const char *ref)
@@ -61,19 +53,20 @@ static int match_pattern(const char **patterns, const char *ref)
  * removed as we port tag.c to use the ref-filter APIs.
  */
 static const unsigned char *match_points_at(const char *refname,
-					    const unsigned char *sha1)
+					    const unsigned char *sha1,
+					    struct sha1_array *points_at)
 {
 	const unsigned char *tagged_sha1 = NULL;
 	struct object *obj;
 
-	if (sha1_array_lookup(&points_at, sha1) >= 0)
+	if (sha1_array_lookup(points_at, sha1) >= 0)
 		return sha1;
 	obj = parse_object(sha1);
 	if (!obj)
 		die(_("malformed object at '%s'"), refname);
 	if (obj->type == OBJ_TAG)
 		tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
-	if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
+	if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
 		return tagged_sha1;
 	return NULL;
 }
@@ -228,12 +221,24 @@ free_return:
 	free(buf);
 }
 
+static void ref_array_append(struct ref_array *array, const char *refname)
+{
+	size_t len = strlen(refname);
+	struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
+	memcpy(ref->refname, refname, len);
+	ref->refname[len] = '\0';
+	REALLOC_ARRAY(array->items, array->nr + 1);
+	array->items[array->nr++] = ref;
+}
+
 static int show_reference(const char *refname, const struct object_id *oid,
 			  int flag, void *cb_data)
 {
-	struct tag_filter *filter = cb_data;
+	struct ref_filter_cbdata *data = cb_data;
+	struct ref_array *array = data->array;
+	struct ref_filter *filter = data->filter;
 
-	if (match_pattern(filter->patterns, refname)) {
+	if (match_pattern(filter->name_patterns, refname)) {
 		if (filter->with_commit) {
 			struct commit *commit;
 
@@ -244,12 +249,12 @@ static int show_reference(const char *refname, const struct object_id *oid,
 				return 0;
 		}
 
-		if (points_at.nr && !match_points_at(refname, oid->hash))
+		if (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at))
 			return 0;
 
 		if (!filter->lines) {
-			if (filter->sort)
-				string_list_append(&filter->tags, refname);
+			if (tag_sort)
+				ref_array_append(array, refname);
 			else
 				printf("%s\n", refname);
 			return 0;
@@ -264,36 +269,36 @@ static int show_reference(const char *refname, const struct object_id *oid,
 
 static int sort_by_version(const void *a_, const void *b_)
 {
-	const struct string_list_item *a = a_;
-	const struct string_list_item *b = b_;
-	return versioncmp(a->string, b->string);
+	const struct ref_array_item *a = *((struct ref_array_item **)a_);
+	const struct ref_array_item *b = *((struct ref_array_item **)b_);
+	return versioncmp(a->refname, b->refname);
 }
 
-static int list_tags(const char **patterns, int lines,
-		     struct commit_list *with_commit, int sort)
+static int list_tags(struct ref_filter *filter, int sort)
 {
-	struct tag_filter filter;
+	struct ref_array array;
+	struct ref_filter_cbdata data;
+
+	memset(&array, 0, sizeof(array));
+	data.array = &array;
+	data.filter = filter;
 
-	filter.patterns = patterns;
-	filter.lines = lines;
-	filter.sort = sort;
-	filter.with_commit = with_commit;
-	memset(&filter.tags, 0, sizeof(filter.tags));
-	filter.tags.strdup_strings = 1;
+	if (filter->lines == -1)
+		filter->lines = 0;
 
-	for_each_tag_ref(show_reference, (void *)&filter);
+	for_each_tag_ref(show_reference, &data);
 	if (sort) {
 		int i;
 		if ((sort & SORT_MASK) == VERCMP_SORT)
-			qsort(filter.tags.items, filter.tags.nr,
-			      sizeof(struct string_list_item), sort_by_version);
+			qsort(array.items, array.nr,
+			      sizeof(struct ref_array_item *), sort_by_version);
 		if (sort & REVERSE_SORT)
-			for (i = filter.tags.nr - 1; i >= 0; i--)
-				printf("%s\n", filter.tags.items[i].string);
+			for (i = array.nr - 1; i >= 0; i--)
+				printf("%s\n", array.items[i]->refname);
 		else
-			for (i = 0; i < filter.tags.nr; i++)
-				printf("%s\n", filter.tags.items[i].string);
-		string_list_clear(&filter.tags, 0);
+			for (i = 0; i < array.nr; i++)
+				printf("%s\n", array.items[i]->refname);
+		ref_array_clear(&array);
 	}
 	return 0;
 }
@@ -574,17 +579,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	const char *object_ref, *tag;
 	struct create_tag_options opt;
 	char *cleanup_arg = NULL;
-	int annotate = 0, force = 0, lines = -1;
 	int create_reflog = 0;
+	int annotate = 0, force = 0;
 	int cmdmode = 0;
 	const char *msgfile = NULL, *keyid = NULL;
 	struct msg_arg msg = { 0, STRBUF_INIT };
-	struct commit_list *with_commit = NULL;
 	struct ref_transaction *transaction;
 	struct strbuf err = STRBUF_INIT;
+	struct ref_filter filter;
 	struct option options[] = {
 		OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
-		{ OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+		{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
 				N_("print <n> lines of each tag message"),
 				PARSE_OPT_OPTARG, NULL, 1 },
 		OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
@@ -606,14 +611,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
 		OPT_GROUP(N_("Tag listing options")),
 		OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
-		OPT_CONTAINS(&with_commit, N_("print only tags that contain the commit")),
-		OPT_WITH(&with_commit, N_("print only tags that contain the commit")),
+		OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+		OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
 		{
 			OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
 			PARSE_OPT_NONEG, parse_opt_sort
 		},
 		{
-			OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"),
+			OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
 			N_("print only tags of the object"), 0, parse_opt_object_name
 		},
 		OPT_END()
@@ -622,6 +627,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	git_config(git_tag_config, NULL);
 
 	memset(&opt, 0, sizeof(opt));
+	memset(&filter, 0, sizeof(filter));
+	filter.lines = -1;
 
 	argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
 
@@ -638,7 +645,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 		usage_with_options(git_tag_usage, options);
 
 	finalize_colopts(&colopts, -1);
-	if (cmdmode == 'l' && lines != -1) {
+	if (cmdmode == 'l' && filter.lines != -1) {
 		if (explicitly_enable_column(colopts))
 			die(_("--column and -n are incompatible"));
 		colopts = 0;
@@ -651,18 +658,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 			copts.padding = 2;
 			run_column_filter(colopts, &copts);
 		}
-		if (lines != -1 && tag_sort)
+		if (filter.lines != -1 && tag_sort)
 			die(_("--sort and -n are incompatible"));
-		ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+		filter.name_patterns = argv;
+		ret = list_tags(&filter, tag_sort);
 		if (column_active(colopts))
 			stop_column_filter();
 		return ret;
 	}
-	if (lines != -1)
+	if (filter.lines != -1)
 		die(_("-n option is only allowed with -l."));
-	if (with_commit)
+	if (filter.with_commit)
 		die(_("--contains option is only allowed with -l."));
-	if (points_at.nr)
+	if (filter.points_at.nr)
 		die(_("--points-at option is only allowed with -l."));
 	if (cmdmode == 'd')
 		return for_each_tag_name(argv, delete_tag);
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 10/12] tag.c: use 'ref-filter' APIs

From: Karthik Nayak <karthik.188@gmail.com>

Make 'tag.c' use 'ref-filter' APIs for iterating through refs, sorting and printing of refs. This removes most of the code used in 'tag.c' replacing it with calls to the 'ref-filter' library.

Make 'tag.c' use the 'filter_refs()' function provided by 'ref-filter' to filter out tags based on the options set.

For printing tags we use 'show_ref_array_item()' function provided by 'ref-filter'.

We improve the sorting option provided by 'tag.c' by using the sorting options provided by 'ref-filter'. This causes the test 'invalid sort parameter on command line' in t7004 to fail, as 'ref-filter' throws an error for all sorting fields which are incorrect. The test is changed to reflect the same.

Modify documentation for the same.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 Documentation/git-tag.txt |  16 ++-
 builtin/tag.c             | 342 ++++++----------------------------------------
 t/t7004-tag.sh            |   8 +-
 3 files changed, 50 insertions(+), 316 deletions(-)
Show changes to 3 files +50 −315

Documentation/git-tag.txt, builtin/tag.c, t/t7004-tag.sh

diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 84f6496..3ac4a96 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -13,7 +13,7 @@ SYNOPSIS
 	<tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
-	[--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
+	[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -94,14 +94,16 @@ OPTIONS
 	using fnmatch(3)).  Multiple patterns may be given; if any of
 	them matches, the tag is shown.
 
---sort=<type>::
-	Sort in a specific order. Supported type is "refname"
-	(lexicographic order), "version:refname" or "v:refname" (tag
+--sort=<key>::
+	Sort based on the key given.  Prefix `-` to sort in
+	descending order of the value. You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key. Also supports "version:refname" or "v:refname" (tag
 	names are treated as versions). The "version:refname" sort
 	order can also be affected by the
-	"versionsort.prereleaseSuffix" configuration variable. Prepend
-	"-" to reverse sort order. When this option is not given, the
-	sort order defaults to the value configured for the 'tag.sort'
+	"versionsort.prereleaseSuffix" configuration variable.
+	The keys supported are the same as those in `git for-each-ref`.
+	Sort order defaults to the value configured for the 'tag.sort'
 	variable if it exists, or lexicographic order otherwise. See
 	linkgit:git-config[1].
 
diff --git a/builtin/tag.c b/builtin/tag.c
index e96bae2..501fc52 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -28,278 +28,32 @@ static const char * const git_tag_usage[] = {
 	NULL
 };
 
-#define STRCMP_SORT     0	/* must be zero */
-#define VERCMP_SORT     1
-#define SORT_MASK       0x7fff
-#define REVERSE_SORT    0x8000
-
-static int tag_sort;
-
 static unsigned int colopts;
 
-static int match_pattern(const char **patterns, const char *ref)
-{
-	/* no pattern means match everything */
-	if (!*patterns)
-		return 1;
-	for (; *patterns; patterns++)
-		if (!wildmatch(*patterns, ref, 0, NULL))
-			return 1;
-	return 0;
-}
-
-/*
- * This is currently duplicated in ref-filter.c, and will eventually be
- * removed as we port tag.c to use the ref-filter APIs.
- */
-static const unsigned char *match_points_at(const char *refname,
-					    const unsigned char *sha1,
-					    struct sha1_array *points_at)
-{
-	const unsigned char *tagged_sha1 = NULL;
-	struct object *obj;
-
-	if (sha1_array_lookup(points_at, sha1) >= 0)
-		return sha1;
-	obj = parse_object(sha1);
-	if (!obj)
-		die(_("malformed object at '%s'"), refname);
-	if (obj->type == OBJ_TAG)
-		tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
-	if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0)
-		return tagged_sha1;
-	return NULL;
-}
-
-static int in_commit_list(const struct commit_list *want, struct commit *c)
-{
-	for (; want; want = want->next)
-		if (!hashcmp(want->item->object.sha1, c->object.sha1))
-			return 1;
-	return 0;
-}
-
-/*
- * The entire code segment for supporting the --contains option has been
- * copied over to ref-filter.{c,h}. This will be deleted evetually when
- * we port tag.c to use ref-filter APIs.
- */
-enum contains_result {
-	CONTAINS_UNKNOWN = -1,
-	CONTAINS_NO = 0,
-	CONTAINS_YES = 1
-};
-
-/*
- * Test whether the candidate or one of its parents is contained in the list.
- * Do not recurse to find out, though, but return -1 if inconclusive.
- */
-static enum contains_result contains_test(struct commit *candidate,
-			    const struct commit_list *want)
-{
-	/* was it previously marked as containing a want commit? */
-	if (candidate->object.flags & TMP_MARK)
-		return 1;
-	/* or marked as not possibly containing a want commit? */
-	if (candidate->object.flags & UNINTERESTING)
-		return 0;
-	/* or are we it? */
-	if (in_commit_list(want, candidate)) {
-		candidate->object.flags |= TMP_MARK;
-		return 1;
-	}
-
-	if (parse_commit(candidate) < 0)
-		return 0;
-
-	return -1;
-}
-
-/*
- * Mimicking the real stack, this stack lives on the heap, avoiding stack
- * overflows.
- *
- * At each recursion step, the stack items points to the commits whose
- * ancestors are to be inspected.
- */
-struct stack {
-	int nr, alloc;
-	struct stack_entry {
-		struct commit *commit;
-		struct commit_list *parents;
-	} *stack;
-};
-
-static void push_to_stack(struct commit *candidate, struct stack *stack)
-{
-	int index = stack->nr++;
-	ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
-	stack->stack[index].commit = candidate;
-	stack->stack[index].parents = candidate->parents;
-}
-
-static enum contains_result contains(struct commit *candidate,
-		const struct commit_list *want)
-{
-	struct stack stack = { 0, 0, NULL };
-	int result = contains_test(candidate, want);
-
-	if (result != CONTAINS_UNKNOWN)
-		return result;
-
-	push_to_stack(candidate, &stack);
-	while (stack.nr) {
-		struct stack_entry *entry = &stack.stack[stack.nr - 1];
-		struct commit *commit = entry->commit;
-		struct commit_list *parents = entry->parents;
-
-		if (!parents) {
-			commit->object.flags |= UNINTERESTING;
-			stack.nr--;
-		}
-		/*
-		 * If we just popped the stack, parents->item has been marked,
-		 * therefore contains_test will return a meaningful 0 or 1.
-		 */
-		else switch (contains_test(parents->item, want)) {
-		case CONTAINS_YES:
-			commit->object.flags |= TMP_MARK;
-			stack.nr--;
-			break;
-		case CONTAINS_NO:
-			entry->parents = parents->next;
-			break;
-		case CONTAINS_UNKNOWN:
-			push_to_stack(parents->item, &stack);
-			break;
-		}
-	}
-	free(stack.stack);
-	return contains_test(candidate, want);
-}
-
-/*
- * Currently duplicated in ref-filter, will eventually be removed as
- * we port tag.c to use ref-filter APIs.
- */
-static void show_tag_lines(const struct object_id *oid, int lines)
-{
-	int i;
-	unsigned long size;
-	enum object_type type;
-	char *buf, *sp, *eol;
-	size_t len;
-
-	buf = read_sha1_file(oid->hash, &type, &size);
-	if (!buf)
-		die_errno("unable to read object %s", oid_to_hex(oid));
-	if (type != OBJ_COMMIT && type != OBJ_TAG)
-		goto free_return;
-	if (!size)
-		die("an empty %s object %s?",
-		    typename(type), oid_to_hex(oid));
-
-	/* skip header */
-	sp = strstr(buf, "\n\n");
-	if (!sp)
-		goto free_return;
-
-	/* only take up to "lines" lines, and strip the signature from a tag */
-	if (type == OBJ_TAG)
-		size = parse_signature(buf, size);
-	for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
-		if (i)
-			printf("\n    ");
-		eol = memchr(sp, '\n', size - (sp - buf));
-		len = eol ? eol - sp : size - (sp - buf);
-		fwrite(sp, len, 1, stdout);
-		if (!eol)
-			break;
-		sp = eol + 1;
-	}
-free_return:
-	free(buf);
-}
-
-static void ref_array_append(struct ref_array *array, const char *refname)
-{
-	size_t len = strlen(refname);
-	struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1);
-	memcpy(ref->refname, refname, len);
-	ref->refname[len] = '\0';
-	REALLOC_ARRAY(array->items, array->nr + 1);
-	array->items[array->nr++] = ref;
-}
-
-static int show_reference(const char *refname, const struct object_id *oid,
-			  int flag, void *cb_data)
-{
-	struct ref_filter_cbdata *data = cb_data;
-	struct ref_array *array = data->array;
-	struct ref_filter *filter = data->filter;
-
-	if (match_pattern(filter->name_patterns, refname)) {
-		if (filter->with_commit) {
-			struct commit *commit;
-
-			commit = lookup_commit_reference_gently(oid->hash, 1);
-			if (!commit)
-				return 0;
-			if (!contains(commit, filter->with_commit))
-				return 0;
-		}
-
-		if (filter->points_at.nr && !match_points_at(refname, oid->hash, &filter->points_at))
-			return 0;
-
-		if (!filter->lines) {
-			if (tag_sort)
-				ref_array_append(array, refname);
-			else
-				printf("%s\n", refname);
-			return 0;
-		}
-		printf("%-15s ", refname);
-		show_tag_lines(oid, filter->lines);
-		putchar('\n');
-	}
-
-	return 0;
-}
-
-static int sort_by_version(const void *a_, const void *b_)
-{
-	const struct ref_array_item *a = *((struct ref_array_item **)a_);
-	const struct ref_array_item *b = *((struct ref_array_item **)b_);
-	return versioncmp(a->refname, b->refname);
-}
-
-static int list_tags(struct ref_filter *filter, int sort)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)
 {
 	struct ref_array array;
-	struct ref_filter_cbdata data;
+	char *format;
+	int i;
 
 	memset(&array, 0, sizeof(array));
-	data.array = &array;
-	data.filter = filter;
 
 	if (filter->lines == -1)
 		filter->lines = 0;
 
-	for_each_tag_ref(show_reference, &data);
-	if (sort) {
-		int i;
-		if ((sort & SORT_MASK) == VERCMP_SORT)
-			qsort(array.items, array.nr,
-			      sizeof(struct ref_array_item *), sort_by_version);
-		if (sort & REVERSE_SORT)
-			for (i = array.nr - 1; i >= 0; i--)
-				printf("%s\n", array.items[i]->refname);
-		else
-			for (i = 0; i < array.nr; i++)
-				printf("%s\n", array.items[i]->refname);
-		ref_array_clear(&array);
-	}
+	if (filter->lines)
+		format = "%(align:16,left)%(refname:short)%(end)";
+	else
+		format = "%(refname:short)";
+
+	verify_ref_format(format);
+	filter_refs(&array, filter, FILTER_REFS_TAGS);
+	ref_array_sort(sorting, &array);
+
+	for (i = 0; i < array.nr; i++)
+		show_ref_array_item(array.items[i], format, QUOTE_NONE, filter->lines);
+	ref_array_clear(&array);
+
 	return 0;
 }
 
@@ -366,35 +120,26 @@ static const char tag_template_nocleanup[] =
 	"Lines starting with '%c' will be kept; you may remove them"
 	" yourself if you want to.\n");
 
-/*
- * Parse a sort string, and return 0 if parsed successfully. Will return
- * non-zero when the sort string does not parse into a known type. If var is
- * given, the error message becomes a warning and includes information about
- * the configuration value.
- */
-static int parse_sort_string(const char *var, const char *arg, int *sort)
+/* Parse arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
 {
-	int type = 0, flags = 0;
-
-	if (skip_prefix(arg, "-", &arg))
-		flags |= REVERSE_SORT;
+	struct ref_sorting *s;
+	int len;
 
-	if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
-		type = VERCMP_SORT;
-	else
-		type = STRCMP_SORT;
+	s = xcalloc(1, sizeof(*s));
+	s->next = *sorting_tail;
+	*sorting_tail = s;
 
-	if (strcmp(arg, "refname")) {
-		if (!var)
-			return error(_("unsupported sort specification '%s'"), arg);
-		else {
-			warning(_("unsupported sort specification '%s' in variable '%s'"),
-				var, arg);
-			return -1;
-		}
+	if (*arg == '-') {
+		s->reverse = 1;
+		arg++;
 	}
+	if (skip_prefix(arg, "version:", &arg) ||
+	    skip_prefix(arg, "v:", &arg))
+		s->version = 1;
 
-	*sort = (type | flags);
+	len = strlen(arg);
+	s->atom = parse_ref_filter_atom(arg, arg+len);
 
 	return 0;
 }
@@ -402,11 +147,12 @@ static int parse_sort_string(const char *var, const char *arg, int *sort)
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
 	int status;
+	struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
 
 	if (!strcmp(var, "tag.sort")) {
 		if (!value)
 			return config_error_nonbool(var);
-		parse_sort_string(var, value, &tag_sort);
+		parse_sorting_string(value, sorting_tail);
 		return 0;
 	}
 
@@ -564,13 +310,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
 	return check_refname_format(sb->buf, 0);
 }
 
-static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
-{
-	int *sort = opt->value;
-
-	return parse_sort_string(NULL, arg, sort);
-}
-
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -587,6 +326,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	struct ref_transaction *transaction;
 	struct strbuf err = STRBUF_INIT;
 	struct ref_filter filter;
+	static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 	struct option options[] = {
 		OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
 		{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
@@ -613,10 +353,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 		OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
 		OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
 		OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
-		{
-			OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
-			PARSE_OPT_NONEG, parse_opt_sort
-		},
+		OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+			     N_("field name to sort on"), &parse_opt_ref_sorting),
 		{
 			OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
 			N_("print only tags of the object"), 0, parse_opt_object_name
@@ -624,7 +362,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 		OPT_END()
 	};
 
-	git_config(git_tag_config, NULL);
+	git_config(git_tag_config, sorting_tail);
 
 	memset(&opt, 0, sizeof(opt));
 	memset(&filter, 0, sizeof(filter));
@@ -650,6 +388,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 			die(_("--column and -n are incompatible"));
 		colopts = 0;
 	}
+	if (!sorting)
+		sorting = ref_default_sorting();
 	if (cmdmode == 'l') {
 		int ret;
 		if (column_active(colopts)) {
@@ -658,10 +398,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 			copts.padding = 2;
 			run_column_filter(colopts, &copts);
 		}
-		if (filter.lines != -1 && tag_sort)
-			die(_("--sort and -n are incompatible"));
 		filter.name_patterns = argv;
-		ret = list_tags(&filter, tag_sort);
+		ret = list_tags(&filter, sorting);
 		if (column_active(colopts))
 			stop_column_filter();
 		return ret;
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index d31788c..84153ef 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '
 
 test_expect_success 'invalid sort parameter in configuratoin' '
 	git config tag.sort "v:notvalid" &&
-	git tag -l "foo*" >actual &&
-	cat >expect <<-\EOF &&
-	foo1.10
-	foo1.3
-	foo1.6
-	EOF
-	test_cmp expect actual
+	test_must_fail git tag -l "foo*"
 '
 
 test_expect_success 'version sort with prerelease reordering' '
-- 
2.5.0
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 11/12] tag.c: implement '--format' option

Implement the '--format' option provided by 'ref-filter'. This lets the user list tags as per desired format similar to the implementation in 'git for-each-ref'.

Add tests and documentation for the same.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 Documentation/git-tag.txt |  8 +++++++-
 builtin/tag.c             | 19 +++++++++++--------
 t/t7004-tag.sh            | 12 ++++++++++++
 3 files changed, 30 insertions(+), 9 deletions(-)
Show changes to 3 files +30 −9

Documentation/git-tag.txt, builtin/tag.c, t/t7004-tag.sh

diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 3ac4a96..0c7f4e6 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -13,7 +13,8 @@ SYNOPSIS
 	<tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
-	[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
+	[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
+	[--format=<format>] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.
 	The object that the new tag will refer to, usually a commit.
 	Defaults to HEAD.
 
+<format>::
+	A string that interpolates `%(fieldname)` from the object
+	pointed at by a ref being shown.  The format is the same as
+	that of linkgit:git-for-each-ref[1].  When unspecified,
+	defaults to `%(refname:short)`.
 
 CONFIGURATION
 -------------
diff --git a/builtin/tag.c b/builtin/tag.c
index 501fc52..4b8d6df 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -23,17 +23,16 @@ static const char * const git_tag_usage[] = {
 	N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
 	N_("git tag -d <tagname>..."),
 	N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-		"\n\t\t[<pattern>...]"),
+		"\n\t\t[--format=<format>] [<pattern>...]"),
 	N_("git tag -v <tagname>..."),
 	NULL
 };
 
 static unsigned int colopts;
 
-static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
 {
 	struct ref_array array;
-	char *format;
 	int i;
 
 	memset(&array, 0, sizeof(array));
@@ -41,10 +40,12 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting)
 	if (filter->lines == -1)
 		filter->lines = 0;
 
-	if (filter->lines)
-		format = "%(align:16,left)%(refname:short)%(end)";
-	else
-		format = "%(refname:short)";
+	if (!format) {
+		if (filter->lines)
+			format = "%(align:16,left)%(refname:short)%(end)";
+		else
+			format = "%(refname:short)";
+	}
 
 	verify_ref_format(format);
 	filter_refs(&array, filter, FILTER_REFS_TAGS);
@@ -327,6 +328,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 	struct strbuf err = STRBUF_INIT;
 	struct ref_filter filter;
 	static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+	const char *format = NULL;
 	struct option options[] = {
 		OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
 		{ OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
@@ -359,6 +361,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 			OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
 			N_("print only tags of the object"), 0, parse_opt_object_name
 		},
+		OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
 		OPT_END()
 	};
 
@@ -399,7 +402,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 			run_column_filter(colopts, &copts);
 		}
 		filter.name_patterns = argv;
-		ret = list_tags(&filter, sorting);
+		ret = list_tags(&filter, sorting, format);
 		if (column_active(colopts))
 			stop_column_filter();
 		return ret;
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 84153ef..8987fb1 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1519,4 +1519,16 @@ EOF"
 	test_cmp expect actual
 '
 
+test_expect_success '--format should list tags as per format given' '
+	cat >expect <<-\EOF &&
+	refname : refs/tags/foo1.10
+	refname : refs/tags/foo1.3
+	refname : refs/tags/foo1.6
+	refname : refs/tags/foo1.6-rc1
+	refname : refs/tags/foo1.6-rc2
+	EOF
+	git tag -l --format="refname : %(refname)" "foo*" >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.5.0
Matthieu Moy· Aug 23, 2015, 19:56 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 11/12] tag.c: implement '--format' option

Karthik Nayak <karthik.188@gmail.com> writes:
Show 17 quoted lines
> --- a/Documentation/git-tag.txt
> +++ b/Documentation/git-tag.txt
> @@ -13,7 +13,8 @@ SYNOPSIS
>  	<tagname> [<commit> | <object>]
>  'git tag' -d <tagname>...
>  'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
> -	[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
> +	[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
> +	[--format=<format>] [<pattern>...]
>  'git tag' -v <tagname>...
>  
>  DESCRIPTION
> @@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.
>  	The object that the new tag will refer to, usually a commit.
>  	Defaults to HEAD.
>  
> +<format>::

Shouldn't this be --format <format>, not just <format>? We usually use the named argument in the SYNOPSIS for positional arguments, but not for arguments following an option.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Karthik Nayak· Aug 24, 2015, 15:07 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 11/12] tag.c: implement '--format' option

On Mon, Aug 24, 2015 at 1:26 AM, Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> wrote:

Show 24 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> --- a/Documentation/git-tag.txt
>> +++ b/Documentation/git-tag.txt
>> @@ -13,7 +13,8 @@ SYNOPSIS
>>       <tagname> [<commit> | <object>]
>>  'git tag' -d <tagname>...
>>  'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
>> -     [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
>> +     [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
>> +     [--format=<format>] [<pattern>...]
>>  'git tag' -v <tagname>...
>>
>>  DESCRIPTION
>> @@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.
>>       The object that the new tag will refer to, usually a commit.
>>       Defaults to HEAD.
>>
>> +<format>::
>
> Shouldn't this be --format <format>, not just <format>? We usually use
> the named argument in the SYNOPSIS for positional arguments, but not for
> arguments following an option.
>

This is how it was in for-each-ref Documentation, hence to keep it similar I just put <format>.

-- 
Regards,
Karthik Nayak
Matthieu Moy· Aug 24, 2015, 15:14 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 11/12] tag.c: implement '--format' option

Karthik Nayak <karthik.188@gmail.com> writes:
Show 28 quoted lines
> On Mon, Aug 24, 2015 at 1:26 AM, Matthieu Moy
> <Matthieu.Moy@grenoble-inp.fr> wrote:
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>>> --- a/Documentation/git-tag.txt
>>> +++ b/Documentation/git-tag.txt
>>> @@ -13,7 +13,8 @@ SYNOPSIS
>>>       <tagname> [<commit> | <object>]
>>>  'git tag' -d <tagname>...
>>>  'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
>>> -     [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
>>> +     [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
>>> +     [--format=<format>] [<pattern>...]
>>>  'git tag' -v <tagname>...
>>>
>>>  DESCRIPTION
>>> @@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.
>>>       The object that the new tag will refer to, usually a commit.
>>>       Defaults to HEAD.
>>>
>>> +<format>::
>>
>> Shouldn't this be --format <format>, not just <format>? We usually use
>> the named argument in the SYNOPSIS for positional arguments, but not for
>> arguments following an option.
>
> This is how it was in for-each-ref Documentation, hence to keep it similar I
> just put <format>.

"It's wrong in another place" sounds like an argument to fix the other place, not to get it wrong here too ;-).

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Karthik Nayak· Aug 24, 2015, 15:21 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 11/12] tag.c: implement '--format' option

On Mon, Aug 24, 2015 at 8:44 PM, Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> wrote:

Show 34 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> On Mon, Aug 24, 2015 at 1:26 AM, Matthieu Moy
>> <Matthieu.Moy@grenoble-inp.fr> wrote:
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>
>>>> --- a/Documentation/git-tag.txt
>>>> +++ b/Documentation/git-tag.txt
>>>> @@ -13,7 +13,8 @@ SYNOPSIS
>>>>       <tagname> [<commit> | <object>]
>>>>  'git tag' -d <tagname>...
>>>>  'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
>>>> -     [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [<pattern>...]
>>>> +     [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
>>>> +     [--format=<format>] [<pattern>...]
>>>>  'git tag' -v <tagname>...
>>>>
>>>>  DESCRIPTION
>>>> @@ -158,6 +159,11 @@ This option is only applicable when listing tags without annotation lines.
>>>>       The object that the new tag will refer to, usually a commit.
>>>>       Defaults to HEAD.
>>>>
>>>> +<format>::
>>>
>>> Shouldn't this be --format <format>, not just <format>? We usually use
>>> the named argument in the SYNOPSIS for positional arguments, but not for
>>> arguments following an option.
>>
>> This is how it was in for-each-ref Documentation, hence to keep it similar I
>> just put <format>.
>
> "It's wrong in another place" sounds like an argument to fix the other
> place, not to get it wrong here too ;-).
>

Of course! That was just me justifying my action. I agree, it should be corrected both places.

-- 
Regards,
Karthik Nayak
Karthik Nayak· Aug 22, 2015, 03:39 UTC · re: Karthik Nayak · lore

[PATCH v13 12/12] tag.c: implement '--merged' and '--no-merged' options

Use 'ref-filter' APIs to implement the '--merged' and '--no-merged' options into 'tag.c'. The '--merged' option lets the user to only list tags merged into the named commit. The '--no-merged' option lets the user to only list tags not merged into the named commit. If no object is provided it assumes HEAD as the object.

Add documentation and tests for the same.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
---
 Documentation/git-tag.txt |  7 ++++++-
 builtin/tag.c             |  6 +++++-
 t/t7004-tag.sh            | 27 +++++++++++++++++++++++++++
 3 files changed, 38 insertions(+), 2 deletions(-)
Show changes to 3 files +38 −2

Documentation/git-tag.txt, builtin/tag.c, t/t7004-tag.sh

diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 0c7f4e6..3803bf7 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -14,7 +14,7 @@ SYNOPSIS
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
 	[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
-	[--format=<format>] [<pattern>...]
+	[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
 'git tag' -v <tagname>...
 
 DESCRIPTION
@@ -165,6 +165,11 @@ This option is only applicable when listing tags without annotation lines.
 	that of linkgit:git-for-each-ref[1].  When unspecified,
 	defaults to `%(refname:short)`.
 
+--[no-]merged [<commit>]::
+	Only list tags whose tips are reachable, or not reachable
+	if '--no-merged' is used, from the specified commit ('HEAD'
+	if not specified).
+
 CONFIGURATION
 -------------
 By default, 'git tag' in sign-with-default mode (-s) will use your
diff --git a/builtin/tag.c b/builtin/tag.c
index 4b8d6df..bbbcaed 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -23,7 +23,7 @@ static const char * const git_tag_usage[] = {
 	N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
 	N_("git tag -d <tagname>..."),
 	N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
-		"\n\t\t[--format=<format>] [<pattern>...]"),
+		"\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
 	N_("git tag -v <tagname>..."),
 	NULL
 };
@@ -355,6 +355,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 		OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
 		OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
 		OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+		OPT_MERGED(&filter, N_("print only tags that are merged")),
+		OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
 		OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
 			     N_("field name to sort on"), &parse_opt_ref_sorting),
 		{
@@ -413,6 +415,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 		die(_("--contains option is only allowed with -l."));
 	if (filter.points_at.nr)
 		die(_("--points-at option is only allowed with -l."));
+	if (filter.merge_commit)
+		die(_("--merged and --no-merged option are only allowed with -l"));
 	if (cmdmode == 'd')
 		return for_each_tag_name(argv, delete_tag);
 	if (cmdmode == 'v')
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 8987fb1..3dd2f51 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -1531,4 +1531,31 @@ test_expect_success '--format should list tags as per format given' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setup --merged test tags' '
+	git tag mergetest-1 HEAD~2 &&
+	git tag mergetest-2 HEAD~1 &&
+	git tag mergetest-3 HEAD
+'
+
+test_expect_success '--merged cannot be used in non-list mode' '
+	test_must_fail git tag --merged=mergetest-2 foo
+'
+
+test_expect_success '--merged shows merged tags' '
+	cat >expect <<-\EOF &&
+	mergetest-1
+	mergetest-2
+	EOF
+	git tag -l --merged=mergetest-2 mergetest-* >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--no-merged show unmerged tags' '
+	cat >expect <<-\EOF &&
+	mergetest-3
+	EOF
+	git tag -l --no-merged=mergetest-2 mergetest-* >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.5.0
Matthieu Moy· Aug 23, 2015, 20:00 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 11 quoted lines
> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
> index 1997657..06d468e 100644
> --- a/Documentation/git-for-each-ref.txt
> +++ b/Documentation/git-for-each-ref.txt
> @@ -133,7 +133,8 @@ align::
>  	`<position>` is either left, right or middle and `<width>` is
>  	the total length of the content with alignment. If the
>  	contents length is more than the width then no alignment is
> -	performed.
> +	performed. If used with '--quote' everything in between %(align:..)
> +	and %(end) is quoted.

There's no --quote, there are --shell, --python, ... (well, actually, I would have prefered to have a single --quote=language option, but that's not how it is now).

I had already commented on a preliminary version of this series off-list. I think all my previous comments have been taken into account.

Thanks,
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Karthik Nayak· Aug 24, 2015, 15:09 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Mon, Aug 24, 2015 at 1:30 AM, Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> wrote:

Show 17 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>> index 1997657..06d468e 100644
>> --- a/Documentation/git-for-each-ref.txt
>> +++ b/Documentation/git-for-each-ref.txt
>> @@ -133,7 +133,8 @@ align::
>>       `<position>` is either left, right or middle and `<width>` is
>>       the total length of the content with alignment. If the
>>       contents length is more than the width then no alignment is
>> -     performed.
>> +     performed. If used with '--quote' everything in between %(align:..)
>> +     and %(end) is quoted.
>
> There's no --quote, there are --shell, --python, ... (well, actually, I
> would have prefered to have a single --quote=language option, but that's
> not how it is now).

That'd be easy to implement, but I didn't because `git tag -l` is human readable and I didn't see the necessity for having something like `--<quote_type>` here.

It'd be better to just use `git for-each-ref refs/tags/` for that.
Show 6 quoted lines
>
> I had already commented on a preliminary version of this series
> off-list. I think all my previous comments have been taken into account.
>
> Thanks,
>
Thanks for the review :)
-- 
Regards,
Karthik Nayak
Matthieu Moy· Aug 24, 2015, 15:16 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 23 quoted lines
> On Mon, Aug 24, 2015 at 1:30 AM, Matthieu Moy
> <Matthieu.Moy@grenoble-inp.fr> wrote:
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>>> index 1997657..06d468e 100644
>>> --- a/Documentation/git-for-each-ref.txt
>>> +++ b/Documentation/git-for-each-ref.txt
>>> @@ -133,7 +133,8 @@ align::
>>>       `<position>` is either left, right or middle and `<width>` is
>>>       the total length of the content with alignment. If the
>>>       contents length is more than the width then no alignment is
>>> -     performed.
>>> +     performed. If used with '--quote' everything in between %(align:..)
>>> +     and %(end) is quoted.
>>
>> There's no --quote, there are --shell, --python, ... (well, actually, I
>> would have prefered to have a single --quote=language option, but that's
>> not how it is now).
>
> That'd be easy to implement, but I didn't because `git tag -l` is
> human readable and
> I didn't see the necessity for having something like `--<quote_type>` here.
Agreed: tag does not have --shell, --python & so and does not need it.

But that's not my point: you write "if used with '--quote'", and the option name is not --quote. It should be "if used with `--shell`, `--python`, ... then everything ...".

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Karthik Nayak· Aug 24, 2015, 15:22 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Mon, Aug 24, 2015 at 8:46 PM, Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> wrote:

Show 32 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> On Mon, Aug 24, 2015 at 1:30 AM, Matthieu Moy
>> <Matthieu.Moy@grenoble-inp.fr> wrote:
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>
>>>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>>>> index 1997657..06d468e 100644
>>>> --- a/Documentation/git-for-each-ref.txt
>>>> +++ b/Documentation/git-for-each-ref.txt
>>>> @@ -133,7 +133,8 @@ align::
>>>>       `<position>` is either left, right or middle and `<width>` is
>>>>       the total length of the content with alignment. If the
>>>>       contents length is more than the width then no alignment is
>>>> -     performed.
>>>> +     performed. If used with '--quote' everything in between %(align:..)
>>>> +     and %(end) is quoted.
>>>
>>> There's no --quote, there are --shell, --python, ... (well, actually, I
>>> would have prefered to have a single --quote=language option, but that's
>>> not how it is now).
>>
>> That'd be easy to implement, but I didn't because `git tag -l` is
>> human readable and
>> I didn't see the necessity for having something like `--<quote_type>` here.
>
> Agreed: tag does not have --shell, --python & so and does not need it.
>
> But that's not my point: you write "if used with '--quote'", and the
> option name is not --quote. It should be "if used with `--shell`,
> `--python`, ... then everything ...".
>
Oops! misread what you sent, maybe' --<quote_type>' instead.
-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 24, 2015, 22:34 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
Show 20 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>> index 1997657..06d468e 100644
>> --- a/Documentation/git-for-each-ref.txt
>> +++ b/Documentation/git-for-each-ref.txt
>> @@ -133,7 +133,8 @@ align::
>>  	`<position>` is either left, right or middle and `<width>` is
>>  	the total length of the content with alignment. If the
>>  	contents length is more than the width then no alignment is
>> -	performed.
>> +	performed. If used with '--quote' everything in between %(align:..)
>> +	and %(end) is quoted.
>
> There's no --quote, there are --shell, --python, ... (well, actually, I
> would have prefered to have a single --quote=language option, but that's
> not how it is now).
>
> I had already commented on a preliminary version of this series
> off-list. I think all my previous comments have been taken into account.

Thanks, both. I think this is pretty close to being satisfactory ;-) There may probably be a handful of minor nits like the above that need to be addressed, but I do not think I saw anything glaringly wrong that makes the series unsalvageable. It was a very pleasant read.

It's almost there, and I am very happy to see how this and other series evolved so far ;-)

Junio C Hamano· Aug 24, 2015, 22:58 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Junio C Hamano <gitster@pobox.com> writes:
Show 31 quoted lines
> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>>> index 1997657..06d468e 100644
>>> --- a/Documentation/git-for-each-ref.txt
>>> +++ b/Documentation/git-for-each-ref.txt
>>> @@ -133,7 +133,8 @@ align::
>>>  	`<position>` is either left, right or middle and `<width>` is
>>>  	the total length of the content with alignment. If the
>>>  	contents length is more than the width then no alignment is
>>> -	performed.
>>> +	performed. If used with '--quote' everything in between %(align:..)
>>> +	and %(end) is quoted.
>>
>> There's no --quote, there are --shell, --python, ... (well, actually, I
>> would have prefered to have a single --quote=language option, but that's
>> not how it is now).
>>
>> I had already commented on a preliminary version of this series
>> off-list. I think all my previous comments have been taken into account.
>
> Thanks, both.  I think this is pretty close to being satisfactory
> ;-)  There may probably be a handful of minor nits like the above
> that need to be addressed, but I do not think I saw anything
> glaringly wrong that makes the series unsalvageable.  It was a very
> pleasant read.
>
> It's almost there, and I am very happy to see how this and other
> series evolved so far ;-)

Having said all that, it seems that there is some trivial typo or thinko in the formatting code to break t7004.

Here is what I see...
ok 98 - verifying rfc1991 signature
expecting success:
        echo "rfc1991" >gpghome/gpg.conf &&
        echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
        git tag -l -n1 rfc1991-signed-tag >actual &&
        test_cmp expect actual &&
        git tag -l -n2 rfc1991-signed-tag >actual &&
        test_cmp expect actual &&
        git tag -l -n999 rfc1991-signed-tag >actual &&
        test_cmp expect actual

--- expect 2015-08-24 22:54:44.607272653 +0000 +++ actual 2015-08-24 22:54:44.611272643 +0000

Show changes to diff +1 −1
@@ -1 +1 @@
-rfc1991-signed-tag RFC1991 signed tag
+rfc1991-signed-tagRFC1991 signed tag
not ok 99 - list tag with rfc1991 signature
Karthik Nayak· Aug 25, 2015, 13:26 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Tue, Aug 25, 2015 at 4:28 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 58 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>>
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>
>>>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>>>> index 1997657..06d468e 100644
>>>> --- a/Documentation/git-for-each-ref.txt
>>>> +++ b/Documentation/git-for-each-ref.txt
>>>> @@ -133,7 +133,8 @@ align::
>>>>     `<position>` is either left, right or middle and `<width>` is
>>>>     the total length of the content with alignment. If the
>>>>     contents length is more than the width then no alignment is
>>>> -   performed.
>>>> +   performed. If used with '--quote' everything in between %(align:..)
>>>> +   and %(end) is quoted.
>>>
>>> There's no --quote, there are --shell, --python, ... (well, actually, I
>>> would have prefered to have a single --quote=language option, but that's
>>> not how it is now).
>>>
>>> I had already commented on a preliminary version of this series
>>> off-list. I think all my previous comments have been taken into account.
>>
>> Thanks, both.  I think this is pretty close to being satisfactory
>> ;-)  There may probably be a handful of minor nits like the above
>> that need to be addressed, but I do not think I saw anything
>> glaringly wrong that makes the series unsalvageable.  It was a very
>> pleasant read.
>>
>> It's almost there, and I am very happy to see how this and other
>> series evolved so far ;-)
>
> Having said all that, it seems that there is some trivial typo or
> thinko in the formatting code to break t7004.
>
> Here is what I see...
>
> ok 98 - verifying rfc1991 signature
>
> expecting success:
>         echo "rfc1991" >gpghome/gpg.conf &&
>         echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
>         git tag -l -n1 rfc1991-signed-tag >actual &&
>         test_cmp expect actual &&
>         git tag -l -n2 rfc1991-signed-tag >actual &&
>         test_cmp expect actual &&
>         git tag -l -n999 rfc1991-signed-tag >actual &&
>         test_cmp expect actual
>
> --- expect      2015-08-24 22:54:44.607272653 +0000
> +++ actual      2015-08-24 22:54:44.611272643 +0000
> @@ -1 +1 @@
> -rfc1991-signed-tag RFC1991 signed tag
> +rfc1991-signed-tagRFC1991 signed tag
> not ok 99 - list tag with rfc1991 signature
>

Thats weird, I just ran all tests, and nothing failed. Is this mid-way of the patch series? I tried it on the entire series and seems fine to me

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 25, 2015, 19:16 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 22 quoted lines
>> Here is what I see...
>>
>> ok 98 - verifying rfc1991 signature
>>
>> expecting success:
>>         echo "rfc1991" >gpghome/gpg.conf &&
>>         echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
>>         git tag -l -n1 rfc1991-signed-tag >actual &&
>>         test_cmp expect actual &&
>>         git tag -l -n2 rfc1991-signed-tag >actual &&
>>         test_cmp expect actual &&
>>         git tag -l -n999 rfc1991-signed-tag >actual &&
>>         test_cmp expect actual
>>
>> --- expect      2015-08-24 22:54:44.607272653 +0000
>> +++ actual      2015-08-24 22:54:44.611272643 +0000
>> @@ -1 +1 @@
>> -rfc1991-signed-tag RFC1991 signed tag
>> +rfc1991-signed-tagRFC1991 signed tag
>> not ok 99 - list tag with rfc1991 signature
>
> Thats weird, I just ran all tests, and nothing failed.

You may be missing GPG or RFC1991 prerequisites and not running this particular test, which could be why you missed it.

Your builtin/tag.c calls show_ref_array_item() with 
	"%(align:16,left)%(refname:short)%(end)"
as the format, and "rfc1991-signed-tag" is longer than 16.

And immediately after showing that, there is this hack at the end of show_ref_array_item() function, which I _think_ should not be there in ref-filter.c:show_ref_array_item() in the first place.

	if (lines > 0) {
		struct object_id oid;
		hashcpy(oid.hash, info->objectname);
		show_tag_lines(&oid, lines);
	}
This belongs to the caller who knows that it is dealing with a tag.

But that broken design aside, the reason why this breaks is because you do not have a separating SP after the aligned short refs.

I didn't check how wide the original is supposed to be, but perhaps changing builtin/tag.c this way

		if (filter->lines)
-			format = "%(align:16,left)%(refname:short)%(end)";
+			format = "%(align:15,left)%(refname:short)%(end) ";
		else
			format = "%(refname:short)";
	}

may be one way to fix it. Check the original "tag -l" output for tags whose names are 14, 15, 16 and 17 display-columns wide and try to match it.

And then move the tag-specific code at the end of show_ref_array_item() to here:

	verify_ref_format(format);
	filter_refs(&array, filter, FILTER_REFS_TAGS);
	ref_array_sort(sorting, &array);
-	for (i = 0; i < array.nr; i++)
+	for (i = 0; i < array.nr; i++) {
		show_ref_array_item(array.items[i], format, QUOTE_NONE, filter->lines);
+		if (lines) {
+			struct object_id oid;
+			hashcpy(oid.hash, info->objectname);
+			show_tag_lines(&oid, lines);
+		}
+	}
perhaps.
Junio C Hamano· Aug 25, 2015, 19:43 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Junio C Hamano <gitster@pobox.com> writes:
Show 13 quoted lines
> I didn't check how wide the original is supposed to be, but perhaps
> changing builtin/tag.c this way
>
> 		if (filter->lines)
> -			format = "%(align:16,left)%(refname:short)%(end)";
> +			format = "%(align:15,left)%(refname:short)%(end) ";
> 		else
> 			format = "%(refname:short)";
> 	}
>
> may be one way to fix it.  Check the original "tag -l" output for
> tags whose names are 14, 15, 16 and 17 display-columns wide and try
> to match it.

Heh, I did it myself. %(align:15) with trailing whitespace is what you want.

An alternative way to spell it would be
    "%(align:16,left)%(refname:short) %(end)"
I don't know which one is more readable, though.
Karthik Nayak· Aug 26, 2015, 05:56 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Wed, Aug 26, 2015 at 12:46 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 28 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>>> Here is what I see...
>>>
>>> ok 98 - verifying rfc1991 signature
>>>
>>> expecting success:
>>>         echo "rfc1991" >gpghome/gpg.conf &&
>>>         echo "rfc1991-signed-tag RFC1991 signed tag" >expect &&
>>>         git tag -l -n1 rfc1991-signed-tag >actual &&
>>>         test_cmp expect actual &&
>>>         git tag -l -n2 rfc1991-signed-tag >actual &&
>>>         test_cmp expect actual &&
>>>         git tag -l -n999 rfc1991-signed-tag >actual &&
>>>         test_cmp expect actual
>>>
>>> --- expect      2015-08-24 22:54:44.607272653 +0000
>>> +++ actual      2015-08-24 22:54:44.611272643 +0000
>>> @@ -1 +1 @@
>>> -rfc1991-signed-tag RFC1991 signed tag
>>> +rfc1991-signed-tagRFC1991 signed tag
>>> not ok 99 - list tag with rfc1991 signature
>>
>> Thats weird, I just ran all tests, and nothing failed.
>
> You may be missing GPG or RFC1991 prerequisites and not running this
> particular test, which could be why you missed it.
>
That explains.
Show 18 quoted lines
> Your builtin/tag.c calls show_ref_array_item() with
>
>         "%(align:16,left)%(refname:short)%(end)"
>
> as the format, and "rfc1991-signed-tag" is longer than 16.
>
> And immediately after showing that, there is this hack at the end of
> show_ref_array_item() function, which I _think_ should not be there
> in ref-filter.c:show_ref_array_item() in the first place.
>
>         if (lines > 0) {
>                 struct object_id oid;
>                 hashcpy(oid.hash, info->objectname);
>                 show_tag_lines(&oid, lines);
>         }
>
> This belongs to the caller who knows that it is dealing with a tag.
>
Explained the idea behind this below.
> But that broken design aside, the reason why this breaks is because
> you do not have a separating SP after the aligned short refs.
>
Makes sense.
Show 14 quoted lines
> I didn't check how wide the original is supposed to be, but perhaps
> changing builtin/tag.c this way
>
>                 if (filter->lines)
> -                       format = "%(align:16,left)%(refname:short)%(end)";
> +                       format = "%(align:15,left)%(refname:short)%(end) ";
>                 else
>                         format = "%(refname:short)";
>         }
>
> may be one way to fix it.  Check the original "tag -l" output for
> tags whose names are 14, 15, 16 and 17 display-columns wide and try
> to match it.
>
That should be the fix, since it's a space problem.
Show 18 quoted lines
> And then move the tag-specific code at the end of
> show_ref_array_item() to here:
>
>         verify_ref_format(format);
>         filter_refs(&array, filter, FILTER_REFS_TAGS);
>         ref_array_sort(sorting, &array);
>
> -       for (i = 0; i < array.nr; i++)
> +       for (i = 0; i < array.nr; i++) {
>                 show_ref_array_item(array.items[i], format, QUOTE_NONE, filter->lines);
> +               if (lines) {
> +                       struct object_id oid;
> +                       hashcpy(oid.hash, info->objectname);
> +                       show_tag_lines(&oid, lines);
> +               }
> +       }
>
> perhaps.

The problem with doing this is, the lines need to be displayed immediately after the refname, followed by a newline, what you're suggesting breaks that flow.

We could pass a boolean to show_ref_array_item() and print a newline if no lines are to be printed and probably print the newline in tag.c itself, but seems confusing to me.

Show 8 quoted lines
> Heh, I did it myself.  %(align:15) with trailing whitespace is what
> you want.
>
> An alternative way to spell it would be
>
>     "%(align:16,left)%(refname:short) %(end)"
>
> I don't know which one is more readable, though.

I find this more readable, since the space is clearly visible, unlike format = "%(align:15,left)%(refname:short)%(end) "; in which the space after %(end) is easily unnoticeable.

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 26, 2015, 14:37 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 22 quoted lines
> On Wed, Aug 26, 2015 at 12:46 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>> I didn't check how wide the original is supposed to be, but perhaps
>> changing builtin/tag.c this way
>>
>>                 if (filter->lines)
>> -                       format = "%(align:16,left)%(refname:short)%(end)";
>> +                       format = "%(align:15,left)%(refname:short)%(end) ";
>>                 else
>>                         format = "%(refname:short)";
>>         }
>>
>> may be one way to fix it.  Check the original "tag -l" output for
>> tags whose names are 14, 15, 16 and 17 display-columns wide and try
>> to match it.
>
> That should be the fix, since it's a space problem.
> ....
> The problem with doing this is, the lines need to be displayed
> immediately after  the refname,
> followed by a newline, what you're suggesting breaks that flow.

That is only because show_ref_array_item() does too much. The function is given a placeholder string and a set of data to fill the placeholder with for an item, and the reason why the caller repeatedly calls it, once per item it has, is because it wants to produce a one-line-per-item output. An alternative valid way to structure the API is to have it format into a string and leave the printing to the caller. You can give a new format_ref_array_item() that does not print but fills a strbuf to this caller, make show_ref_array_item() a thin wrapper that calls it and prints it with the final LF for other callers.

Another alternate approach, if you want to give "tag -l" annotation available to for-each-ref, would be to do this:

       if (filter->lines)
               format = xstrfmt("%%(align:15,left)%%(refname:short)%%(end) "
                                "%%(contents:lines=%s)", filter->lines);
       else
               format = "%(refname:short)";

and teach a new %(contents:lines=1) atom. That way, you can lose the ugly special case call to show_tag_lines() that can only be at the end of the output. I somehow think this is a better approach.

Of course you can (and probably would want to) do both, giving a bit lower level "emit to a strbuf" function to the callers _and_ losing hardcoded call to show_tag_lines().

Karthik Nayak· Aug 26, 2015, 19:14 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Wed, Aug 26, 2015 at 8:07 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 49 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> On Wed, Aug 26, 2015 at 12:46 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>
>>> I didn't check how wide the original is supposed to be, but perhaps
>>> changing builtin/tag.c this way
>>>
>>>                 if (filter->lines)
>>> -                       format = "%(align:16,left)%(refname:short)%(end)";
>>> +                       format = "%(align:15,left)%(refname:short)%(end) ";
>>>                 else
>>>                         format = "%(refname:short)";
>>>         }
>>>
>>> may be one way to fix it.  Check the original "tag -l" output for
>>> tags whose names are 14, 15, 16 and 17 display-columns wide and try
>>> to match it.
>>
>> That should be the fix, since it's a space problem.
>> ....
>> The problem with doing this is, the lines need to be displayed
>> immediately after  the refname,
>> followed by a newline, what you're suggesting breaks that flow.
>
> That is only because show_ref_array_item() does too much.  The
> function is given a placeholder string and a set of data to fill the
> placeholder with for an item, and the reason why the caller
> repeatedly calls it, once per item it has, is because it wants to
> produce a one-line-per-item output.  An alternative valid way to
> structure the API is to have it format into a string and leave the
> printing to the caller.  You can give a new format_ref_array_item()
> that does not print but fills a strbuf to this caller, make
> show_ref_array_item() a thin wrapper that calls it and prints it
> with the final LF for other callers.
>
> Another alternate approach, if you want to give "tag -l" annotation
> available to for-each-ref, would be to do this:
>
>        if (filter->lines)
>                format = xstrfmt("%%(align:15,left)%%(refname:short)%%(end) "
>                                 "%%(contents:lines=%s)", filter->lines);
>        else
>                format = "%(refname:short)";
>
> and teach a new %(contents:lines=1) atom.  That way, you can lose
> the ugly special case call to show_tag_lines() that can only be at
> the end of the output.  I somehow think this is a better approach.
>

This seems like a good approach, since contents is already an atom, this would fit in easily.

> Of course you can (and probably would want to) do both, giving a
> bit lower level "emit to a strbuf" function to the callers _and_
> losing hardcoded call to show_tag_lines().

You're saying remove show_ref_array_item() (even the wrapper you mentioned above) and just have something like format_ref_array_item() which would output to a strbuf. and let the caller worry about the printing?

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 26, 2015, 20:19 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 10 quoted lines
> On Wed, Aug 26, 2015 at 8:07 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>
>> ...  You can give a new format_ref_array_item()
>> that does not print but fills a strbuf to this caller, make
>> show_ref_array_item() a thin wrapper that calls it and prints it
>> with the final LF for other callers.
>>
> You're saying remove show_ref_array_item() (even the wrapper you mentioned
> above) and just have something like format_ref_array_item() which
> would output to a strbuf. and let the caller worry about the printing?

Among the current callers, the one in builtin/tag.c that wants to trigger show_tag_lines() hack embedded in show_ref_array_item() function can stop calling show_ref_array_item() and instead can do

	for (i = 0; i < array.nr; i++) {
		struct strbuf out = STRBUF_INIT;
        	format_ref_array_item(&out, ...);
                if (filter->lines) {
                	... append tag lines to out ...
		}
                printf("%s\n", out.buf);
                strbuf_reset(&out);
	}
                
The current and future callers of show_ref_array_item() that do not
want to trigger the show_tag_liens() hack embedded in there may
still want it to print the formatted string including the trailing
LF, so you can keep show_ref_array_item() as a thin wrapper around
format_ref_array_item() for them to call, e.g.
	show_ref_array_item(...) {
		struct strbuf out = STRBUF_INIT;
        	format_ref_array_item(&out, ...);
                printf("%s\n", out.buf);
                strbuf_release(&out);
	}

But if it has only one caller each, you may not even want to have show_ref_array_item(), if you are going to do the "output to strbuf" variant.

		        	
Karthik Nayak· Aug 27, 2015, 18:00 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Thu, Aug 27, 2015 at 1:49 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 46 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> On Wed, Aug 26, 2015 at 8:07 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>>
>>> ...  You can give a new format_ref_array_item()
>>> that does not print but fills a strbuf to this caller, make
>>> show_ref_array_item() a thin wrapper that calls it and prints it
>>> with the final LF for other callers.
>>>
>> You're saying remove show_ref_array_item() (even the wrapper you mentioned
>> above) and just have something like format_ref_array_item() which
>> would output to a strbuf. and let the caller worry about the printing?
>
> Among the current callers, the one in builtin/tag.c that wants to
> trigger show_tag_lines() hack embedded in show_ref_array_item()
> function can stop calling show_ref_array_item() and instead can do
>
>         for (i = 0; i < array.nr; i++) {
>                 struct strbuf out = STRBUF_INIT;
>                 format_ref_array_item(&out, ...);
>                 if (filter->lines) {
>                         ... append tag lines to out ...
>                 }
>                 printf("%s\n", out.buf);
>                 strbuf_reset(&out);
>         }
>
> The current and future callers of show_ref_array_item() that do not
> want to trigger the show_tag_liens() hack embedded in there may
> still want it to print the formatted string including the trailing
> LF, so you can keep show_ref_array_item() as a thin wrapper around
> format_ref_array_item() for them to call, e.g.
>
>         show_ref_array_item(...) {
>                 struct strbuf out = STRBUF_INIT;
>                 format_ref_array_item(&out, ...);
>                 printf("%s\n", out.buf);
>                 strbuf_release(&out);
>         }
>
> But if it has only one caller each, you may not even want to have
> show_ref_array_item(), if you are going to do the "output to strbuf"
> variant.
>
>
>

This is exactly what I did at the moment, I'm also trying to get %(contents:lines=N) work. Thanks for explaining though.

-- 
Regards,
Karthik Nayak
Karthik Nayak· Aug 25, 2015, 13:23 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Tue, Aug 25, 2015 at 4:04 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 31 quoted lines
> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>>> index 1997657..06d468e 100644
>>> --- a/Documentation/git-for-each-ref.txt
>>> +++ b/Documentation/git-for-each-ref.txt
>>> @@ -133,7 +133,8 @@ align::
>>>      `<position>` is either left, right or middle and `<width>` is
>>>      the total length of the content with alignment. If the
>>>      contents length is more than the width then no alignment is
>>> -    performed.
>>> +    performed. If used with '--quote' everything in between %(align:..)
>>> +    and %(end) is quoted.
>>
>> There's no --quote, there are --shell, --python, ... (well, actually, I
>> would have prefered to have a single --quote=language option, but that's
>> not how it is now).
>>
>> I had already commented on a preliminary version of this series
>> off-list. I think all my previous comments have been taken into account.
>
> Thanks, both.  I think this is pretty close to being satisfactory
> ;-)  There may probably be a handful of minor nits like the above
> that need to be addressed, but I do not think I saw anything
> glaringly wrong that makes the series unsalvageable.  It was a very
> pleasant read.
>
> It's almost there, and I am very happy to see how this and other
> series evolved so far ;-)

I like how it's totally different from what I started off with, it's been a great learning curve for me. Thanks for reviews.

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 24, 2015, 17:27 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 13 quoted lines
> Interdiff:
>
> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
> index 1997657..06d468e 100644
> --- a/Documentation/git-for-each-ref.txt
> +++ b/Documentation/git-for-each-ref.txt
> @@ -133,7 +133,8 @@ align::
>  	`<position>` is either left, right or middle and `<width>` is
>  	the total length of the content with alignment. If the
>  	contents length is more than the width then no alignment is
> -	performed.
> +	performed. If used with '--quote' everything in between %(align:..)
> +	and %(end) is quoted.

That sounds like a buggy behaviour that we may want to fix later, though. Perhaps document it as a known bug, e.g. "Currently it does not work well when used with language-specific quoting like --shell, etc." (while punting on fixing the implementation for now)?

Karthik Nayak· Aug 24, 2015, 18:09 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Mon, Aug 24, 2015 at 10:57 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 21 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> Interdiff:
>>
>> diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
>> index 1997657..06d468e 100644
>> --- a/Documentation/git-for-each-ref.txt
>> +++ b/Documentation/git-for-each-ref.txt
>> @@ -133,7 +133,8 @@ align::
>>       `<position>` is either left, right or middle and `<width>` is
>>       the total length of the content with alignment. If the
>>       contents length is more than the width then no alignment is
>> -     performed.
>> +     performed. If used with '--quote' everything in between %(align:..)
>> +     and %(end) is quoted.
>
> That sounds like a buggy behaviour that we may want to fix later,
> though.  Perhaps document it as a known bug, e.g. "Currently it does
> not work well when used with language-specific quoting like --shell,
> etc." (while punting on fixing the implementation for now)?
>

I might have misunderstood you, But based on the discussion held here (thread.gmane.org/gmane.comp.version-control.git/276140) I thought we wanted everything inside the %(align) .... %(end) atoms to be quoted. So I'm a little confused by what you're trying to say.

-- 
Regards,
Karthik Nayak
Junio C Hamano· Aug 24, 2015, 18:53 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 10 quoted lines
> On Mon, Aug 24, 2015 at 10:57 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Karthik Nayak <karthik.188@gmail.com> writes:
>> ...
>>> +     performed. If used with '--quote' everything in between %(align:..)
>>> +     and %(end) is quoted.
> ...
> I might have misunderstood you, But based on the discussion held here
> (thread.gmane.org/gmane.comp.version-control.git/276140)
> I thought we wanted everything inside the %(align) .... %(end) atoms
> to be quoted.
Perhaps I misunderstood your intention in the doc.
	
I took "everything in between %(align:...) and %(end) is quoted" to
mean that
	%(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)

can never satisfy %(if:empty), because %(align)%(end) would expand to a string that has two single-quotes, that is not an empty string.

If that is not what would happen in the "branch --list" enhancement, then the proposed behaviour is good, but the above documentation would need to be updated when it happens, I think. It at least is misleading.

Thanks.
Junio C Hamano· Aug 24, 2015, 22:35 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Junio C Hamano <gitster@pobox.com> writes:
Show 26 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> On Mon, Aug 24, 2015 at 10:57 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>> ...
>>>> +     performed. If used with '--quote' everything in between %(align:..)
>>>> +     and %(end) is quoted.
>> ...
>> I might have misunderstood you, But based on the discussion held here
>> (thread.gmane.org/gmane.comp.version-control.git/276140)
>> I thought we wanted everything inside the %(align) .... %(end) atoms
>> to be quoted.
>
> Perhaps I misunderstood your intention in the doc.
> 	
> I took "everything in between %(align:...) and %(end) is quoted" to
> mean that
>
> 	%(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>
> can never satisfy %(if:empty), because %(align)%(end) would expand
> to a string that has two single-quotes, that is not an empty string.
>
> If that is not what would happen in the "branch --list" enhancement,
> then the proposed behaviour is good, but the above documentation would
> need to be updated when it happens, I think.  It at least is misleading.

OK, now I checked the code, and I _think_ the recursive logic is doing the right thing (modulo minor nits on comment-vs-code discrepancy and code structure I sent separately).

Karthik Nayak· Aug 26, 2015, 10:07 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Tue, Aug 25, 2015 at 4:05 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 33 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Karthik Nayak <karthik.188@gmail.com> writes:
>>
>>> On Mon, Aug 24, 2015 at 10:57 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>> ...
>>>>> +     performed. If used with '--quote' everything in between %(align:..)
>>>>> +     and %(end) is quoted.
>>> ...
>>> I might have misunderstood you, But based on the discussion held here
>>> (thread.gmane.org/gmane.comp.version-control.git/276140)
>>> I thought we wanted everything inside the %(align) .... %(end) atoms
>>> to be quoted.
>>
>> Perhaps I misunderstood your intention in the doc.
>>
>> I took "everything in between %(align:...) and %(end) is quoted" to
>> mean that
>>
>>       %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>
>> can never satisfy %(if:empty), because %(align)%(end) would expand
>> to a string that has two single-quotes, that is not an empty string.
>>
>> If that is not what would happen in the "branch --list" enhancement,
>> then the proposed behaviour is good, but the above documentation would
>> need to be updated when it happens, I think.  It at least is misleading.
>
> OK, now I checked the code, and I _think_ the recursive logic is
> doing the right thing (modulo minor nits on comment-vs-code
> discrepancy and code structure I sent separately).
>

For the current code %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end) would print non-empty, I guess the documentation holds in that case. Not sure if we require it to print non-empty.

-- 
Regards,
Karthik Nayak
Matthieu Moy· Aug 26, 2015, 11:54 UTC · re: Karthik Nayak · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Karthik Nayak <karthik.188@gmail.com> writes:
Show 38 quoted lines
> On Tue, Aug 25, 2015 at 4:05 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Junio C Hamano <gitster@pobox.com> writes:
>>
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>
>>>> On Mon, Aug 24, 2015 at 10:57 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>>>> ...
>>>>>> +     performed. If used with '--quote' everything in between %(align:..)
>>>>>> +     and %(end) is quoted.
>>>> ...
>>>> I might have misunderstood you, But based on the discussion held here
>>>> (thread.gmane.org/gmane.comp.version-control.git/276140)
>>>> I thought we wanted everything inside the %(align) .... %(end) atoms
>>>> to be quoted.
>>>
>>> Perhaps I misunderstood your intention in the doc.
>>>
>>> I took "everything in between %(align:...) and %(end) is quoted" to
>>> mean that
>>>
>>>       %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>>
>>> can never satisfy %(if:empty), because %(align)%(end) would expand
>>> to a string that has two single-quotes, that is not an empty string.
>>>
>>> If that is not what would happen in the "branch --list" enhancement,
>>> then the proposed behaviour is good, but the above documentation would
>>> need to be updated when it happens, I think.  It at least is misleading.
>>
>> OK, now I checked the code, and I _think_ the recursive logic is
>> doing the right thing (modulo minor nits on comment-vs-code
>> discrepancy and code structure I sent separately).
>>
>
> For the current code %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
> would print non-empty, I guess the documentation holds in that case.
> Not sure if we require it to print non-empty.

You don't want the %(if) condition to depend on whether --shell/--python/... is used. Since %(if:empty)%(align)%(end)%(then) holds when you don't use --shell, you also want it to hold when you quote. IOW, you should check for emptyness before (or actually without) doing the quoting. I guess this is what you're doing, and if so, I think it's "The Right Thing".

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Junio C Hamano· Aug 26, 2015, 15:44 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
Show 10 quoted lines
>> For the current code %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>> would print non-empty, I guess the documentation holds in that case.
>> Not sure if we require it to print non-empty.
>
> You don't want the %(if) condition to depend on whether
> --shell/--python/... is used. Since %(if:empty)%(align)%(end)%(then)
> holds when you don't use --shell, you also want it to hold when you
> quote. IOW, you should check for emptyness before (or actually without)
> doing the quoting. I guess this is what you're doing, and if so, I think
> it's "The Right Thing".

I agree that %(align)%(end) should expand to empty and %(if:empty)...%(then) should look at that empty string without quoting. So

    %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
should give "Empty"; otherwise the code is buggy, I think.
Junio C Hamano· Aug 26, 2015, 15:46 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Junio C Hamano <gitster@pobox.com> writes:
Show 19 quoted lines
> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>
>>> For the current code %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>> would print non-empty, I guess the documentation holds in that case.
>>> Not sure if we require it to print non-empty.
>>
>> You don't want the %(if) condition to depend on whether
>> --shell/--python/... is used. Since %(if:empty)%(align)%(end)%(then)
>> holds when you don't use --shell, you also want it to hold when you
>> quote. IOW, you should check for emptyness before (or actually without)
>> doing the quoting. I guess this is what you're doing, and if so, I think
>> it's "The Right Thing".
>
> I agree that %(align)%(end) should expand to empty and %(if:empty)...%(then)
> should look at that empty string without quoting.  So 
>
>     %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>
> should give "Empty"; otherwise the code is buggy, I think.
(I shouldn't be typing while eating...)

It should give "Empty", but the --shell/--python/... may make the whole "Empty", as the result of %(if:...)...%(end), be quoted. So you may see "'Empty'" in the output.

Matthieu Moy· Aug 26, 2015, 15:48 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

Junio C Hamano <gitster@pobox.com> writes:
Show 27 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>>
>>>> For the current code %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>>> would print non-empty, I guess the documentation holds in that case.
>>>> Not sure if we require it to print non-empty.
>>>
>>> You don't want the %(if) condition to depend on whether
>>> --shell/--python/... is used. Since %(if:empty)%(align)%(end)%(then)
>>> holds when you don't use --shell, you also want it to hold when you
>>> quote. IOW, you should check for emptyness before (or actually without)
>>> doing the quoting. I guess this is what you're doing, and if so, I think
>>> it's "The Right Thing".
>>
>> I agree that %(align)%(end) should expand to empty and %(if:empty)...%(then)
>> should look at that empty string without quoting.  So 
>>
>>     %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>
>> should give "Empty"; otherwise the code is buggy, I think.
>
> (I shouldn't be typing while eating...)
>
> It should give "Empty", but the --shell/--python/... may make the
> whole "Empty", as the result of %(if:...)...%(end), be quoted.  So
> you may see "'Empty'" in the output.
Agreed (with both points).
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Karthik Nayak· Aug 26, 2015, 19:11 UTC · re: Matthieu Moy · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Wed, Aug 26, 2015 at 9:18 PM, Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> wrote:

Show 32 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Junio C Hamano <gitster@pobox.com> writes:
>>
>>> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>>>
>>>>> For the current code %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>>>> would print non-empty, I guess the documentation holds in that case.
>>>>> Not sure if we require it to print non-empty.
>>>>
>>>> You don't want the %(if) condition to depend on whether
>>>> --shell/--python/... is used. Since %(if:empty)%(align)%(end)%(then)
>>>> holds when you don't use --shell, you also want it to hold when you
>>>> quote. IOW, you should check for emptyness before (or actually without)
>>>> doing the quoting. I guess this is what you're doing, and if so, I think
>>>> it's "The Right Thing".
>>>
>>> I agree that %(align)%(end) should expand to empty and %(if:empty)...%(then)
>>> should look at that empty string without quoting.  So
>>>
>>>     %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>>>
>>> should give "Empty"; otherwise the code is buggy, I think.
>>
>> (I shouldn't be typing while eating...)
>>
>> It should give "Empty", but the --shell/--python/... may make the
>> whole "Empty", as the result of %(if:...)...%(end), be quoted.  So
>> you may see "'Empty'" in the output.
>
> Agreed (with both points).
>
Thanks, will work on this.
-- 
Regards,
Karthik Nayak
Karthik Nayak· Aug 25, 2015, 13:25 UTC · re: Junio C Hamano · lore

Re: [PATCH v13 00/12] port tag.c to use ref-filter APIs

On Tue, Aug 25, 2015 at 12:23 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 28 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
>
>> On Mon, Aug 24, 2015 at 10:57 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>> Karthik Nayak <karthik.188@gmail.com> writes:
>>> ...
>>>> +     performed. If used with '--quote' everything in between %(align:..)
>>>> +     and %(end) is quoted.
>> ...
>> I might have misunderstood you, But based on the discussion held here
>> (thread.gmane.org/gmane.comp.version-control.git/276140)
>> I thought we wanted everything inside the %(align) .... %(end) atoms
>> to be quoted.
>
> Perhaps I misunderstood your intention in the doc.
>
> I took "everything in between %(align:...) and %(end) is quoted" to
> mean that
>
>         %(if:empty)%(align)%(end)%(then)Empty%(else)Not Empty%(end)
>
> can never satisfy %(if:empty), because %(align)%(end) would expand
> to a string that has two single-quotes, that is not an empty string.
>
> If that is not what would happen in the "branch --list" enhancement,
> then the proposed behaviour is good, but the above documentation would
> need to be updated when it happens, I think.  It at least is misleading.
>
> Thanks.

I'm not sure I checked this condition, will have a look at this, thanks for pointing it out.

> OK, now I checked the code, and I _think_ the recursive logic is
> doing the right thing (modulo minor nits on comment-vs-code
> discrepancy and code structure I sent separately).

I'm not so sure about that, I'll get back on this. (I didn't think about empty string alignment and its effect on %(if:empty) and so on).

-- 
Regards,
Karthik Nayak

← back to recent threads