Merge branch 'hv/ref-filter-misc'

The "--format=" option to the "for-each-ref" command and friends
learned a few more tricks, e.g. the ":short" suffix that applies to
"objectname" now also can be used for "parent", "tree", etc.

* hv/ref-filter-misc:
  ref-filter: add `sanitize` option for 'subject' atom
  pretty: refactor `format_sanitized_subject()`
  ref-filter: add `short` modifier to 'parent' atom
  ref-filter: add `short` modifier to 'tree' atom
  ref-filter: rename `objectname` related functions and fields
  ref-filter: modify error messages in `grab_objectname()`
  ref-filter: refactor `grab_objectname()`
  ref-filter: support different email formats
This commit is contained in:
Junio C Hamano 2020-09-09 13:53:07 -07:00
commit c25fba986b
5 changed files with 161 additions and 65 deletions

View File

@ -222,6 +222,8 @@ worktreepath::
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.
Fields `tree` and `parent` can also be used with modifier `:short` and
`:short=<length>` just like `objectname`.
For commit and tag objects, the special `creatordate` and `creator`
fields will correspond to the appropriate date or name-email-date tuple
@ -230,7 +232,10 @@ These are intended for working on a mix of annotated and lightweight tags.
Fields that have name-email-date tuple as its value (`author`,
`committer`, and `tagger`) can be suffixed with `name`, `email`,
and `date` to extract the named component.
and `date` to extract the named component. For email fields (`authoremail`,
`committeremail` and `taggeremail`), `:trim` can be appended to get the email
without angle brackets, and `:localpart` to get the part before the `@` symbol
out of the trimmed email.
The message in a commit or a tag object is `contents`, from which
`contents:<part>` can be used to extract various parts out of:
@ -242,6 +247,9 @@ contents:subject::
The first paragraph of the message, which typically is a
single line, is taken as the "subject" of the commit or the
tag message.
Instead of `contents:subject`, field `subject` can also be used to
obtain same results. `:sanitize` can be appended to `subject` for
subject line suitable for filename.
contents:body::
The remainder of the commit or the tag message that follows

View File

