summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-for-each-ref.txt10
-rw-r--r--pretty.c20
-rw-r--r--pretty.h3
-rw-r--r--ref-filter.c158
-rwxr-xr-xt/t6300-for-each-ref.sh35
5 files changed, 161 insertions, 65 deletions
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 2ea71c5f6c..616ce46087 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -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
diff --git a/pretty.c b/pretty.c
index 2a3d46bf42..7a7708a0ea 100644
--- a/pretty.c
+++ b/pretty.c
@@ -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);
diff --git a/pretty.h b/pretty.h
index 071f2fb8e4..7ce6c0b437 100644
--- a/pretty.h
+++ b/pretty.h
@@ -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 */
diff --git a/ref-filter.c b/ref-filter.c
index 8ba0e31915..110bcd741a 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -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))
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 58adee7d18..b359023189 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -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"