This is part of porting tag.c to ref-filter APIs. Version 13 can be found: thread.gmane.org/gmane.comp.version-control.git/276363 Changes in this version: * Introduce format_ref_array_item() and make show_ref_array_item() a wrapper around the same. * Introduce %(contents:lines=X) which gets the first X lines from a given object. * Change code in 05/13 to make the code neater and consistent. * %(align) without arguments fails now. * Add test for nested alignment. * We perform quoting on each layer of nested alignment. Karthik Nayak (13): 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: introduce format_ref_array_item() ref-filter: add support for %(contents:lines=X) 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 | 13 ++ Documentation/git-tag.txt | 27 ++- builtin/for-each-ref.c | 1 + builtin/tag.c | 368 ++++++-------------------------- ref-filter.c | 418 ++++++++++++++++++++++++++++++++----- ref-filter.h | 32 ++- refs.c | 9 + refs.h | 1 + t/t6302-for-each-ref-filter.sh | 137 ++++++++++++ t/t7004-tag.sh | 47 ++++- utf8.c | 21 ++ utf8.h | 15 ++ 12 files changed, 712 insertions(+), 377 deletions(-) Interdiff between version 13 and version 14 diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 06d468e..1b48b95 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -149,6 +149,7 @@ Its first line is `contents:subject`, where subject is the concatenation of all lines of the commit message up to the first blank line. The next line is 'contents:body', where body is all of the lines after the first blank line. Finally, the optional GPG signature is `contents:signature`. +The first `N` lines of the object is obtained using `contents:lines=N`. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 3ad6a64..4e9f6c2 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -75,7 +75,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, 0); + show_ref_array_item(array.items[i], format, quote_style); ref_array_clear(&array); return 0; } diff --git a/builtin/tag.c b/builtin/tag.c index bbbcaed..9fa1400 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -33,6 +33,7 @@ static unsigned int colopts; static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { struct ref_array array; + char *to_free = NULL; int i; memset(&array, 0, sizeof(array)); @@ -42,7 +43,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con if (!format) { if (filter->lines) - format = "%(align:16,left)%(refname:short)%(end)"; + format = to_free = xstrfmt("%%(align:15,left)%%(refname:short)%%(end) %%(contents:lines=%d)", + filter->lines); else format = "%(refname:short)"; } @@ -52,8 +54,9 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) - show_ref_array_item(array.items[i], format, QUOTE_NONE, filter->lines); + show_ref_array_item(array.items[i], format, 0); ref_array_clear(&array); + free(to_free); return 0; } diff --git a/ref-filter.c b/ref-filter.c index f8b8fb7..f268cd7 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -58,6 +58,7 @@ static struct { { "color" }, { "align" }, { "end" }, + { "contents:lines" }, }; struct align { @@ -65,6 +66,11 @@ struct align { unsigned int width; }; +struct contents { + unsigned int lines; + struct object_id oid; +}; + #define REF_FORMATTING_STATE_INIT { 0, NULL } struct ref_formatting_stack { @@ -82,6 +88,7 @@ struct ref_formatting_state { struct atom_value { const char *s; struct align *align; + struct contents *contents; void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); unsigned long ul; /* used for sorting when not FIELD_STR */ }; @@ -155,7 +162,28 @@ int parse_ref_filter_atom(const char *atom, const char *ep) return at; } -static void push_new_stack_element(struct ref_formatting_stack **stack) +static void quote_formatting(struct strbuf *s, const char *str, int quote_style) +{ + switch (quote_style) { + case QUOTE_NONE: + strbuf_addstr(s, str); + break; + case QUOTE_SHELL: + sq_quote_buf(s, str); + break; + case QUOTE_PERL: + perl_quote_buf(s, str); + break; + case QUOTE_PYTHON: + python_quote_buf(s, str); + break; + case QUOTE_TCL: + tcl_quote_buf(s, str); + break; + } +} + +static void push_stack_element(struct ref_formatting_stack **stack) { struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack)); @@ -550,6 +578,61 @@ static void find_subpos(const char *buf, unsigned long sz, *nonsiglen = *sig - buf; } +/* + * If 'lines' is greater than 0, append that many lines from the given + * object_id 'oid' to the given strbuf. + */ +static void append_tag_lines(struct strbuf *out, 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) + strbuf_addstr(out, "\n "); + eol = memchr(sp, '\n', size - (sp - buf)); + len = eol ? eol - sp : size - (sp - buf); + strbuf_add(out, sp, len); + if (!eol) + break; + sp = eol + 1; + } +free_return: + free(buf); +} + +static void contents_lines_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct contents *contents = (struct contents *)atomv->contents; + struct strbuf s = STRBUF_INIT; + + append_tag_lines(&s, &contents->oid, contents->lines); + quote_formatting(&state->stack->output, s.buf, state->quote_style); + strbuf_release(&s); + + free(contents); +} + /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { @@ -560,6 +643,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; + const char *valp = NULL; if (!!deref != (*name == '*')) continue; if (deref) @@ -569,7 +653,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj strcmp(name, "contents") && strcmp(name, "contents:subject") && strcmp(name, "contents:body") && - strcmp(name, "contents:signature")) + strcmp(name, "contents:signature") && + !starts_with(name, "contents:lines=")) continue; if (!subpos) find_subpos(buf, sz, @@ -589,6 +674,15 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); + else if (skip_prefix(name, "contents:lines=", &valp)) { + struct contents *contents = xmalloc(sizeof(struct contents)); + + if (strtoul_ui(valp, 10, &contents->lines)) + die(_("positive width expected align:%s"), valp); + hashcpy(contents->oid.hash, obj->sha1); + v->handler = contents_lines_handler; + v->contents = contents; + } } } @@ -661,13 +755,25 @@ static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_s { struct ref_formatting_stack *new; - push_new_stack_element(&state->stack); + push_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 append_atom(struct atom_value *v, struct ref_formatting_state *state) +{ + /* + * Quote formatting is only done when the stack has a single + * element. Otherwise quote formatting is done on the + * element's entire output strbuf when the %(end) atom is + * encountered. + */ + if (!state->stack->prev) + quote_formatting(&state->stack->output, v->s, state->quote_style); + else + strbuf_addstr(&state->stack->output, v->s); +} static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) { @@ -682,8 +788,8 @@ static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_sta * 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); + if (state->stack->prev) { + quote_formatting(&s, current->output.buf, state->quote_style); strbuf_reset(¤t->output); strbuf_addbuf(¤t->output, &s); } @@ -722,6 +828,8 @@ static void populate_value(struct ref_array_item *ref) const char *valp; struct branch *branch = NULL; + v->handler = append_atom; + if (*name == '*') { deref = 1; name++; @@ -785,7 +893,9 @@ static void populate_value(struct ref_array_item *ref) else v->s = " "; continue; - } else if (skip_prefix(name, "align:", &valp)) { + } else if (!strcmp(name, "align")) + die(_("format: incomplete use of the `align` atom")); + else if (skip_prefix(name, "align:", &valp)) { struct align *align = xmalloc(sizeof(struct align)); struct strbuf **s = strbuf_split_str(valp, ',', 0); @@ -1172,12 +1282,10 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) { "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; + if (filter->kind == FILTER_REFS_BRANCHES || + filter->kind == FILTER_REFS_REMOTES || + filter->kind == FILTER_REFS_TAGS) + return filter->kind; else if (!strcmp(refname, "HEAD")) return FILTER_REFS_DETACHED_HEAD; @@ -1211,6 +1319,11 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, return 0; } + /* + * Get the current ref kind. If we're filtering tags, remotes or local branches + * only then the current ref-kind is nothing but filter->kind and filter_ref_kind() + * will only return that value. + */ kind = filter_ref_kind(filter, refname); if (!(kind & filter->kind)) return 0; @@ -1328,25 +1441,26 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int ref_cbdata.array = array; ref_cbdata.filter = filter; - /* Simple per-ref filtering */ - if (type & FILTER_REFS_INCLUDE_BROKEN) { - type &= ~FILTER_REFS_INCLUDE_BROKEN; + if (type & FILTER_REFS_INCLUDE_BROKEN) broken = 1; - } + filter->kind = type & FILTER_REFS_KIND_MASK; - 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 + /* Simple per-ref filtering */ + if (!filter->kind) die("filter_refs: invalid type"); + else { + if (filter->kind == FILTER_REFS_BRANCHES) + ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind == FILTER_REFS_REMOTES) + ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind == FILTER_REFS_TAGS) + ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind & FILTER_REFS_ALL) + ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken); + if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) + head_ref(ref_filter_handler, &ref_cbdata); + } + /* Filters that need revision walking */ if (filter->merge_commit) @@ -1400,33 +1514,6 @@ 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 perform_quote_formatting(struct strbuf *s, const char *str, int quote_style) -{ - switch (quote_style) { - case QUOTE_NONE: - strbuf_addstr(s, str); - break; - case QUOTE_SHELL: - sq_quote_buf(s, str); - break; - case QUOTE_PERL: - perl_quote_buf(s, str); - break; - case QUOTE_PYTHON: - python_quote_buf(s, str); - break; - case QUOTE_TCL: - 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') @@ -1467,58 +1554,14 @@ static void append_literal(const char *cp, const char *ep, struct ref_formatting } } -/* - * 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) +void format_ref_array_item(struct strbuf *out, 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); + push_stack_element(&state.stack); for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { struct atom_value *atomv; @@ -1527,18 +1570,7 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, if (cp < sp) 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); - else if (!state.stack->prev) - append_atom(atomv, &state); - else - strbuf_addstr(&state.stack->output, atomv->s); + atomv->handler(atomv, &state); } if (*cp) { sp = cp + strlen(cp); @@ -1555,15 +1587,17 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, } if (state.stack->prev) die(_("format: `end` atom missing")); - final_buf = &state.stack->output; - fwrite(final_buf->buf, 1, final_buf->len, stdout); + strbuf_addbuf(out, &state.stack->output); pop_stack_element(&state.stack); - if (lines > 0) { - struct object_id oid; - hashcpy(oid.hash, info->objectname); - show_tag_lines(&oid, lines); - } - putchar('\n'); +} + +void show_ref_array_item(struct ref_array_item *item, const char *format, unsigned int quote_style) +{ + struct strbuf out = STRBUF_INIT; + format_ref_array_item(&out, item, format, quote_style); + fwrite(out.buf, out.len, 1, stdout); + printf("\n"); + strbuf_release(&out); } /* If no sorting option is given, use refname to sort as default */ diff --git a/ref-filter.h b/ref-filter.h index 8241066..179944c 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -21,6 +21,7 @@ #define FILTER_REFS_ALL (FILTER_REFS_TAGS | FILTER_REFS_BRANCHES | \ FILTER_REFS_REMOTES | FILTER_REFS_OTHERS) #define FILTER_REFS_DETACHED_HEAD 0x0020 +#define FILTER_REFS_KIND_MASK (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD) struct atom_value; @@ -93,12 +94,11 @@ 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. 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); +/* Format the ref as per given format and quote_style and store it into the strbuf */ +void format_ref_array_item(struct strbuf *out, struct ref_array_item *info, + const char *format, int quote_style); +/* Wrapper around format_ref_array_item() which prints the given ref_array_item */ +void show_ref_array_item(struct ref_array_item *item, const char *format, unsigned int quote_style); /* 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 */ diff --git a/refs.c b/refs.c index 3266617..a9469c2 100644 --- a/refs.c +++ b/refs.c @@ -2108,6 +2108,15 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data); } +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) +{ + unsigned int flag = 0; + + if (broken) + flag = DO_FOR_EACH_INCLUDE_BROKEN; + return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data); +} + int for_each_ref_in_submodule(const char *submodule, const char *prefix, each_ref_fn fn, void *cb_data) { @@ -2150,15 +2159,6 @@ 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 6e913ee..6d30c98 100644 --- a/refs.h +++ b/refs.h @@ -173,13 +173,13 @@ typedef int each_ref_fn(const char *refname, extern int head_ref(each_ref_fn fn, void *cb_data); extern int for_each_ref(each_ref_fn fn, void *cb_data); extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data); +extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken); extern int for_each_tag_ref(each_ref_fn fn, void *cb_data); extern int for_each_branch_ref(each_ref_fn fn, void *cb_data); 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); diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 38c99c9..8f18f86 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -150,6 +150,38 @@ test_expect_success 'alignment with format quote' ' test_cmp expect actual ' +test_expect_success 'nested alignment' ' + cat >expect <<-\EOF && + | master | + | side | + | odd/spot | + | double-tag | + | four | + | one | + | signed-tag | + | three | + | two | + EOF + git for-each-ref --format="|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|" >actual && + test_cmp expect actual +' + +test_expect_success 'check `%(contents:lines=X)`' ' + cat >expect <<-\EOF && + master three + side four + odd/spot three + double-tag Annonated doubly + four four + one one + signed-tag A signed tag message + three three + two two + EOF + git for-each-ref --format="%(refname:short) %(contents:lines=1)" >actual && + test_cmp expect actual +' + test_expect_success 'setup for version sort' ' test_commit foo1.3 && test_commit foo1.6 && -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html