@ -839,21 +839,22 @@ static int istitlechar(char c)
(c >= '0' && c <= '9') || c == '.' || c == '_';
}
static void format_sanitized_subject(struct strbuf *sb, const char *msg)
void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len)
{
size_t trimlen;
size_t start_len = sb->len;
int space = 2;
int i;
for (; *msg && *msg != '\n'; msg++) {
if (istitlechar(*msg)) {
for (i = 0; i < len; i++) {
if (istitlechar(msg[i])) {
if (space == 1)
strbuf_addch(sb, '-');
space = 0;
strbuf_addch(sb, *msg);
if (*msg == '.')
while (*(msg+1) == '.')
msg++;
strbuf_addch(sb, msg[i]);
if (msg[i] == '.')
while (msg[i+1] == '.')
i++;
} else
space |= 1;
}
@ -1155,7 +1156,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
const struct commit *commit = c->commit;
const char *msg = c->message;
struct commit_list *p;
const char *arg;
const char *arg, *eol;
size_t res;
char **slot;
@ -1405,7 +1406,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
format_subject(sb, msg + c->subject_off, " ");
return 1;
case 'f': /* sanitized subject */
format_sanitized_subject(sb, msg + c->subject_off);
eol = strchrnul(msg + c->subject_off, '\n');
format_sanitized_subject(sb, msg + c->subject_off, eol - (msg + c->subject_off));
return 1;
case 'b': /* body */
strbuf_addstr(sb, msg + c->body_off);

View File

@ -139,4 +139,7 @@ const char *format_subject(struct strbuf *sb, const char *msg,
/* Check if "cmit_fmt" will produce an empty output. */
int commit_format_is_empty(enum cmit_fmt);
/* Make subject of commit message suitable for filename */
void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len);
#endif /* PRETTY_H */

View File

@ -127,8 +127,8 @@ static struct used_atom {
unsigned int nobracket : 1, push : 1, push_remote : 1;
} remote_ref;
struct {
enum { C_BARE, C_BODY, C_BODY_DEP, C_LENGTH,
C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
enum { C_BARE, C_BODY, C_BODY_DEP, C_LENGTH, C_LINES,
C_SIG, C_SUB, C_SUB_SANITIZE, C_TRAILERS } option;
struct process_trailer_options trailer_opts;
unsigned int nlines;
} contents;
@ -139,7 +139,10 @@ static struct used_atom {
struct {
enum { O_FULL, O_LENGTH, O_SHORT } option;
unsigned int length;
} objectname;
} oid;
struct email_option {
enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
} email_option;
struct refname_atom refname;
char *head;
} u;
@ -298,9 +301,12 @@ static int body_atom_parser(const struct ref_format *format, struct used_atom *a
static int subject_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (arg)
return strbuf_addf_ret(err, -1, _("%%(subject) does not take arguments"));
atom->u.contents.option = C_SUB;
if (!arg)
atom->u.contents.option = C_SUB;
else if (!strcmp(arg, "sanitize"))
atom->u.contents.option = C_SUB_SANITIZE;
else
return strbuf_addf_ret(err, -1, _("unrecognized %%(subject) argument: %s"), arg);
return 0;
}
@ -360,22 +366,36 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato
return 0;
}
static int objectname_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg)
atom->u.objectname.option = O_FULL;
atom->u.oid.option = O_FULL;
else if (!strcmp(arg, "short"))
atom->u.objectname.option = O_SHORT;
atom->u.oid.option = O_SHORT;
else if (skip_prefix(arg, "short=", &arg)) {
atom->u.objectname.option = O_LENGTH;
if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
atom->u.objectname.length == 0)
return strbuf_addf_ret(err, -1, _("positive value expected objectname:short=%s"), arg);
if (atom->u.objectname.length < MINIMUM_ABBREV)
atom->u.objectname.length = MINIMUM_ABBREV;
atom->u.oid.option = O_LENGTH;
if (strtoul_ui(arg, 10, &atom->u.oid.length) ||
atom->u.oid.length == 0)
return strbuf_addf_ret(err, -1, _("positive value expected '%s' in %%(%s)"), arg, atom->name);
if (atom->u.oid.length < MINIMUM_ABBREV)
atom->u.oid.length = MINIMUM_ABBREV;
} else
return strbuf_addf_ret(err, -1, _("unrecognized %%(objectname) argument: %s"), arg);
return strbuf_addf_ret(err, -1, _("unrecognized argument '%s' in %%(%s)"), arg, atom->name);
return 0;
}
static int person_email_atom_parser(const struct ref_format *format, struct used_atom *atom,
const char *arg, struct strbuf *err)
{
if (!arg)
atom->u.email_option.option = EO_RAW;
else if (!strcmp(arg, "trim"))
atom->u.email_option.option = EO_TRIM;
else if (!strcmp(arg, "localpart"))
atom->u.email_option.option = EO_LOCALPART;
else
return strbuf_addf_ret(err, -1, _("unrecognized email option: %s"), arg);
return 0;
}
@ -480,25 +500,25 @@ static struct {
{ "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
{ "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
{ "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
{ "objectname", SOURCE_OTHER, FIELD_STR, objectname_atom_parser },
{ "objectname", SOURCE_OTHER, FIELD_STR, oid_atom_parser },
{ "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser },
{ "tree", SOURCE_OBJ },
{ "parent", SOURCE_OBJ },
{ "tree", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
{ "parent", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
{ "numparent", SOURCE_OBJ, FIELD_ULONG },
{ "object", SOURCE_OBJ },
{ "type", SOURCE_OBJ },
{ "tag", SOURCE_OBJ },
{ "author", SOURCE_OBJ },
{ "authorname", SOURCE_OBJ },
{ "authoremail", SOURCE_OBJ },
{ "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
{ "authordate", SOURCE_OBJ, FIELD_TIME },
{ "committer", SOURCE_OBJ },
{ "committername", SOURCE_OBJ },
{ "committeremail", SOURCE_OBJ },
{ "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
{ "committerdate", SOURCE_OBJ, FIELD_TIME },
{ "tagger", SOURCE_OBJ },
{ "taggername", SOURCE_OBJ },
{ "taggeremail", SOURCE_OBJ },
{ "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
{ "taggerdate", SOURCE_OBJ, FIELD_TIME },
{ "creator", SOURCE_OBJ },
{ "creatordate", SOURCE_OBJ, FIELD_TIME },
@ -903,21 +923,27 @@ int verify_ref_format(struct ref_format *format)
return 0;
}
static int grab_objectname(const char *name, const struct object_id *oid,
struct atom_value *v, struct used_atom *atom)
static const char *do_grab_oid(const char *field, const struct object_id *oid,
struct used_atom *atom)
{
if (starts_with(name, "objectname")) {
if (atom->u.objectname.option == O_SHORT) {
v->s = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
return 1;
} else if (atom->u.objectname.option == O_FULL) {
v->s = xstrdup(oid_to_hex(oid));
return 1;
} else if (atom->u.objectname.option == O_LENGTH) {
v->s = xstrdup(find_unique_abbrev(oid, atom->u.objectname.length));
return 1;
} else
BUG("unknown %%(objectname) option");
switch (atom->u.oid.option) {
case O_FULL:
return oid_to_hex(oid);
case O_LENGTH:
return find_unique_abbrev(oid, atom->u.oid.length);
case O_SHORT:
return find_unique_abbrev(oid, DEFAULT_ABBREV);
default:
BUG("unknown %%(%s) option", field);
}
}
static int grab_oid(const char *name, const char *field, const struct object_id *oid,
struct atom_value *v, struct used_atom *atom)
{
if (starts_with(name, field)) {
v->s = xstrdup(do_grab_oid(field, oid, atom));
return 1;
}
return 0;
}
@ -945,7 +971,7 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
} else if (!strcmp(name, "deltabase"))
v->s = xstrdup(oid_to_hex(&oi->delta_base_oid));
else if (deref)
grab_objectname(name, &oi->oid, v, &used_atom[i]);
grab_oid(name, "objectname", &oi->oid, v, &used_atom[i]);
}
}
@ -984,21 +1010,20 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
continue;
if (deref)
name++;
if (!strcmp(name, "tree")) {
v->s = xstrdup(oid_to_hex(get_commit_tree_oid(commit)));
}
else if (!strcmp(name, "numparent")) {
if (grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
continue;
if (!strcmp(name, "numparent")) {
v->value = commit_list_count(commit->parents);
v->s = xstrfmt("%lu", (unsigned long)v->value);
}
else if (!strcmp(name, "parent")) {
else if (starts_with(name, "parent")) {
struct commit_list *parents;
struct strbuf s = STRBUF_INIT;
for (parents = commit->parents; parents; parents = parents->next) {
struct commit *parent = parents->item;
struct object_id *oid = &parents->item->object.oid;
if (parents != commit->parents)
strbuf_addch(&s, ' ');
strbuf_addstr(&s, oid_to_hex(&parent->object.oid));
strbuf_addstr(&s, do_grab_oid("parent", oid, &used_atom[i]));
}
v->s = strbuf_detach(&s, NULL);
}
@ -1039,16 +1064,35 @@ static const char *copy_name(const char *buf)
return xstrdup("");
}
static const char *copy_email(const char *buf)
static const char *copy_email(const char *buf, struct used_atom *atom)
{
const char *email = strchr(buf, '<');
const char *eoemail;
if (!email)
return xstrdup("");
eoemail = strchr(email, '>');
switch (atom->u.email_option.option) {
case EO_RAW:
eoemail = strchr(email, '>');
if (eoemail)
eoemail++;
break;
case EO_TRIM:
email++;
eoemail = strchr(email, '>');
break;
case EO_LOCALPART:
email++;
eoemail = strchr(email, '@');
if (!eoemail)
eoemail = strchr(email, '>');
break;
default:
BUG("unknown email option");
}
if (!eoemail)
return xstrdup("");
return xmemdupz(email, eoemail + 1 - email);
return xmemdupz(email, eoemail - email);
}
static char *copy_subject(const char *buf, unsigned long len)
@ -1118,7 +1162,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
continue;
if (name[wholen] != 0 &&
strcmp(name + wholen, "name") &&
strcmp(name + wholen, "email") &&
!starts_with(name + wholen, "email") &&
!starts_with(name + wholen, "date"))
continue;
if (!wholine)
@ -1129,8 +1173,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
v->s = copy_line(wholine);
else if (!strcmp(name + wholen, "name"))
v->s = copy_name(wholine);
else if (!strcmp(name + wholen, "email"))
v->s = copy_email(wholine);
else if (starts_with(name + wholen, "email"))
v->s = copy_email(wholine, &used_atom[i]);
else if (starts_with(name + wholen, "date"))
grab_date(wholine, v, name);
}
@ -1243,8 +1287,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
continue;
if (deref)
name++;
if (strcmp(name, "subject") &&
strcmp(name, "body") &&
if (strcmp(name, "body") &&
!starts_with(name, "subject") &&
!starts_with(name, "trailers") &&
!starts_with(name, "contents"))
continue;
@ -1256,7 +1300,11 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
if (atom->u.contents.option == C_SUB)
v->s = copy_subject(subpos, sublen);
else if (atom->u.contents.option == C_BODY_DEP)
else if (atom->u.contents.option == C_SUB_SANITIZE) {
struct strbuf sb = STRBUF_INIT;
format_sanitized_subject(&sb, subpos, sublen);
v->s = strbuf_detach(&sb, NULL);
} else if (atom->u.contents.option == C_BODY_DEP)
v->s = xmemdupz(bodypos, bodylen);
else if (atom->u.contents.option == C_LENGTH)
v->s = xstrfmt("%"PRIuMAX, (uintmax_t)strlen(subpos));
@ -1706,7 +1754,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
v->s = xstrdup(buf + 1);
}
continue;
} else if (!deref && grab_objectname(name, &ref->objectname, v, atom)) {
} else if (!deref && grab_oid(name, "objectname", &ref->objectname, v, atom)) {
continue;
} else if (!strcmp(name, "HEAD")) {
if (atom->u.head && !strcmp(ref->refname, atom->u.head))

View File

@ -116,7 +116,13 @@ test_atom head objectname:short $(git rev-parse --short refs/heads/master)
test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
test_atom head tree $(git rev-parse refs/heads/master^{tree})
test_atom head tree:short $(git rev-parse --short refs/heads/master^{tree})
test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/master^{tree})
test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/master^{tree})
test_atom head parent ''
test_atom head parent:short ''
test_atom head parent:short=1 ''
test_atom head parent:short=10 ''
test_atom head numparent 0
test_atom head object ''
test_atom head type ''
@ -125,19 +131,26 @@ test_atom head '*objecttype' ''
test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
test_atom head authorname 'A U Thor'
test_atom head authoremail '<author@example.com>'
test_atom head authoremail:trim 'author@example.com'
test_atom head authoremail:localpart 'author'
test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
test_atom head committername 'C O Mitter'
test_atom head committeremail '<committer@example.com>'
test_atom head committeremail:trim 'committer@example.com'
test_atom head committeremail:localpart 'committer'
test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
test_atom head tag ''
test_atom head tagger ''
test_atom head taggername ''
test_atom head taggeremail ''
test_atom head taggeremail:trim ''
test_atom head taggeremail:localpart ''
test_atom head taggerdate ''
test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
test_atom head subject 'Initial'
test_atom head subject:sanitize 'Initial'
test_atom head contents:subject 'Initial'
test_atom head body ''
test_atom head contents:body ''
@ -161,7 +174,13 @@ test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
test_atom tag tree ''
test_atom tag tree:short ''
test_atom tag tree:short=1 ''
test_atom tag tree:short=10 ''
test_atom tag parent ''
test_atom tag parent:short ''
test_atom tag parent:short=1 ''
test_atom tag parent:short=10 ''
test_atom tag numparent ''
test_atom tag object $(git rev-parse refs/tags/testtag^0)
test_atom tag type 'commit'
@ -170,19 +189,26 @@ test_atom tag '*objecttype' 'commit'
test_atom tag author ''
test_atom tag authorname ''
test_atom tag authoremail ''
test_atom tag authoremail:trim ''
test_atom tag authoremail:localpart ''
test_atom tag authordate ''
test_atom tag committer ''
test_atom tag committername ''
test_atom tag committeremail ''
test_atom tag committeremail:trim ''
test_atom tag committeremail:localpart ''
test_atom tag committerdate ''
test_atom tag tag 'testtag'
test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
test_atom tag taggername 'C O Mitter'
test_atom tag taggeremail '<committer@example.com>'
test_atom tag taggeremail:trim 'committer@example.com'
test_atom tag taggeremail:localpart 'committer'
test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
test_atom tag subject 'Tagging at 1151968727'
test_atom tag subject:sanitize 'Tagging-at-1151968727'
test_atom tag contents:subject 'Tagging at 1151968727'
test_atom tag body ''
test_atom tag contents:body ''
@ -564,10 +590,14 @@ test_atom refs/tags/taggerless tag 'taggerless'
test_atom refs/tags/taggerless tagger ''
test_atom refs/tags/taggerless taggername ''
test_atom refs/tags/taggerless taggeremail ''
test_atom refs/tags/taggerless taggeremail:trim ''
test_atom refs/tags/taggerless taggeremail:localpart ''
test_atom refs/tags/taggerless taggerdate ''
test_atom refs/tags/taggerless committer ''
test_atom refs/tags/taggerless committername ''
test_atom refs/tags/taggerless committeremail ''
test_atom refs/tags/taggerless committeremail:trim ''
test_atom refs/tags/taggerless committeremail:localpart ''
test_atom refs/tags/taggerless committerdate ''
test_atom refs/tags/taggerless subject 'Broken tag'
@ -591,6 +621,7 @@ test_expect_success 'create tag with subject and body content' '
git tag -F msg subject-body
'
test_atom refs/tags/subject-body subject 'the subject line'
test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
test_atom refs/tags/subject-body body 'first body line
second body line
'
@ -611,6 +642,7 @@ test_expect_success 'create tag with multiline subject' '
git tag -F msg multiline
'
test_atom refs/tags/multiline subject 'first subject line second subject line'
test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
test_atom refs/tags/multiline body 'first body line
second body line
@ -643,6 +675,7 @@ sig='-----BEGIN PGP SIGNATURE-----
PREREQ=GPG
test_atom refs/tags/signed-empty subject ''
test_atom refs/tags/signed-empty subject:sanitize ''
test_atom refs/tags/signed-empty contents:subject ''
test_atom refs/tags/signed-empty body "$sig"
test_atom refs/tags/signed-empty contents:body ''
@ -650,6 +683,7 @@ test_atom refs/tags/signed-empty contents:signature "$sig"
test_atom refs/tags/signed-empty contents "$sig"
test_atom refs/tags/signed-short subject 'subject line'
test_atom refs/tags/signed-short subject:sanitize 'subject-line'
test_atom refs/tags/signed-short contents:subject 'subject line'
test_atom refs/tags/signed-short body "$sig"
test_atom refs/tags/signed-short contents:body ''
@ -658,6 +692,7 @@ test_atom refs/tags/signed-short contents "subject line
$sig"
test_atom refs/tags/signed-long subject 'subject line'
test_atom refs/tags/signed-long subject:sanitize 'subject-line'
test_atom refs/tags/signed-long contents:subject 'subject line'
test_atom refs/tags/signed-long body "body contents
$sig"