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
|
Change output color. Followed by `:<colorname>`, where names
|
||||||
are described in `color.branch.*`.
|
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
|
In addition to the above, for commit and tag objects, the header
|
||||||
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
|
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
|
||||||
be used to specify the value in the header field.
|
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
|
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
|
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
|
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
|
For sorting purposes, fields with numeric values sort in numeric
|
||||||
order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
|
order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
|
||||||
All other fields are used to sort in their byte-value order.
|
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
|
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
|
the object referred by the ref does not cause an error. It
|
||||||
returns an empty string instead.
|
returns an empty string instead.
|
||||||
|
@ -13,7 +13,8 @@ SYNOPSIS
|
|||||||
<tagname> [<commit> | <object>]
|
<tagname> [<commit> | <object>]
|
||||||
'git tag' -d <tagname>...
|
'git tag' -d <tagname>...
|
||||||
'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
|
'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>...
|
'git tag' -v <tagname>...
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
@ -94,14 +95,16 @@ OPTIONS
|
|||||||
using fnmatch(3)). Multiple patterns may be given; if any of
|
using fnmatch(3)). Multiple patterns may be given; if any of
|
||||||
them matches, the tag is shown.
|
them matches, the tag is shown.
|
||||||
|
|
||||||
--sort=<type>::
|
--sort=<key>::
|
||||||
Sort in a specific order. Supported type is "refname"
|
Sort based on the key given. Prefix `-` to sort in
|
||||||
(lexicographic order), "version:refname" or "v:refname" (tag
|
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
|
names are treated as versions). The "version:refname" sort
|
||||||
order can also be affected by the
|
order can also be affected by the
|
||||||
"versionsort.prereleaseSuffix" configuration variable. Prepend
|
"versionsort.prereleaseSuffix" configuration variable.
|
||||||
"-" to reverse sort order. When this option is not given, the
|
The keys supported are the same as those in `git for-each-ref`.
|
||||||
sort order defaults to the value configured for the 'tag.sort'
|
Sort order defaults to the value configured for the 'tag.sort'
|
||||||
variable if it exists, or lexicographic order otherwise. See
|
variable if it exists, or lexicographic order otherwise. See
|
||||||
linkgit:git-config[1].
|
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.
|
The object that the new tag will refer to, usually a commit.
|
||||||
Defaults to HEAD.
|
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
|
CONFIGURATION
|
||||||
-------------
|
-------------
|
||||||
|
@ -68,6 +68,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
|
|
||||||
filter.name_patterns = argv;
|
filter.name_patterns = argv;
|
||||||
|
filter.match_as_path = 1;
|
||||||
filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
|
filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
|
||||||
ref_array_sort(sorting, &array);
|
ref_array_sort(sorting, &array);
|
||||||
|
|
||||||
|
370
builtin/tag.c
370
builtin/tag.c
@ -17,280 +17,49 @@
|
|||||||
#include "gpg-interface.h"
|
#include "gpg-interface.h"
|
||||||
#include "sha1-array.h"
|
#include "sha1-array.h"
|
||||||
#include "column.h"
|
#include "column.h"
|
||||||
|
#include "ref-filter.h"
|
||||||
|
|
||||||
static const char * const git_tag_usage[] = {
|
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 [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"),
|
||||||
N_("git tag -d <tagname>..."),
|
N_("git tag -d <tagname>..."),
|
||||||
N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
|
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>..."),
|
N_("git tag -v <tagname>..."),
|
||||||
NULL
|
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 unsigned int colopts;
|
||||||
|
|
||||||
static int match_pattern(const char **patterns, const char *ref)
|
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
|
||||||
{
|
|
||||||
/* 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)
|
|
||||||
{
|
{
|
||||||
|
struct ref_array array;
|
||||||
|
char *to_free = NULL;
|
||||||
int i;
|
int i;
|
||||||
unsigned long size;
|
|
||||||
enum object_type type;
|
|
||||||
char *buf, *sp, *eol;
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
buf = read_sha1_file(oid->hash, &type, &size);
|
memset(&array, 0, sizeof(array));
|
||||||
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 */
|
if (filter->lines == -1)
|
||||||
sp = strstr(buf, "\n\n");
|
filter->lines = 0;
|
||||||
if (!sp)
|
|
||||||
goto free_return;
|
|
||||||
|
|
||||||
/* only take up to "lines" lines, and strip the signature from a tag */
|
if (!format) {
|
||||||
if (type == OBJ_TAG)
|
if (filter->lines) {
|
||||||
size = parse_signature(buf, size);
|
to_free = xstrfmt("%s %%(contents:lines=%d)",
|
||||||
for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
|
"%(align:15)%(refname:short)%(end)",
|
||||||
if (i)
|
filter->lines);
|
||||||
printf("\n ");
|
format = to_free;
|
||||||
eol = memchr(sp, '\n', size - (sp - buf));
|
} else
|
||||||
len = eol ? eol - sp : size - (sp - buf);
|
format = "%(refname:short)";
|
||||||
fwrite(sp, len, 1, stdout);
|
|
||||||
if (!eol)
|
|
||||||
break;
|
|
||||||
sp = eol + 1;
|
|
||||||
}
|
|
||||||
free_return:
|
|
||||||
free(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int show_reference(const char *refname, const struct object_id *oid,
|
verify_ref_format(format);
|
||||||
int flag, void *cb_data)
|
filter_refs(&array, filter, FILTER_REFS_TAGS);
|
||||||
{
|
ref_array_sort(sorting, &array);
|
||||||
struct tag_filter *filter = cb_data;
|
|
||||||
|
|
||||||
if (match_pattern(filter->patterns, refname)) {
|
for (i = 0; i < array.nr; i++)
|
||||||
if (filter->with_commit) {
|
show_ref_array_item(array.items[i], format, 0);
|
||||||
struct commit *commit;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,35 +126,26 @@ static const char tag_template_nocleanup[] =
|
|||||||
"Lines starting with '%c' will be kept; you may remove them"
|
"Lines starting with '%c' will be kept; you may remove them"
|
||||||
" yourself if you want to.\n");
|
" yourself if you want to.\n");
|
||||||
|
|
||||||
/*
|
/* Parse arg given and add it the ref_sorting array */
|
||||||
* Parse a sort string, and return 0 if parsed successfully. Will return
|
static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
int type = 0, flags = 0;
|
struct ref_sorting *s;
|
||||||
|
int len;
|
||||||
|
|
||||||
if (skip_prefix(arg, "-", &arg))
|
s = xcalloc(1, sizeof(*s));
|
||||||
flags |= REVERSE_SORT;
|
s->next = *sorting_tail;
|
||||||
|
*sorting_tail = s;
|
||||||
|
|
||||||
if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
|
if (*arg == '-') {
|
||||||
type = VERCMP_SORT;
|
s->reverse = 1;
|
||||||
else
|
arg++;
|
||||||
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 (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;
|
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)
|
static int git_tag_config(const char *var, const char *value, void *cb)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
|
struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
|
||||||
|
|
||||||
if (!strcmp(var, "tag.sort")) {
|
if (!strcmp(var, "tag.sort")) {
|
||||||
if (!value)
|
if (!value)
|
||||||
return config_error_nonbool(var);
|
return config_error_nonbool(var);
|
||||||
parse_sort_string(var, value, &tag_sort);
|
parse_sorting_string(value, sorting_tail);
|
||||||
return 0;
|
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);
|
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)
|
int cmd_tag(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct strbuf buf = STRBUF_INIT;
|
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;
|
const char *object_ref, *tag;
|
||||||
struct create_tag_options opt;
|
struct create_tag_options opt;
|
||||||
char *cleanup_arg = NULL;
|
char *cleanup_arg = NULL;
|
||||||
int annotate = 0, force = 0, lines = -1;
|
|
||||||
int create_reflog = 0;
|
int create_reflog = 0;
|
||||||
|
int annotate = 0, force = 0;
|
||||||
int cmdmode = 0;
|
int cmdmode = 0;
|
||||||
const char *msgfile = NULL, *keyid = NULL;
|
const char *msgfile = NULL, *keyid = NULL;
|
||||||
struct msg_arg msg = { 0, STRBUF_INIT };
|
struct msg_arg msg = { 0, STRBUF_INIT };
|
||||||
struct commit_list *with_commit = NULL;
|
|
||||||
struct ref_transaction *transaction;
|
struct ref_transaction *transaction;
|
||||||
struct strbuf err = STRBUF_INIT;
|
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[] = {
|
struct option options[] = {
|
||||||
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
|
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"),
|
N_("print <n> lines of each tag message"),
|
||||||
PARSE_OPT_OPTARG, NULL, 1 },
|
PARSE_OPT_OPTARG, NULL, 1 },
|
||||||
OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
|
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_GROUP(N_("Tag listing options")),
|
||||||
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
|
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
|
||||||
OPT_CONTAINS(&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(&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"),
|
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
|
||||||
PARSE_OPT_NONEG, parse_opt_sort
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OPTION_CALLBACK, 0, "points-at", &points_at, N_("object"),
|
|
||||||
N_("print only tags of the object"), 0, parse_opt_object_name
|
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()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
git_config(git_tag_config, NULL);
|
git_config(git_tag_config, sorting_tail);
|
||||||
|
|
||||||
memset(&opt, 0, sizeof(opt));
|
memset(&opt, 0, sizeof(opt));
|
||||||
|
memset(&filter, 0, sizeof(filter));
|
||||||
|
filter.lines = -1;
|
||||||
|
|
||||||
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
|
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);
|
usage_with_options(git_tag_usage, options);
|
||||||
|
|
||||||
finalize_colopts(&colopts, -1);
|
finalize_colopts(&colopts, -1);
|
||||||
if (cmdmode == 'l' && lines != -1) {
|
if (cmdmode == 'l' && filter.lines != -1) {
|
||||||
if (explicitly_enable_column(colopts))
|
if (explicitly_enable_column(colopts))
|
||||||
die(_("--column and -n are incompatible"));
|
die(_("--column and -n are incompatible"));
|
||||||
colopts = 0;
|
colopts = 0;
|
||||||
}
|
}
|
||||||
|
if (!sorting)
|
||||||
|
sorting = ref_default_sorting();
|
||||||
if (cmdmode == 'l') {
|
if (cmdmode == 'l') {
|
||||||
int ret;
|
int ret;
|
||||||
if (column_active(colopts)) {
|
if (column_active(colopts)) {
|
||||||
@ -647,19 +408,20 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
|
|||||||
copts.padding = 2;
|
copts.padding = 2;
|
||||||
run_column_filter(colopts, &copts);
|
run_column_filter(colopts, &copts);
|
||||||
}
|
}
|
||||||
if (lines != -1 && tag_sort)
|
filter.name_patterns = argv;
|
||||||
die(_("--sort and -n are incompatible"));
|
ret = list_tags(&filter, sorting, format);
|
||||||
ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
|
|
||||||
if (column_active(colopts))
|
if (column_active(colopts))
|
||||||
stop_column_filter();
|
stop_column_filter();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (lines != -1)
|
if (filter.lines != -1)
|
||||||
die(_("-n option is only allowed with -l."));
|
die(_("-n option is only allowed with -l."));
|
||||||
if (with_commit)
|
if (filter.with_commit)
|
||||||
die(_("--contains option is only allowed with -l."));
|
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."));
|
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')
|
if (cmdmode == 'd')
|
||||||
return for_each_tag_name(argv, delete_tag);
|
return for_each_tag_name(argv, delete_tag);
|
||||||
if (cmdmode == 'v')
|
if (cmdmode == 'v')
|
||||||
|
@ -814,6 +814,9 @@ static inline int strtoul_ui(char const *s, int base, unsigned int *result)
|
|||||||
char *p;
|
char *p;
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
/* negative values would be accepted by strtoul */
|
||||||
|
if (strchr(s, '-'))
|
||||||
|
return -1;
|
||||||
ul = strtoul(s, &p, base);
|
ul = strtoul(s, &p, base);
|
||||||
if (errno || *p || p == s || (unsigned int) ul != ul)
|
if (errno || *p || p == s || (unsigned int) ul != ul)
|
||||||
return -1;
|
return -1;
|
||||||
|
415
ref-filter.c
415
ref-filter.c
@ -10,6 +10,9 @@
|
|||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
#include "ref-filter.h"
|
#include "ref-filter.h"
|
||||||
#include "revision.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;
|
typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
|
||||||
|
|
||||||
@ -44,15 +47,48 @@ static struct {
|
|||||||
{ "subject" },
|
{ "subject" },
|
||||||
{ "body" },
|
{ "body" },
|
||||||
{ "contents" },
|
{ "contents" },
|
||||||
{ "contents:subject" },
|
|
||||||
{ "contents:body" },
|
|
||||||
{ "contents:signature" },
|
|
||||||
{ "upstream" },
|
{ "upstream" },
|
||||||
{ "push" },
|
{ "push" },
|
||||||
{ "symref" },
|
{ "symref" },
|
||||||
{ "flag" },
|
{ "flag" },
|
||||||
{ "HEAD" },
|
{ "HEAD" },
|
||||||
{ "color" },
|
{ "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;
|
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).
|
* 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;
|
*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 */
|
/* See grab_values */
|
||||||
static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
|
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++) {
|
for (i = 0; i < used_atom_cnt; i++) {
|
||||||
const char *name = used_atom[i];
|
const char *name = used_atom[i];
|
||||||
struct atom_value *v = &val[i];
|
struct atom_value *v = &val[i];
|
||||||
|
const char *valp = NULL;
|
||||||
if (!!deref != (*name == '*'))
|
if (!!deref != (*name == '*'))
|
||||||
continue;
|
continue;
|
||||||
if (deref)
|
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") &&
|
||||||
strcmp(name, "contents:subject") &&
|
strcmp(name, "contents:subject") &&
|
||||||
strcmp(name, "contents:body") &&
|
strcmp(name, "contents:body") &&
|
||||||
strcmp(name, "contents:signature"))
|
strcmp(name, "contents:signature") &&
|
||||||
|
!starts_with(name, "contents:lines="))
|
||||||
continue;
|
continue;
|
||||||
if (!subpos)
|
if (!subpos)
|
||||||
find_subpos(buf, sz,
|
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);
|
v->s = xmemdupz(sigpos, siglen);
|
||||||
else if (!strcmp(name, "contents"))
|
else if (!strcmp(name, "contents"))
|
||||||
v->s = xstrdup(subpos);
|
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;
|
int deref = 0;
|
||||||
const char *refname;
|
const char *refname;
|
||||||
const char *formatp;
|
const char *formatp;
|
||||||
|
const char *valp;
|
||||||
struct branch *branch = NULL;
|
struct branch *branch = NULL;
|
||||||
|
|
||||||
|
v->handler = append_atom;
|
||||||
|
|
||||||
if (*name == '*') {
|
if (*name == '*') {
|
||||||
deref = 1;
|
deref = 1;
|
||||||
name++;
|
name++;
|
||||||
@ -654,10 +843,12 @@ static void populate_value(struct ref_array_item *ref)
|
|||||||
refname = branch_get_push(branch, NULL);
|
refname = branch_get_push(branch, NULL);
|
||||||
if (!refname)
|
if (!refname)
|
||||||
continue;
|
continue;
|
||||||
} else if (starts_with(name, "color:")) {
|
} else if (match_atom_name(name, "color", &valp)) {
|
||||||
char color[COLOR_MAXLEN] = "";
|
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"));
|
die(_("unable to parse format"));
|
||||||
v->s = xstrdup(color);
|
v->s = xstrdup(color);
|
||||||
continue;
|
continue;
|
||||||
@ -687,6 +878,48 @@ static void populate_value(struct ref_array_item *ref)
|
|||||||
else
|
else
|
||||||
v->s = " ";
|
v->s = " ";
|
||||||
continue;
|
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
|
} else
|
||||||
continue;
|
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 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.
|
* 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"
|
* 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 a pattern "refs/heads/" but not "refs/heads/m") or a
|
||||||
* matches "refs/heads/m*",too).
|
* wildcard (e.g. the same ref matches "refs/heads/m*", too).
|
||||||
*/
|
*/
|
||||||
static int match_name_as_path(const char **pattern, const char *refname)
|
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 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
|
* 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
|
* 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;
|
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
|
* A call-back given to for_each_ref(). Filter refs and keep them for
|
||||||
* later object processing.
|
* 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_filter *filter = ref_cbdata->filter;
|
||||||
struct ref_array_item *ref;
|
struct ref_array_item *ref;
|
||||||
struct commit *commit = NULL;
|
struct commit *commit = NULL;
|
||||||
|
unsigned int kind;
|
||||||
|
|
||||||
if (flag & REF_BAD_NAME) {
|
if (flag & REF_BAD_NAME) {
|
||||||
warning("ignoring ref with broken name %s", refname);
|
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;
|
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;
|
return 0;
|
||||||
|
|
||||||
if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname))
|
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);
|
REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1);
|
||||||
ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
|
ref_cbdata->array->items[ref_cbdata->array->nr++] = ref;
|
||||||
|
ref->kind = kind;
|
||||||
return 0;
|
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;
|
struct ref_filter_cbdata ref_cbdata;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
unsigned int broken = 0;
|
||||||
|
|
||||||
ref_cbdata.array = array;
|
ref_cbdata.array = array;
|
||||||
ref_cbdata.filter = filter;
|
ref_cbdata.filter = filter;
|
||||||
|
|
||||||
|
if (type & FILTER_REFS_INCLUDE_BROKEN)
|
||||||
|
broken = 1;
|
||||||
|
filter->kind = type & FILTER_REFS_KIND_MASK;
|
||||||
|
|
||||||
/* Simple per-ref filtering */
|
/* Simple per-ref filtering */
|
||||||
if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN))
|
if (!filter->kind)
|
||||||
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)
|
|
||||||
die("filter_refs: invalid type");
|
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 */
|
/* Filters that need revision walking */
|
||||||
if (filter->merge_commit)
|
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(a, s->atom, &va);
|
||||||
get_ref_atom_value(b, s->atom, &vb);
|
get_ref_atom_value(b, s->atom, &vb);
|
||||||
switch (cmp_type) {
|
if (s->version)
|
||||||
case FIELD_STR:
|
cmp = versioncmp(va->s, vb->s);
|
||||||
|
else if (cmp_type == FIELD_STR)
|
||||||
cmp = strcmp(va->s, vb->s);
|
cmp = strcmp(va->s, vb->s);
|
||||||
break;
|
else {
|
||||||
default:
|
|
||||||
if (va->ul < vb->ul)
|
if (va->ul < vb->ul)
|
||||||
cmp = -1;
|
cmp = -1;
|
||||||
else if (va->ul == vb->ul)
|
else if (va->ul == vb->ul)
|
||||||
cmp = 0;
|
cmp = 0;
|
||||||
else
|
else
|
||||||
cmp = 1;
|
cmp = 1;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (s->reverse) ? -cmp : cmp;
|
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);
|
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)
|
static int hex1(char ch)
|
||||||
{
|
{
|
||||||
if ('0' <= ch && ch <= '9')
|
if ('0' <= ch && ch <= '9')
|
||||||
@ -1234,8 +1530,10 @@ static int hex2(const char *cp)
|
|||||||
return -1;
|
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)) {
|
while (*cp && (!ep || cp < ep)) {
|
||||||
if (*cp == '%') {
|
if (*cp == '%') {
|
||||||
if (cp[1] == '%')
|
if (cp[1] == '%')
|
||||||
@ -1243,13 +1541,13 @@ static void emit(const char *cp, const char *ep)
|
|||||||
else {
|
else {
|
||||||
int ch = hex2(cp + 1);
|
int ch = hex2(cp + 1);
|
||||||
if (0 <= ch) {
|
if (0 <= ch) {
|
||||||
putchar(ch);
|
strbuf_addch(s, ch);
|
||||||
cp += 3;
|
cp += 3;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
putchar(*cp);
|
strbuf_addch(s, *cp);
|
||||||
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)
|
void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
|
||||||
{
|
{
|
||||||
const char *cp, *sp, *ep;
|
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) {
|
for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
|
||||||
struct atom_value *atomv;
|
struct atom_value *atomv;
|
||||||
|
|
||||||
ep = strchr(sp, ')');
|
ep = strchr(sp, ')');
|
||||||
if (cp < 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);
|
get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv);
|
||||||
print_value(atomv, quote_style);
|
atomv->handler(atomv, &state);
|
||||||
}
|
}
|
||||||
if (*cp) {
|
if (*cp) {
|
||||||
sp = cp + strlen(cp);
|
sp = cp + strlen(cp);
|
||||||
emit(cp, sp);
|
append_literal(cp, sp, &state);
|
||||||
}
|
}
|
||||||
if (need_color_reset_at_eol) {
|
if (need_color_reset_at_eol) {
|
||||||
struct atom_value resetv;
|
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)
|
if (color_parse("reset", color) < 0)
|
||||||
die("BUG: couldn't parse 'reset' as a color");
|
die("BUG: couldn't parse 'reset' as a color");
|
||||||
resetv.s = 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');
|
putchar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1312,6 +1620,9 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset)
|
|||||||
s->reverse = 1;
|
s->reverse = 1;
|
||||||
arg++;
|
arg++;
|
||||||
}
|
}
|
||||||
|
if (skip_prefix(arg, "version:", &arg) ||
|
||||||
|
skip_prefix(arg, "v:", &arg))
|
||||||
|
s->version = 1;
|
||||||
len = strlen(arg);
|
len = strlen(arg);
|
||||||
s->atom = parse_ref_filter_atom(arg, arg+len);
|
s->atom = parse_ref_filter_atom(arg, arg+len);
|
||||||
return 0;
|
return 0;
|
||||||
|
25
ref-filter.h
25
ref-filter.h
@ -13,23 +13,29 @@
|
|||||||
#define QUOTE_PYTHON 4
|
#define QUOTE_PYTHON 4
|
||||||
#define QUOTE_TCL 8
|
#define QUOTE_TCL 8
|
||||||
|
|
||||||
#define FILTER_REFS_INCLUDE_BROKEN 0x1
|
#define FILTER_REFS_INCLUDE_BROKEN 0x0001
|
||||||
#define FILTER_REFS_ALL 0x2
|
#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 {
|
struct atom_value;
|
||||||
const char *s;
|
|
||||||
unsigned long ul; /* used for sorting when not FIELD_STR */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ref_sorting {
|
struct ref_sorting {
|
||||||
struct ref_sorting *next;
|
struct ref_sorting *next;
|
||||||
int atom; /* index into used_atom array (internal) */
|
int atom; /* index into used_atom array (internal) */
|
||||||
unsigned reverse : 1;
|
unsigned reverse : 1,
|
||||||
|
version : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ref_array_item {
|
struct ref_array_item {
|
||||||
unsigned char objectname[20];
|
unsigned char objectname[20];
|
||||||
int flag;
|
int flag;
|
||||||
|
unsigned int kind;
|
||||||
const char *symref;
|
const char *symref;
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
struct atom_value *value;
|
struct atom_value *value;
|
||||||
@ -53,7 +59,10 @@ struct ref_filter {
|
|||||||
} merge;
|
} merge;
|
||||||
struct commit *merge_commit;
|
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 {
|
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);
|
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,
|
int for_each_ref_in_submodule(const char *submodule, const char *prefix,
|
||||||
each_ref_fn fn, void *cb_data)
|
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 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(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_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_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_branch_ref(each_ref_fn fn, void *cb_data);
|
||||||
extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
|
extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
|
||||||
|
@ -81,4 +81,178 @@ test_expect_success 'filtering with --contains' '
|
|||||||
test_cmp expect actual
|
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
|
test_done
|
||||||
|
@ -1462,13 +1462,7 @@ test_expect_success 'invalid sort parameter on command line' '
|
|||||||
|
|
||||||
test_expect_success 'invalid sort parameter in configuratoin' '
|
test_expect_success 'invalid sort parameter in configuratoin' '
|
||||||
git config tag.sort "v:notvalid" &&
|
git config tag.sort "v:notvalid" &&
|
||||||
git tag -l "foo*" >actual &&
|
test_must_fail git tag -l "foo*"
|
||||||
cat >expect <<-\EOF &&
|
|
||||||
foo1.10
|
|
||||||
foo1.3
|
|
||||||
foo1.6
|
|
||||||
EOF
|
|
||||||
test_cmp expect actual
|
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'version sort with prerelease reordering' '
|
test_expect_success 'version sort with prerelease reordering' '
|
||||||
@ -1525,4 +1519,43 @@ EOF"
|
|||||||
test_cmp expect actual
|
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
|
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);
|
*text += strlen(utf8_bom);
|
||||||
return 1;
|
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);
|
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
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user