This is part of my GSoC project to unify git tag -l, git branch -l,
git for-each-ref
This patch series is continued from:
http://article.gmane.org/gmane.comp.version-control.git/273569
The previous RFC version is here:
http://thread.gmane.org/gmane.comp.version-control.git/272654
Changes in this version:
* Cleanup Documentation/tag
* Fixed grammatical errors
* Fixed a small merge conflict
* Other small changes
Documentation/git-tag.txt | 39 ++++++++++---
builtin/for-each-ref.c | 3 +-
builtin/tag.c | 368
++++++++++++++++++++------------------------------------------------------------------------------------------------
ref-filter.c | 95 ++++++++++++++++++++++++++++--
ref-filter.h | 7 ++-
t/t7004-tag.sh | 51 +++++++++++++---
6 files changed, 234 insertions(+), 329 deletions(-)
--
Regards,
Karthik Nayak
Add support for %(refname:shortalign=X) where X is a number.
This will print a shortened refname aligned to the left
followed by spaces for a total length of X characters.
If X is less than the shortened refname size, the entire
shortened refname is printed.
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
ref-filter.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
Add support to sort by version using the "v:refname" and
"version:refname" option. This is achieved by using the
'version_cmp()' function as the comparing function for qsort.
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
ref-filter.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
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 [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
builtin/tag.c | 106 +++++++++++++++++++++++++++++++---------------------------
1 file changed, 57 insertions(+), 49 deletions(-)
@@ -574,16 +579,16 @@ int cmd_tag(int argc, const char **argv, const char *prefix)constchar*object_ref,*tag;structcreate_tag_optionsopt;char*cleanup_arg=NULL;-intannotate=0,force=0,lines=-1;+intannotate=0,force=0;intcmdmode=0;constchar*msgfile=NULL,*keyid=NULL;structmsg_argmsg={0,STRBUF_INIT};-structcommit_list*with_commit=NULL;structref_transaction*transaction;structstrbuferr=STRBUF_INIT;+structref_filterfilter;structoptionoptions[]={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'),
@@ -604,14 +609,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()
@@ -636,7 +643,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;
@@ -649,18 +656,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();returnret;}-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')returnfor_each_tag_name(argv,delete_tag);
Add an option in 'filter_refs()' to use 'for_each_tag_ref()'
and filter refs. This type checking is done by adding a
'FILTER_REFS_TAGS' in 'ref-filter.h'
Mentored-by: Christian Couder [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
ref-filter.c | 2 ++
ref-filter.h | 1 +
2 files changed, 3 insertions(+)
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 [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
builtin/for-each-ref.c | 2 +-
builtin/tag.c | 4 ++++
ref-filter.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
ref-filter.h | 3 ++-
4 files changed, 54 insertions(+), 3 deletions(-)
@@ -1273,7 +1273,48 @@ static void emit(const char *cp, const char *ep)}}-voidshow_ref_array_item(structref_array_item*info,constchar*format,intquote_style)+/* Print 'lines' no of lines of a given oid */+staticvoidshow_tag_lines(conststructobject_id*oid,intlines)+{+inti;+unsignedlongsize;+enumobject_typetype;+char*buf,*sp,*eol;+size_tlen;++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)+gotofree_return;+if(!size)+die("an empty %s object %s?",+typename(type),oid_to_hex(oid));++/* skip header */+sp=strstr(buf,"\n\n");+if(!sp)+gotofree_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);+}++voidshow_ref_array_item(structref_array_item*info,constchar*format,+intquote_style,unsignedintlines){constchar*cp,*sp,*ep;
@@ -87,7 +88,7 @@ int verify_ref_format(const char *format);/* Sort the given ref_array as per the ref_sorting provided */voidref_array_sort(structref_sorting*sort,structref_array*array);/* Print the ref using the given format and quote_style */-voidshow_ref_array_item(structref_array_item*info,constchar*format,intquote_style);+voidshow_ref_array_item(structref_array_item*info,constchar*format,intquote_style,unsignedintlines);/* Callback function for parsing the sort option */intparse_opt_ref_sorting(conststructoption*opt,constchar*arg,intunset);/* Default sort option based on refname */
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 [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
Documentation/git-tag.txt | 16 ++-
builtin/tag.c | 343
++++++----------------------------------------
t/t7004-tag.sh | 8 +-
3 files changed, 50 insertions(+), 317 deletions(-)
@@ -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].
@@ -28,278 +28,34 @@ 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--staticinttag_sort;-staticunsignedintcolopts;-staticintmatch_pattern(constchar**patterns,constchar*ref)-{-/* no pattern means match everything */-if(!*patterns)-return1;-for(;*patterns;patterns++)-if(!wildmatch(*patterns,ref,0,NULL))-return1;-return0;-}--/*-*Thisiscurrentlyduplicatedinref-filter.c,andwilleventuallybe-*removedasweporttag.ctousetheref-filterAPIs.-*/-staticconstunsignedchar*match_points_at(constchar*refname,-constunsignedchar*sha1,-structsha1_array*points_at)-{-constunsignedchar*tagged_sha1=NULL;-structobject*obj;--if(sha1_array_lookup(points_at,sha1)>=0)-returnsha1;-obj=parse_object(sha1);-if(!obj)-die(_("malformed object at '%s'"),refname);-if(obj->type==OBJ_TAG)-tagged_sha1=((structtag*)obj)->tagged->sha1;-if(tagged_sha1&&sha1_array_lookup(points_at,tagged_sha1)>=0)-returntagged_sha1;-returnNULL;-}--staticintin_commit_list(conststructcommit_list*want,structcommit*c)-{-for(;want;want=want->next)-if(!hashcmp(want->item->object.sha1,c->object.sha1))-return1;-return0;-}--/*-*Theentirecodesegmentforsupportingthe--containsoptionhasbeen-*copiedovertoref-filter.{c,h}.Thiswillbedeletedevetuallywhen-*weporttag.ctouseref-filterAPIs.-*/-enumcontains_result{-CONTAINS_UNKNOWN=-1,-CONTAINS_NO=0,-CONTAINS_YES=1-};--/*-*Testwhetherthecandidateoroneofitsparentsiscontainedinthe
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 dupplicated 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 = "%(refname:shortalign=16)";
+ else
+ format = "%(refname:short)";
+
+ verify_ref_format(format);
+ if (!sorting)
+ sorting = ref_default_sorting();
+ 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 +122,23 @@ 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++;
+ }
- *sort = (type | flags);
+ len = strlen(arg);
+ s->atom = parse_ref_filter_atom(arg, arg+len);
return 0;
}
@@ -402,11 +146,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 +309,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb,
*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
*prefix)
die(_("--column and -n are incompatible"));
colopts = 0;
}
+ if (!sorting)
+ sorting = ref_default_sorting();
if (cmdmode == 'l') {
int ret;
if (column_active(colopts)) {
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 [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
Documentation/git-tag.txt | 16 ++-
builtin/tag.c | 343
++++++----------------------------------------
t/t7004-tag.sh | 8 +-
3 files changed, 50 insertions(+), 317 deletions(-)
@@ -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].
@@ -28,278 +28,34 @@ 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--staticinttag_sort;-staticunsignedintcolopts;-staticintmatch_pattern(constchar**patterns,constchar*ref)-{-/* no pattern means match everything */-if(!*patterns)-return1;-for(;*patterns;patterns++)-if(!wildmatch(*patterns,ref,0,NULL))-return1;-return0;-}--/*-*Thisiscurrentlyduplicatedinref-filter.c,andwilleventuallybe-*removedasweporttag.ctousetheref-filterAPIs.-*/-staticconstunsignedchar*match_points_at(constchar*refname,-constunsignedchar*sha1,-structsha1_array*points_at)-{-constunsignedchar*tagged_sha1=NULL;-structobject*obj;--if(sha1_array_lookup(points_at,sha1)>=0)-returnsha1;-obj=parse_object(sha1);-if(!obj)-die(_("malformed object at '%s'"),refname);-if(obj->type==OBJ_TAG)-tagged_sha1=((structtag*)obj)->tagged->sha1;-if(tagged_sha1&&sha1_array_lookup(points_at,tagged_sha1)>=0)-returntagged_sha1;-returnNULL;-}--staticintin_commit_list(conststructcommit_list*want,structcommit*c)-{-for(;want;want=want->next)-if(!hashcmp(want->item->object.sha1,c->object.sha1))-return1;-return0;-}--/*-*Theentirecodesegmentforsupportingthe--containsoptionhasbeen-*copiedovertoref-filter.{c,h}.Thiswillbedeletedevetuallywhen-*weporttag.ctouseref-filterAPIs.-*/-enumcontains_result{-CONTAINS_UNKNOWN=-1,-CONTAINS_NO=0,-CONTAINS_YES=1-};--/*-*Testwhetherthecandidateoroneofitsparentsiscontainedinthe
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 dupplicated 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 = "%(refname:shortalign=16)";
+ else
+ format = "%(refname:short)";
+
+ verify_ref_format(format);
+ if (!sorting)
+ sorting = ref_default_sorting();
+ 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 +122,23 @@ 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++;
+ }
- *sort = (type | flags);
+ len = strlen(arg);
+ s->atom = parse_ref_filter_atom(arg, arg+len);
return 0;
}
@@ -402,11 +146,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 +309,6 @@ static int strbuf_check_tag_ref(struct strbuf *sb,
*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
*prefix)
die(_("--column and -n are incompatible"));
colopts = 0;
}
+ if (!sorting)
+ sorting = ref_default_sorting();
if (cmdmode == 'l') {
int ret;
if (column_active(colopts)) {
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 [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
Documentation/git-tag.txt | 16 +++++++++++++++-
builtin/tag.c | 11 +++++++----
t/t7004-tag.sh | 16 ++++++++++++++++
3 files changed, 38 insertions(+), 5 deletions(-)
@@ -155,6 +156,19 @@ 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. 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
+ `%(objectname) SPC %(objecttype) TAB %(refname)`.
+ 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`.
+
CONFIGURATION
-------------
@@ -43,7 +42,7 @@ static int list_tags(struct ref_filter *filter, struct
ref_sorting *sorting)
if (filter->lines)
format = "%(refname:shortalign=16)";
- else
+ else if (!format)
format = "%(refname:short)";
verify_ref_format(format);
*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()
};
@@ -1507,4 +1507,20 @@ EOF"test_cmpexpectactual'+test_expect_success'--format cannot be used with -n''+test_must_failgittag-l-n4--format="%(refname)"+'++test_expect_success'--format should list tags as per format given''+cat>expect<<-\EOF&&+foo1.10+foo1.3+foo1.6+foo1.6-rc1+foo1.6-rc2+EOF+gittag-l--format="%(refname)""foo*">actual&&+test_cmpexpectactual+'+test_done
Using 'ref-filter' APIs 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 [off-list ref]
Mentored-by: Matthieu Moy [off-list ref]
Signed-off-by: Karthik Nayak <redacted>
---
Documentation/git-tag.txt | 10 +++++++++-
builtin/tag.c | 6 +++++-
t/t7004-tag.sh | 27 +++++++++++++++++++++++++++
3 files changed, 41 insertions(+), 2 deletions(-)
@@ -169,6 +169,14 @@ This option is only applicable when listing tags
without annotation lines.
`%09` to `\t` (TAB) and `%0a` to `\n` (LF).
The fields are same as those in `git for-each-ref`.
+--merged [<commit>]::
+ Only list tags whose tips are reachable from the
+ specified commit (HEAD if not specified).
+
+--no-merged [<commit>]::
+ Only list tags whose tips are not reachable from the
+ specified commit (HEAD if not specified).
+
CONFIGURATION
-------------
*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),
{
*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')
As this patch could be applied directly to master and to maint maybe
you could send it at the top of this patch series or alone outside of
this patch series.
As this patch could be applied directly to master and to maint maybe
you could send it at the top of this patch series or alone outside of
this patch series.
+ if (filter->lines)
+ format = "%(refname:shortalign=16)";
+ else
+ format = "%(refname:short)";
I can see this is a faithful conversion, but this looks line an
opportunity to avoid this special limit 15/16. Even on git.git "git
tag -l -n1" already breaks alignment with *.msysgit.* tags (ok maybe
msysgit, not purely git.git) When you get to "branch -l", it
calculates the max-width automatically so you probably need
"%(refname:shortalign)" any way. "shortalign" (i.e. create the "align"
version for every modifier) does not look good because it could double
the number of modifiers and let's not thinking about truncation
options or right alignment..
--
Duy
+ if (filter->lines)
+ format = "%(refname:shortalign=16)";
+ else
+ format = "%(refname:short)";
I can see this is a faithful conversion, but this looks line an
opportunity to avoid this special limit 15/16. Even on git.git "git
tag -l -n1" already breaks alignment with *.msysgit.* tags (ok maybe
msysgit, not purely git.git) When you get to "branch -l", it
calculates the max-width automatically so you probably need
"%(refname:shortalign)" any way. "shortalign" (i.e. create the "align"
version for every modifier) does not look good because it could double
the number of modifiers and let's not thinking about truncation
options or right alignment..
What I was thinking of was getting rid of the whole "align" feature where
you provide a value to which it would align.
Something like: --format="%(item:modifieralign)" which would use something
on the lines of what the max-width calculator in branch -l uses, to get the max
alignment size. But the problem is that ref-filter goes through the refs using
a function which has no connections with the atoms used. So a more practical
solution would be --format="%(item:modifieralign=X)" where we could provide a
means of calculating X via ref-filter. Something like this in tag.c:
int max_width = get_max_width("<item to get max_width of>");
use this max_width to then do a
--format="%(item:modifieralign=X)", where X = max_width
What do you think?
--
Regards,
Karthik Nayak
On Mon, Jul 13, 2015 at 2:36 AM, Karthik Nayak [off-list ref] wrote:
What I was thinking of was getting rid of the whole "align" feature where
you provide a value to which it would align.
Something like: --format="%(item:modifieralign)" which would use something
on the lines of what the max-width calculator in branch -l uses, to get the max
alignment size. But the problem is that ref-filter goes through the refs using
a function which has no connections with the atoms used. So a more practical
solution would be --format="%(item:modifieralign=X)" where we could provide a
means of calculating X via ref-filter. Something like this in tag.c:
int max_width = get_max_width("<item to get max_width of>");
use this max_width to then do a
--format="%(item:modifieralign=X)", where X = max_width
What do you think?
This is where separate "alignment atoms" (instead of alignment
modifiers) make sense. Suppose you introduce another function, let's
say print_all() for now, to wrap the "for (i < maxcount)" loop at the
end of for-each-ref, you would have total control over display and
formatting. populate_value() generates empty strings for these
alignment atoms (because they don't really have true values). Those
alignment atoms are recognized in print_all() and
show_ref_array_item(). In print_all(), if it sees max width needs to
be calculated (because the user does not specify the width), it can
call populate_value() for an atom for all rows. show_ref_array_item()
does the padding and even truncating if needed. This pattern is
similar to how print_columns() works, first we collect data of the
whole "table", then we place them line by line.
It sounds good to me. But it may not be the best option, I don't know.
And it may create unnecessary work. So you and your mentors decide.
--
Duy
On Mon, Jul 13, 2015 at 4:16 PM, Duy Nguyen [off-list ref] wrote:
On Mon, Jul 13, 2015 at 2:36 AM, Karthik Nayak [off-list ref] wrote:
quoted
What I was thinking of was getting rid of the whole "align" feature where
you provide a value to which it would align.
Something like: --format="%(item:modifieralign)" which would use something
on the lines of what the max-width calculator in branch -l uses, to get the max
alignment size. But the problem is that ref-filter goes through the refs using
a function which has no connections with the atoms used. So a more practical
solution would be --format="%(item:modifieralign=X)" where we could provide a
means of calculating X via ref-filter. Something like this in tag.c:
int max_width = get_max_width("<item to get max_width of>");
use this max_width to then do a
--format="%(item:modifieralign=X)", where X = max_width
What do you think?
This is where separate "alignment atoms" (instead of alignment
modifiers) make sense. Suppose you introduce another function, let's
say print_all() for now, to wrap the "for (i < maxcount)" loop at the
end of for-each-ref, you would have total control over display and
formatting. populate_value() generates empty strings for these
alignment atoms (because they don't really have true values). Those
alignment atoms are recognized in print_all() and
show_ref_array_item(). In print_all(), if it sees max width needs to
be calculated (because the user does not specify the width), it can
call populate_value() for an atom for all rows. show_ref_array_item()
does the padding and even truncating if needed. This pattern is
similar to how print_columns() works, first we collect data of the
whole "table", then we place them line by line.
It sounds good to me. But it may not be the best option, I don't know.
And it may create unnecessary work. So you and your mentors decide.
--
Duy
Sounds good, but what you're saying goes on the lines of interdependence of
atoms, since we would have separate atoms. Not sure we want to do that right
now.
--
Regards,
Karthik Nayak