Merge branch 'kn/for-each-tag'
The "ref-filter" code was taught about many parts of what "tag -l" does and then "tag -l" is being reimplemented in terms of "ref-filter". * kn/for-each-tag: tag.c: implement '--merged' and '--no-merged' options tag.c: implement '--format' option tag.c: use 'ref-filter' APIs tag.c: use 'ref-filter' data structures ref-filter: add option to match literal pattern ref-filter: add support to sort by version ref-filter: add support for %(contents:lines=X) ref-filter: add option to filter out tags, branches and remotes ref-filter: implement an `align` atom ref-filter: introduce match_atom_name() ref-filter: introduce handler function for each atom utf8: add function to align a string into given strbuf ref-filter: introduce ref_formatting_state and ref_formatting_stack ref-filter: move `struct atom_value` to ref-filter.c strtoul_ui: reject negative values
This commit is contained in:
commit
8a54523f0f
@ -127,6 +127,17 @@ 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). The "align:" is followed by `<width>`
|
||||
and `<position>` in any order separated by a comma, where the
|
||||
`<position>` is either left, right or middle, default being
|
||||
left 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, but if nested
|
||||
then only the topmost level performs quoting.
|
||||
|
||||
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.
|
||||
@ -139,12 +150,16 @@ The complete message in a commit and tag object is `contents`.
|
||||
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`.
|
||||
blank line. The optional GPG signature is `contents:signature`. The
|
||||
first `N` lines of the message is obtained using `contents:lines=N`.
|
||||
|
||||
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.
|
||||
|
@ -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] [<pattern>...]
|
||||
[--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>]
|
||||
[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]
|
||||
'git tag' -v <tagname>...
|
||||
|
||||
DESCRIPTION
|
||||
@ -94,14 +95,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].
|
||||
|
||||
@ -156,6 +159,16 @@ 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)`.
|
||||
|
||||
--[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
|
||||
-------------
|
||||
|
@ -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);
|
||||
|
||||
|
370
builtin/tag.c
370
builtin/tag.c
@ -17,280 +17,49 @@
|
||||
#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>]"),
|
||||
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>] [--[no-]merged [<commit>]] [<pattern>...]"),
|
||||
N_("git tag -v <tagname>..."),
|
||||
NULL
|
||||
};
|
||||
|
||||
#define STRCMP_SORT 0 /* must be zero */
|
||||
#define VERCMP_SORT 1
|
||||
#define SORT_MASK 0x7fff
|
||||
#define REVERSE_SORT 0x8000
|
||||
|
||||
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)
|
||||
{
|
||||
/* 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static void show_tag_lines(const struct object_id *oid, int lines)
|
||||
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;
|
||||
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));
|
||||
memset(&array, 0, sizeof(array));
|
||||
|
||||
/* skip header */
|
||||
sp = strstr(buf, "\n\n");
|
||||
if (!sp)
|
||||
goto free_return;
|
||||
if (filter->lines == -1)
|
||||
filter->lines = 0;
|
||||
|
||||
/* 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);
|
||||
if (!format) {
|
||||
if (filter->lines) {
|
||||
to_free = xstrfmt("%s %%(contents:lines=%d)",
|
||||
"%(align:15)%(refname:short)%(end)",
|
||||
filter->lines);
|
||||
format = to_free;
|
||||
} else
|
||||
format = "%(refname:short)";
|
||||
}
|
||||
|
||||
static int show_reference(const char *refname, const struct object_id *oid,
|
||||
int flag, void *cb_data)
|
||||
{
|
||||
struct tag_filter *filter = cb_data;
|
||||
verify_ref_format(format);
|
||||
filter_refs(&array, filter, FILTER_REFS_TAGS);
|
||||
ref_array_sort(sorting, &array);
|
||||
|
||||
if (match_pattern(filter->patterns, refname)) {
|
||||
if (filter->with_commit) {
|
||||
struct commit *commit;
|
||||
for (i = 0; i < array.nr; i++)
|
||||
show_ref_array_item(array.items[i], format, 0);
|
||||
ref_array_clear(&array);
|
||||
free(to_free);
|
||||
|
||||
commit = lookup_commit_reference_gently(oid->hash, 1);
|
||||
if (!commit)
|
||||
return 0;
|
||||
if (!contains(commit, filter->with_commit))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (points_at.nr && !match_points_at(refname, oid->hash))
|
||||
return 0;
|
||||
|
||||
if (!filter->lines) {
|
||||
if (filter->sort)
|
||||
string_list_append(&filter->tags, 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 string_list_item *a = a_;
|
||||
const struct string_list_item *b = b_;
|
||||
return versioncmp(a->string, b->string);
|
||||
}
|
||||
|
||||
static int list_tags(const char **patterns, int lines,
|
||||
struct commit_list *with_commit, int sort)
|
||||
{
|
||||
struct tag_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;
|
||||
|
||||
for_each_tag_ref(show_reference, (void *)&filter);
|
||||
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);
|
||||
if (sort & REVERSE_SORT)
|
||||
for (i = filter.tags.nr - 1; i >= 0; i--)
|
||||
printf("%s\n", filter.tags.items[i].string);
|
||||
else
|
||||
for (i = 0; i < filter.tags.nr; i++)
|
||||
printf("%s\n", filter.tags.items[i].string);
|
||||
string_list_clear(&filter.tags, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -357,35 +126,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;
|
||||
struct ref_sorting *s;
|
||||
int len;
|
||||
|
||||
if (skip_prefix(arg, "-", &arg))
|
||||
flags |= REVERSE_SORT;
|
||||
s = xcalloc(1, sizeof(*s));
|
||||
s->next = *sorting_tail;
|
||||
*sorting_tail = s;
|
||||
|
||||
if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
|
||||
type = VERCMP_SORT;
|
||||
else
|
||||
type = STRCMP_SORT;
|
||||
|
||||
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;
|
||||
}
|
||||
@ -393,11 +153,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;
|
||||
}
|
||||
|
||||
@ -555,13 +316,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;
|
||||
@ -570,17 +324,19 @@ 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;
|
||||
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, &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'),
|
||||
@ -602,22 +358,25 @@ 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")),
|
||||
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),
|
||||
{
|
||||
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_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
|
||||
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));
|
||||
filter.lines = -1;
|
||||
|
||||
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
|
||||
|
||||
@ -634,11 +393,13 @@ 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;
|
||||
}
|
||||
if (!sorting)
|
||||
sorting = ref_default_sorting();
|
||||
if (cmdmode == 'l') {
|
||||
int ret;
|
||||
if (column_active(colopts)) {
|
||||
@ -647,19 +408,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
||||
copts.padding = 2;
|
||||
run_column_filter(colopts, &copts);
|
||||
}
|
||||
if (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, sorting, format);
|
||||
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 (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')
|
||||
|
@ -814,6 +814,9 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
|
||||
char *p;
|
||||
|
||||
errno = 0;
|
||||
/* negative values would be accepted by strtoul */
|
||||
if (strchr(s, '-'))
|
||||
return -1;
|
||||
ul = strtoul(s, &p, base);
|
||||
if (errno || *p || p == s || (unsigned int) ul != ul)
|
||||
return -1;
|
||||
|
415
ref-filter.c
415
ref-filter.c
@ -10,6 +10,9 @@
|
||||
#include "quote.h"
|
||||
#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;
|
||||
|
||||
@ -44,15 +47,48 @@ static struct {
|
||||
{ "subject" },
|
||||
{ "body" },
|
||||
{ "contents" },
|
||||
{ "contents:subject" },
|
||||
{ "contents:body" },
|
||||
{ "contents:signature" },
|
||||
{ "upstream" },
|
||||
{ "push" },
|
||||
{ "symref" },
|
||||
{ "flag" },
|
||||
{ "HEAD" },
|
||||
{ "color" },
|
||||
{ "align" },
|
||||
{ "end" },
|
||||
};
|
||||
|
||||
#define REF_FORMATTING_STATE_INIT { 0, NULL }
|
||||
|
||||
struct align {
|
||||
align_type position;
|
||||
unsigned int width;
|
||||
};
|
||||
|
||||
struct contents {
|
||||
unsigned int lines;
|
||||
struct object_id oid;
|
||||
};
|
||||
|
||||
struct ref_formatting_stack {
|
||||
struct ref_formatting_stack *prev;
|
||||
struct strbuf output;
|
||||
void (*at_end)(struct ref_formatting_stack *stack);
|
||||
void *at_end_data;
|
||||
};
|
||||
|
||||
struct ref_formatting_state {
|
||||
int quote_style;
|
||||
struct ref_formatting_stack *stack;
|
||||
};
|
||||
|
||||
struct atom_value {
|
||||
const char *s;
|
||||
union {
|
||||
struct align align;
|
||||
struct contents contents;
|
||||
} u;
|
||||
void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
|
||||
unsigned long ul; /* used for sorting when not FIELD_STR */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -124,6 +160,120 @@ int parse_ref_filter_atom(const char *atom, const char *ep)
|
||||
return at;
|
||||
}
|
||||
|
||||
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 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 push_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, ¤t->output);
|
||||
strbuf_release(¤t->output);
|
||||
free(current);
|
||||
*stack = prev;
|
||||
}
|
||||
|
||||
static void end_align_handler(struct ref_formatting_stack *stack)
|
||||
{
|
||||
struct align *align = (struct align *)stack->at_end_data;
|
||||
struct strbuf s = STRBUF_INIT;
|
||||
|
||||
strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
|
||||
strbuf_swap(&stack->output, &s);
|
||||
strbuf_release(&s);
|
||||
}
|
||||
|
||||
static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
|
||||
{
|
||||
struct ref_formatting_stack *new;
|
||||
|
||||
push_stack_element(&state->stack);
|
||||
new = state->stack;
|
||||
new->at_end = end_align_handler;
|
||||
new->at_end_data = &atomv->u.align;
|
||||
}
|
||||
|
||||
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 corresponding atom"));
|
||||
current->at_end(current);
|
||||
|
||||
/*
|
||||
* Perform quote formatting when the stack element is that of
|
||||
* a supporting atom. If nested then perform quote formatting
|
||||
* only on the topmost supporting atom.
|
||||
*/
|
||||
if (!state->stack->prev->prev) {
|
||||
quote_formatting(&s, current->output.buf, state->quote_style);
|
||||
strbuf_swap(¤t->output, &s);
|
||||
}
|
||||
strbuf_release(&s);
|
||||
pop_stack_element(&state->stack);
|
||||
}
|
||||
|
||||
static int match_atom_name(const char *name, const char *atom_name, const char **val)
|
||||
{
|
||||
const char *body;
|
||||
|
||||
if (!skip_prefix(name, atom_name, &body))
|
||||
return 0; /* doesn't even begin with "atom_name" */
|
||||
if (!body[0]) {
|
||||
*val = NULL; /* %(atom_name) and no customization */
|
||||
return 1;
|
||||
}
|
||||
if (body[0] != ':')
|
||||
return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */
|
||||
*val = body + 1; /* "atom_name:val" */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* In a format string, find the next occurrence of %(atom).
|
||||
*/
|
||||
@ -498,6 +648,30 @@ 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
|
||||
* 'buf' of length 'size' to the given strbuf.
|
||||
*/
|
||||
static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines)
|
||||
{
|
||||
int i;
|
||||
const char *sp, *eol;
|
||||
size_t len;
|
||||
|
||||
sp = buf;
|
||||
|
||||
for (i = 0; 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* See grab_values */
|
||||
static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
|
||||
{
|
||||
@ -508,6 +682,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)
|
||||
@ -517,7 +692,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,
|
||||
@ -537,6 +713,16 @@ 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 strbuf s = STRBUF_INIT;
|
||||
const char *contents_end = bodylen + bodypos - siglen;
|
||||
|
||||
if (strtoul_ui(valp, 10, &v->u.contents.lines))
|
||||
die(_("positive value expected contents:lines=%s"), valp);
|
||||
/* Size is the length of the message after removing the signature */
|
||||
append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines);
|
||||
v->s = strbuf_detach(&s, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -622,8 +808,11 @@ 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;
|
||||
|
||||
v->handler = append_atom;
|
||||
|
||||
if (*name == '*') {
|
||||
deref = 1;
|
||||
name++;
|
||||
@ -654,10 +843,12 @@ static void populate_value(struct ref_array_item *ref)
|
||||
refname = branch_get_push(branch, NULL);
|
||||
if (!refname)
|
||||
continue;
|
||||
} else if (starts_with(name, "color:")) {
|
||||
} else if (match_atom_name(name, "color", &valp)) {
|
||||
char color[COLOR_MAXLEN] = "";
|
||||
|
||||
if (color_parse(name + 6, color) < 0)
|
||||
if (!valp)
|
||||
die(_("expected format: %%(color:<color>)"));
|
||||
if (color_parse(valp, color) < 0)
|
||||
die(_("unable to parse format"));
|
||||
v->s = xstrdup(color);
|
||||
continue;
|
||||
@ -687,6 +878,48 @@ static void populate_value(struct ref_array_item *ref)
|
||||
else
|
||||
v->s = " ";
|
||||
continue;
|
||||
} else if (match_atom_name(name, "align", &valp)) {
|
||||
struct align *align = &v->u.align;
|
||||
struct strbuf **s, **to_free;
|
||||
int width = -1;
|
||||
|
||||
if (!valp)
|
||||
die(_("expected format: %%(align:<width>,<position>)"));
|
||||
|
||||
/*
|
||||
* TODO: Implement a function similar to strbuf_split_str()
|
||||
* which would omit the separator from the end of each value.
|
||||
*/
|
||||
s = to_free = strbuf_split_str(valp, ',', 0);
|
||||
|
||||
align->position = ALIGN_LEFT;
|
||||
|
||||
while (*s) {
|
||||
/* Strip trailing comma */
|
||||
if (s[1])
|
||||
strbuf_setlen(s[0], s[0]->len - 1);
|
||||
if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width))
|
||||
;
|
||||
else if (!strcmp(s[0]->buf, "left"))
|
||||
align->position = ALIGN_LEFT;
|
||||
else if (!strcmp(s[0]->buf, "right"))
|
||||
align->position = ALIGN_RIGHT;
|
||||
else if (!strcmp(s[0]->buf, "middle"))
|
||||
align->position = ALIGN_MIDDLE;
|
||||
else
|
||||
die(_("improper format entered align:%s"), s[0]->buf);
|
||||
s++;
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
die(_("positive width expected with the %%(align) atom"));
|
||||
align->width = width;
|
||||
strbuf_list_free(to_free);
|
||||
v->handler = align_atom_handler;
|
||||
continue;
|
||||
} else if (!strcmp(name, "end")) {
|
||||
v->handler = end_atom_handler;
|
||||
continue;
|
||||
} else
|
||||
continue;
|
||||
|
||||
@ -926,11 +1159,35 @@ static int commit_contains(struct ref_filter *filter, struct commit *commit)
|
||||
return is_descendant_of(commit, filter->with_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)
|
||||
{
|
||||
@ -951,6 +1208,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
|
||||
@ -998,6 +1265,34 @@ 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 ||
|
||||
filter->kind == FILTER_REFS_REMOTES ||
|
||||
filter->kind == FILTER_REFS_TAGS)
|
||||
return filter->kind;
|
||||
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.
|
||||
@ -1008,6 +1303,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);
|
||||
@ -1019,7 +1315,12 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname))
|
||||
/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */
|
||||
kind = filter_ref_kind(filter, refname);
|
||||
if (!(kind & filter->kind))
|
||||
return 0;
|
||||
|
||||
if (!filter_pattern_match(filter, refname))
|
||||
return 0;
|
||||
|
||||
if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
|
||||
@ -1050,6 +1351,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;
|
||||
}
|
||||
|
||||
@ -1126,17 +1428,37 @@ 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;
|
||||
|
||||
if (type & FILTER_REFS_INCLUDE_BROKEN)
|
||||
broken = 1;
|
||||
filter->kind = type & FILTER_REFS_KIND_MASK;
|
||||
|
||||
/* 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 (!filter->kind)
|
||||
die("filter_refs: invalid type");
|
||||
else {
|
||||
/*
|
||||
* For common cases where we need only branches or remotes or tags,
|
||||
* we only iterate through those refs. If a mix of refs is needed,
|
||||
* we iterate over all refs and filter out required refs with the help
|
||||
* of filter_ref_kind().
|
||||
*/
|
||||
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)
|
||||
@ -1153,19 +1475,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;
|
||||
}
|
||||
|
||||
@ -1190,32 +1512,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 print_value(struct atom_value *v, int quote_style)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
switch (quote_style) {
|
||||
case QUOTE_NONE:
|
||||
fputs(v->s, stdout);
|
||||
break;
|
||||
case QUOTE_SHELL:
|
||||
sq_quote_buf(&sb, v->s);
|
||||
break;
|
||||
case QUOTE_PERL:
|
||||
perl_quote_buf(&sb, v->s);
|
||||
break;
|
||||
case QUOTE_PYTHON:
|
||||
python_quote_buf(&sb, v->s);
|
||||
break;
|
||||
case QUOTE_TCL:
|
||||
tcl_quote_buf(&sb, v->s);
|
||||
break;
|
||||
}
|
||||
if (quote_style != QUOTE_NONE) {
|
||||
fputs(sb.buf, stdout);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
}
|
||||
|
||||
static int hex1(char ch)
|
||||
{
|
||||
if ('0' <= ch && ch <= '9')
|
||||
@ -1234,8 +1530,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] == '%')
|
||||
@ -1243,13 +1541,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++;
|
||||
}
|
||||
}
|
||||
@ -1257,19 +1555,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_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);
|
||||
atomv->handler(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;
|
||||
@ -1278,8 +1581,13 @@ 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);
|
||||
}
|
||||
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);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
@ -1312,6 +1620,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;
|
||||
|
25
ref-filter.h
25
ref-filter.h
@ -13,23 +13,29 @@
|
||||
#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
|
||||
#define FILTER_REFS_KIND_MASK (FILTER_REFS_ALL | FILTER_REFS_DETACHED_HEAD)
|
||||
|
||||
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;
|
||||
int atom; /* index into used_atom array (internal) */
|
||||
unsigned reverse : 1;
|
||||
unsigned reverse : 1,
|
||||
version : 1;
|
||||
};
|
||||
|
||||
struct ref_array_item {
|
||||
unsigned char objectname[20];
|
||||
int flag;
|
||||
unsigned int kind;
|
||||
const char *symref;
|
||||
struct commit *commit;
|
||||
struct atom_value *value;
|
||||
@ -53,7 +59,10 @@ 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;
|
||||
};
|
||||
|
||||
struct ref_filter_cbdata {
|
||||
|
9
refs.c
9
refs.c
@ -2131,6 +2131,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)
|
||||
{
|
||||
|
1
refs.h
1
refs.h
@ -173,6 +173,7 @@ 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);
|
||||
|
@ -81,4 +81,178 @@ test_expect_success 'filtering with --contains' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success '%(color) must fail' '
|
||||
test_must_fail git for-each-ref --format="%(color)%(refname)"
|
||||
'
|
||||
|
||||
test_expect_success 'left alignment is default' '
|
||||
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)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:middle,30)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
|
||||
'
|
||||
|
||||
# Individual atoms inside %(align:...) and %(end) must not be quoted.
|
||||
|
||||
test_expect_success 'alignment with format quote' "
|
||||
cat >expect <<-\EOF &&
|
||||
|' '\''master| A U Thor'\'' '|
|
||||
|' '\''side| A U Thor'\'' '|
|
||||
|' '\''odd/spot| A U Thor'\'' '|
|
||||
|' '\''double-tag| '\'' '|
|
||||
|' '\''four| A U Thor'\'' '|
|
||||
|' '\''one| A U Thor'\'' '|
|
||||
|' '\''signed-tag| '\'' '|
|
||||
|' '\''three| A U Thor'\'' '|
|
||||
|' '\''two| A U Thor'\'' '|
|
||||
EOF
|
||||
git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual &&
|
||||
test_cmp expect actual
|
||||
"
|
||||
|
||||
test_expect_success 'nested alignment with quote formatting' "
|
||||
cat >expect <<-\EOF &&
|
||||
|' master '|
|
||||
|' side '|
|
||||
|' odd/spot '|
|
||||
|' double-tag '|
|
||||
|' four '|
|
||||
|' one '|
|
||||
|' signed-tag '|
|
||||
|' three '|
|
||||
|' two '|
|
||||
EOF
|
||||
git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual &&
|
||||
test_cmp expect actual
|
||||
"
|
||||
|
||||
test_expect_success 'check `%(contents:lines=1)`' '
|
||||
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 'check `%(contents:lines=0)`' '
|
||||
cat >expect <<-\EOF &&
|
||||
master |
|
||||
side |
|
||||
odd/spot |
|
||||
double-tag |
|
||||
four |
|
||||
one |
|
||||
signed-tag |
|
||||
three |
|
||||
two |
|
||||
EOF
|
||||
git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'check `%(contents:lines=99999)`' '
|
||||
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=99999)" >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success '`%(contents:lines=-1)` should fail' '
|
||||
test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)"
|
||||
'
|
||||
|
||||
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
|
||||
|
@ -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' '
|
||||
@ -1525,4 +1519,43 @@ 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_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
|
||||
|
21
utf8.c
21
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);
|
||||
}
|
||||
|
15
utf8.h
15
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
|
||||
|
Loading…
Reference in New Issue
Block a user