threads / patch / 36298

v8, 12 partsMove lower case functions into wrapper.c

Subject: [PATCH v8 03/12] Move lower case functions into wrapper.c

## tl;dr

27 messages between Mar 26, 2014 and Mar 28, 2014. Diffs are folded; open one to read it.

replies: 26people: 3as markdown or json

Christian Couder· Mar 26, 2014, 22:15 UTC · lore

[PATCH v8 00/12] Add interpret-trailers builtin

This patch series implements a new command:
        git interpret-trailers

and an infrastructure to process trailers that can be reused, for example in "commit.c".

1) Rationale:

This command should help with RFC 822 style headers, called "trailers", that are found at the end of commit messages.

(Note that these headers do not follow and are not intended to follow many rules that are in RFC 822. For example they do not follow the line breaking rules, the encoding rules and probably many other rules.)

For a long time, these trailers have become a de facto standard way to add helpful information into commit messages.

Until now git commit has only supported the well known "Signed-off-by: " trailer, that is used by many projects like the Linux kernel and Git.

It is better to implement features for these trailers first in a
new command rather than in builtin/commit.c, because this way the
prepare-commit-msg and commit-msg hooks can reuse this command.
 
2) Current state:
Currently the usage string of this command is:
git interpret-trailers [--trim-empty] [(<token>[(=|:)<value>])...]
    
The following features are implemented:
        - the result is printed on stdout
        - the [<token>[=<value>]>] arguments are interpreted
        - a commit message read from stdin is interpreted
        - the "trailer.<token>.key" options in the config are interpreted
        - the "trailer.<token>.where" options are interpreted
        - the "trailer.<token>.ifExist" options are interpreted
        - the "trailer.<token>.ifMissing" options are interpreted
        - the "trailer.<token>.command" config works
        - $ARG can be used in commands
        - there are some tests
        - there is some documentation
The following features are planned but not yet implemented:
        - add more tests related to commands
        - add examples in documentation
        - integration with "git commit"
Possible improvements:
        - support GIT_COMMIT_PROTO env variable in commands
3) Changes since version 7, thanks to Junio:
* improved handling of empty trailer token
* clearer way to create 'expected' files in tests
* other small test cleanups
* improved commit message
* new way to parse config keys
* strcasecmp() is not used anymore in some config related functions
Some values from the config file are lowercased instead.
To enable that a new patch (3/12) is introduced to rationalize
lowercase related functions. I am not very happy with these
changes.
Christian Couder (12):
  Add data structures and basic functions for commit trailers
  trailer: process trailers from stdin and arguments
  Move lower case functions into wrapper.c
  trailer: read and process config information
  trailer: process command line trailer arguments
  trailer: parse trailers from stdin
  trailer: put all the processing together and print
  trailer: add interpret-trailers command
  trailer: add tests for "git interpret-trailers"
  trailer: execute command from 'trailer.<name>.command'
  trailer: add tests for commands in config file
  Documentation: add documentation for 'git interpret-trailers'
 .gitignore                               |   1 +
 Documentation/git-interpret-trailers.txt | 123 ++++++
 Makefile                                 |   2 +
 builtin.h                                |   1 +
 builtin/interpret-trailers.c             |  33 ++
 config.c                                 |   6 -
 daemon.c                                 |   8 -
 git-compat-util.h                        |   4 +
 git.c                                    |   1 +
 t/t7513-interpret-trailers.sh            | 407 ++++++++++++++++++
 trailer.c                                | 691 +++++++++++++++++++++++++++++++
 trailer.h                                |   6 +
 wrapper.c                                |  14 +
 13 files changed, 1283 insertions(+), 14 deletions(-)
 create mode 100644 Documentation/git-interpret-trailers.txt
 create mode 100644 builtin/interpret-trailers.c
 create mode 100755 t/t7513-interpret-trailers.sh
 create mode 100644 trailer.c
 create mode 100644 trailer.h
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 01/12] Add data structures and basic functions for commit trailers

We will use a doubly linked list to store all information about trailers and their configuration.

This way we can easily remove or add trailers to or from trailer lists while traversing the lists in either direction.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 Makefile  |  1 +
 trailer.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+)
 create mode 100644 trailer.c
Show changes to 2 files +50 −0

Makefile, trailer.c

diff --git a/Makefile b/Makefile
index d4ce53a..ec67ae1 100644
--- a/Makefile
+++ b/Makefile
@@ -883,6 +883,7 @@ LIB_OBJS += submodule.o
 LIB_OBJS += symlinks.o
 LIB_OBJS += tag.o
 LIB_OBJS += trace.o
+LIB_OBJS += trailer.o
 LIB_OBJS += transport.o
 LIB_OBJS += transport-helper.o
 LIB_OBJS += tree-diff.o
diff --git a/trailer.c b/trailer.c
new file mode 100644
index 0000000..db93a63
--- /dev/null
+++ b/trailer.c
@@ -0,0 +1,49 @@
+#include "cache.h"
+/*
+ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
+ */
+
+enum action_where { WHERE_AFTER, WHERE_BEFORE };
+enum action_if_exists { EXISTS_ADD_IF_DIFFERENT, EXISTS_ADD_IF_DIFFERENT_NEIGHBOR,
+			EXISTS_ADD, EXISTS_OVERWRITE, EXISTS_DO_NOTHING };
+enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
+
+struct conf_info {
+	char *name;
+	char *key;
+	char *command;
+	enum action_where where;
+	enum action_if_exists if_exists;
+	enum action_if_missing if_missing;
+};
+
+struct trailer_item {
+	struct trailer_item *previous;
+	struct trailer_item *next;
+	const char *token;
+	const char *value;
+	struct conf_info conf;
+};
+
+static int same_token(struct trailer_item *a, struct trailer_item *b, int alnum_len)
+{
+	return !strncasecmp(a->token, b->token, alnum_len);
+}
+
+static int same_value(struct trailer_item *a, struct trailer_item *b)
+{
+	return !strcasecmp(a->value, b->value);
+}
+
+static int same_trailer(struct trailer_item *a, struct trailer_item *b, int alnum_len)
+{
+	return same_token(a, b, alnum_len) && same_value(a, b);
+}
+
+/* Get the length of buf from its beginning until its last alphanumeric character */
+static size_t alnum_len(const char *buf, size_t len)
+{
+	while (len > 0 && !isalnum(buf[len - 1]))
+		len--;
+	return len;
+}
-- 
1.9.0.164.g3aa33cd.dirty
Junio C Hamano· Mar 26, 2014, 23:06 UTC · re: Christian Couder · lore

Re: [PATCH v8 01/12] Add data structures and basic functions for commit trailers

Christian Couder <chriscool@tuxfamily.org> writes:
>> Subject: Re: [PATCH v8 01/12] Add data structures and basic functions for commit trailers

As pointed out many times for GSoC microprojects students, limit the scope with "area:" prefix for the commit title, e.g.

    Subject: trailers: add data structures and basic functions

Please also refer to what has already been queued on 'pu' to avoid wasting review bandwidth and mark patches that are unchanged as such (but do send them to the list for review, so that people who haven't seen the previous round can also comment).

As far as I can tell, this is the same as 8d1c70e5 (trailers: add data structures and basic functions, 2014-03-06), so I'll queue the remainder on top of that commit already on 'pu', which incidentally will preserve the original author timestamp from the previous incarnation.

Christian Couder· Mar 27, 2014, 07:55 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 01/12] Add data structures and basic functions for commit trailers

From: Junio C Hamano <gitster@pobox.com>
Subject: Re: [PATCH v8 01/12] Add data structures and basic functions for commit trailers
Date: Wed, 26 Mar 2014 16:06:35 -0700
Show 8 quoted lines
> Christian Couder <chriscool@tuxfamily.org> writes:
> 
>>> Subject: Re: [PATCH v8 01/12] Add data structures and basic functions for commit trailers
> 
> As pointed out many times for GSoC microprojects students, limit the
> scope with "area:" prefix for the commit title, e.g.
> 
>     Subject: trailers: add data structures and basic functions
Ok, I will fix that.
 
> Please also refer to what has already been queued on 'pu' to avoid
> wasting review bandwidth and mark patches that are unchanged as such
> (but do send them to the list for review, so that people who haven't
> seen the previous round can also comment).
Yeah, I forgot to do that for this version of the series, sorry.
Show 5 quoted lines
> As far as I can tell, this is the same as 8d1c70e5 (trailers: add
> data structures and basic functions, 2014-03-06), so I'll queue the
> remainder on top of that commit already on 'pu', which incidentally
> will preserve the original author timestamp from the previous
> incarnation.
Ok.

Thanks, Christian.

Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 02/12] trailer: process trailers from stdin and arguments

Implement the logic to process trailers from stdin and arguments.

At the beginning trailers from stdin are in their own in_tok doubly linked list, and trailers from arguments are in their own arg_tok doubly linked list.

The lists are traversed and when an arg_tok should be "applied", it is removed from its list and inserted into the in_tok list.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 trailer.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 198 insertions(+)
Show changes to trailer.c +198 −0
diff --git a/trailer.c b/trailer.c
index db93a63..52108c2 100644
--- a/trailer.c
+++ b/trailer.c
@@ -47,3 +47,201 @@ static size_t alnum_len(const char *buf, size_t len)
 		len--;
 	return len;
 }
+
+static void free_trailer_item(struct trailer_item *item)
+{
+	free(item->conf.name);
+	free(item->conf.key);
+	free(item->conf.command);
+	free((char *)item->token);
+	free((char *)item->value);
+	free(item);
+}
+
+static void add_arg_to_input_list(struct trailer_item *in_tok,
+				  struct trailer_item *arg_tok)
+{
+	if (arg_tok->conf.where == WHERE_AFTER) {
+		arg_tok->next = in_tok->next;
+		in_tok->next = arg_tok;
+		arg_tok->previous = in_tok;
+		if (arg_tok->next)
+			arg_tok->next->previous = arg_tok;
+	} else {
+		arg_tok->previous = in_tok->previous;
+		in_tok->previous = arg_tok;
+		arg_tok->next = in_tok;
+		if (arg_tok->previous)
+			arg_tok->previous->next = arg_tok;
+	}
+}
+
+static int check_if_different(struct trailer_item *in_tok,
+			      struct trailer_item *arg_tok,
+			      int alnum_len, int check_all)
+{
+	enum action_where where = arg_tok->conf.where;
+	do {
+		if (!in_tok)
+			return 1;
+		if (same_trailer(in_tok, arg_tok, alnum_len))
+			return 0;
+		/*
+		 * if we want to add a trailer after another one,
+		 * we have to check those before this one
+		 */
+		in_tok = (where == WHERE_AFTER) ? in_tok->previous : in_tok->next;
+	} while (check_all);
+	return 1;
+}
+
+static void apply_arg_if_exists(struct trailer_item *in_tok,
+				struct trailer_item *arg_tok,
+				int alnum_len)
+{
+	switch (arg_tok->conf.if_exists) {
+	case EXISTS_DO_NOTHING:
+		free_trailer_item(arg_tok);
+		break;
+	case EXISTS_OVERWRITE:
+		free((char *)in_tok->value);
+		in_tok->value = xstrdup(arg_tok->value);
+		free_trailer_item(arg_tok);
+		break;
+	case EXISTS_ADD:
+		add_arg_to_input_list(in_tok, arg_tok);
+		break;
+	case EXISTS_ADD_IF_DIFFERENT:
+		if (check_if_different(in_tok, arg_tok, alnum_len, 1))
+			add_arg_to_input_list(in_tok, arg_tok);
+		else
+			free_trailer_item(arg_tok);
+		break;
+	case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
+		if (check_if_different(in_tok, arg_tok, alnum_len, 0))
+			add_arg_to_input_list(in_tok, arg_tok);
+		else
+			free_trailer_item(arg_tok);
+		break;
+	}
+}
+
+static void remove_from_list(struct trailer_item *item,
+			     struct trailer_item **first)
+{
+	if (item->next)
+		item->next->previous = item->previous;
+	if (item->previous)
+		item->previous->next = item->next;
+	else
+		*first = item->next;
+}
+
+static struct trailer_item *remove_first(struct trailer_item **first)
+{
+	struct trailer_item *item = *first;
+	*first = item->next;
+	if (item->next) {
+		item->next->previous = NULL;
+		item->next = NULL;
+	}
+	return item;
+}
+
+static void process_input_token(struct trailer_item *in_tok,
+				struct trailer_item **arg_tok_first,
+				enum action_where where)
+{
+	struct trailer_item *arg_tok;
+	struct trailer_item *next_arg;
+
+	int after = where == WHERE_AFTER;
+	int tok_alnum_len = alnum_len(in_tok->token, strlen(in_tok->token));
+
+	for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
+		next_arg = arg_tok->next;
+		if (!same_token(in_tok, arg_tok, tok_alnum_len))
+			continue;
+		if (arg_tok->conf.where != where)
+			continue;
+		remove_from_list(arg_tok, arg_tok_first);
+		apply_arg_if_exists(in_tok, arg_tok, tok_alnum_len);
+		/*
+		 * If arg has been added to input,
+		 * then we need to process it too now.
+		 */
+		if ((after ? in_tok->next : in_tok->previous) == arg_tok)
+			in_tok = arg_tok;
+	}
+}
+
+static void update_last(struct trailer_item **last)
+{
+	if (*last)
+		while ((*last)->next != NULL)
+			*last = (*last)->next;
+}
+
+static void update_first(struct trailer_item **first)
+{
+	if (*first)
+		while ((*first)->previous != NULL)
+			*first = (*first)->previous;
+}
+
+static void apply_arg_if_missing(struct trailer_item **in_tok_first,
+				 struct trailer_item **in_tok_last,
+				 struct trailer_item *arg_tok)
+{
+	struct trailer_item **in_tok;
+	enum action_where where;
+
+	switch (arg_tok->conf.if_missing) {
+	case MISSING_DO_NOTHING:
+		free_trailer_item(arg_tok);
+		break;
+	case MISSING_ADD:
+		where = arg_tok->conf.where;
+		in_tok = (where == WHERE_AFTER) ? in_tok_last : in_tok_first;
+		if (*in_tok) {
+			add_arg_to_input_list(*in_tok, arg_tok);
+			*in_tok = arg_tok;
+		} else {
+			*in_tok_first = arg_tok;
+			*in_tok_last = arg_tok;
+		}
+		break;
+	}
+}
+
+static void process_trailers_lists(struct trailer_item **in_tok_first,
+				   struct trailer_item **in_tok_last,
+				   struct trailer_item **arg_tok_first)
+{
+	struct trailer_item *in_tok;
+	struct trailer_item *arg_tok;
+
+	if (!*arg_tok_first)
+		return;
+
+	/* Process input from end to start */
+	for (in_tok = *in_tok_last; in_tok; in_tok = in_tok->previous)
+		process_input_token(in_tok, arg_tok_first, WHERE_AFTER);
+
+	update_last(in_tok_last);
+
+	if (!*arg_tok_first)
+		return;
+
+	/* Process input from start to end */
+	for (in_tok = *in_tok_first; in_tok; in_tok = in_tok->next)
+		process_input_token(in_tok, arg_tok_first, WHERE_BEFORE);
+
+	update_first(in_tok_first);
+
+	/* Process args left */
+	while (*arg_tok_first) {
+		arg_tok = remove_first(arg_tok_first);
+		apply_arg_if_missing(in_tok_first, in_tok_last, arg_tok);
+	}
+}
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

The lowercase() function from config.c and the xstrdup_tolower() function from daemon.c can benefit from being moved to the same place because this way the latter can use the former.

Also let's make them available globally so we can use them from other places like trailer.c.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 config.c          |  6 ------
 daemon.c          |  8 --------
 git-compat-util.h |  4 ++++
 wrapper.c         | 14 ++++++++++++++
 4 files changed, 18 insertions(+), 14 deletions(-)
Show changes to 4 files +18 −14

config.c, daemon.c, git-compat-util.h, wrapper.c

diff --git a/config.c b/config.c
index 314d8ee..dde128e 100644
--- a/config.c
+++ b/config.c
@@ -146,12 +146,6 @@ int git_config_include(const char *var, const char *value, void *data)
 	return ret;
 }
 
-static void lowercase(char *p)
-{
-	for (; *p; p++)
-		*p = tolower(*p);
-}
-
 void git_config_push_parameter(const char *text)
 {
 	struct strbuf env = STRBUF_INIT;
diff --git a/daemon.c b/daemon.c
index eba1255..f9c63e9 100644
--- a/daemon.c
+++ b/daemon.c
@@ -475,14 +475,6 @@ static void make_service_overridable(const char *name, int ena)
 	die("No such service %s", name);
 }
 
-static char *xstrdup_tolower(const char *str)
-{
-	char *p, *dup = xstrdup(str);
-	for (p = dup; *p; p++)
-		*p = tolower(*p);
-	return dup;
-}
-
 static void parse_host_and_port(char *hostport, char **host,
 	char **port)
 {
diff --git a/git-compat-util.h b/git-compat-util.h
index 614a5e9..2397706 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -727,4 +727,8 @@ void warn_on_inaccessible(const char *path);
 /* Get the passwd entry for the UID of the current process. */
 struct passwd *xgetpwuid_self(void);
 
+/* Lowercase strings */
+extern void lowercase(char *str);
+extern char *xstrdup_tolower(const char *str);
+
 #endif
diff --git a/wrapper.c b/wrapper.c
index 0cc5636..c46026a 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -455,3 +455,17 @@ struct passwd *xgetpwuid_self(void)
 		    errno ? strerror(errno) : _("no such user"));
 	return pw;
 }
+
+void lowercase(char *p)
+{
+	for (; *p; p++)
+		*p = tolower(*p);
+}
+
+char *xstrdup_tolower(const char *str)
+{
+	char *dup = xstrdup(str);
+	lowercase(dup);
+	return dup;
+}
+
-- 
1.9.0.164.g3aa33cd.dirty
Junio C Hamano· Mar 26, 2014, 23:07 UTC · re: Christian Couder · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

Christian Couder <chriscool@tuxfamily.org> writes:
Show 22 quoted lines
> diff --git a/wrapper.c b/wrapper.c
> index 0cc5636..c46026a 100644
> --- a/wrapper.c
> +++ b/wrapper.c
> @@ -455,3 +455,17 @@ struct passwd *xgetpwuid_self(void)
>  		    errno ? strerror(errno) : _("no such user"));
>  	return pw;
>  }
> +
> +void lowercase(char *p)
> +{
> +	for (; *p; p++)
> +		*p = tolower(*p);
> +}
> +
> +char *xstrdup_tolower(const char *str)
> +{
> +	char *dup = xstrdup(str);
> +	lowercase(dup);
> +	return dup;
> +}
> +

As a pure code-movement step, this may be OK, but I am not sure if both of them want to be public functions in this shape.

Perhaps
char *downcase_copy(const char *str)
{
	char *copy = xmalloc(strlen(str) + 1);
        int i;
        for (i = 0; str[i]; i++)
        	copy[i] = tolower(str[i]);
	copy[i] = '\0';
        return copy;
}

may avoid having to copy things twice. Do you need the other function exposed?

Christian Couder· Mar 27, 2014, 07:47 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

From: Junio C Hamano <gitster@pobox.com>
Show 42 quoted lines
>
> Christian Couder <chriscool@tuxfamily.org> writes:
> 
>> diff --git a/wrapper.c b/wrapper.c
>> index 0cc5636..c46026a 100644
>> --- a/wrapper.c
>> +++ b/wrapper.c
>> @@ -455,3 +455,17 @@ struct passwd *xgetpwuid_self(void)
>>  		    errno ? strerror(errno) : _("no such user"));
>>  	return pw;
>>  }
>> +
>> +void lowercase(char *p)
>> +{
>> +	for (; *p; p++)
>> +		*p = tolower(*p);
>> +}
>> +
>> +char *xstrdup_tolower(const char *str)
>> +{
>> +	char *dup = xstrdup(str);
>> +	lowercase(dup);
>> +	return dup;
>> +}
>> +
> 
> As a pure code-movement step, this may be OK, but I am not sure if
> both of them want to be public functions in this shape.
> 
> Perhaps
> 
> char *downcase_copy(const char *str)
> {
> 	char *copy = xmalloc(strlen(str) + 1);
>         int i;
>         for (i = 0; str[i]; i++)
>         	copy[i] = tolower(str[i]);
> 	copy[i] = '\0';
>         return copy;
> }
> 
> may avoid having to copy things twice.

Yeah, but it seems a bit wasteful to allocate memory for a new string, then downcase it, then compare it with strcmp() and then free it, instead of just using strcasecmp() on the original string.

> Do you need the other
> function exposed?
No, with the change you suggest, I don't.

Thanks, Christian.

Junio C Hamano· Mar 27, 2014, 16:42 UTC · re: Christian Couder · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

Christian Couder <chriscool@tuxfamily.org> writes:
> Yeah, but it seems a bit wasteful to allocate memory for a new string,
> then downcase it, then compare it with strcmp() and then free it,
> instead of just using strcasecmp() on the original string.

I wasn't looking at the caller (and I haven't). I agree that, if you have to compare case-insensitive user input against known set of tokens, using strcasecmp() would be saner than making a downcased copy and the set of known tokens. I do not know however you want to compare in a case-insensitive way in your application, though.

Junio C Hamano· Mar 27, 2014, 22:16 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

Junio C Hamano <gitster@pobox.com> writes:
Show 11 quoted lines
> Christian Couder <chriscool@tuxfamily.org> writes:
>
>> Yeah, but it seems a bit wasteful to allocate memory for a new string,
>> then downcase it, then compare it with strcmp() and then free it,
>> instead of just using strcasecmp() on the original string.
>
> I wasn't looking at the caller (and I haven't).  I agree that, if
> you have to compare case-insensitive user input against known set of
> tokens, using strcasecmp() would be saner than making a downcased
> copy and the set of known tokens.  I do not know however you want to
> compare in a case-insensitive way in your application, though.

It appears that one place this "lowercase" is used is to allow rAnDOm casing in the configuration file, e.g.

	[trailer "Signed-off-by"]
		where = AfTEr

which I find is totally unnecessary. Do we churn code to accept such a nonsense input in other places?

Jeff King· Mar 27, 2014, 22:34 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

On Thu, Mar 27, 2014 at 03:16:48PM -0700, Junio C Hamano wrote:
Show 14 quoted lines
> > I wasn't looking at the caller (and I haven't).  I agree that, if
> > you have to compare case-insensitive user input against known set of
> > tokens, using strcasecmp() would be saner than making a downcased
> > copy and the set of known tokens.  I do not know however you want to
> > compare in a case-insensitive way in your application, though.
> 
> It appears that one place this "lowercase" is used is to allow
> rAnDOm casing in the configuration file, e.g.
> 
> 	[trailer "Signed-off-by"]
> 		where = AfTEr
> 
> which I find is totally unnecessary.  Do we churn code to accept
> such a nonsense input in other places?
I think we are very inconsistent.

All bool config values allow "tRuE". Ones that take "auto" often use strcasecmp (e.g., diff.*.binary). "blame.date" and "help.format" choose from a fixed set of tokens, but use strcmp.

Command line parameters are of course case-sensitive, and tokens used by them usually are, too (e.g., the date formats for "blame.date" or also the same ones taken by "--date=").

In general I do not see any reason _not_ to use strcasecmp for config values that are matching a fixed set. It's friendlier to the user, the extra CPU time is negligible, and the code is no harder to read than a strcmp. Just looking at the callers in patch 04/12, I think it would be better just used strcasecmp instead of making a lowercase copy. Not because the copy is wasteful (it is, but it almost certainly doesn't matter here), but because avoiding the copy is shorter and easier to follow (you don't have to wonder about memory ownership).

-Peff
Junio C Hamano· Mar 27, 2014, 22:47 UTC · re: Jeff King · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

Jeff King <peff@peff.net> writes:
> All bool config values allow "tRuE".

I was expecting somebody will bring it up, but think about it. Bool is a very special case. Even among CS folks, depending on your background, true may be True may be TRUE may be 1.

Conflating it with some random enum does not make a good argument.
> Ones that take "auto" often use
> strcasecmp (e.g., diff.*.binary). "blame.date" and "help.format" choose
> from a fixed set of tokens, but use strcmp.
I would say that the latter is the right thing to do.
> In general I do not see any reason _not_ to use strcasecmp for config
> values that are matching a fixed set. It's friendlier to the user,...

Actually, I think it ends up being hostile to the users to accept random cases without a good reason. If you see two trailer elements whose where are specified as "after" and "AFTER" in somebody's configuration file, wouldn't that give a wrong impression that a new line with the latter somehow has a stronger desire to come later than the former?

If you consistently take only the fixed strings, you do not have to worry about many people writing the same things in different ways, confusing each other.

I would however fully agree with you that using strcasecmp() would be the cleanest when reading and maintaining the code **IF** we want to accept values in random case, but I do not agree that accepting random cases is a good thing, so...

Jeff King· Mar 27, 2014, 22:56 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

On Thu, Mar 27, 2014 at 03:47:01PM -0700, Junio C Hamano wrote:
Show 10 quoted lines
> Actually, I think it ends up being hostile to the users to accept
> random cases without a good reason.  If you see two trailer elements
> whose where are specified as "after" and "AFTER" in somebody's
> configuration file, wouldn't that give a wrong impression that a new
> line with the latter somehow has a stronger desire to come later
> than the former?
> 
> If you consistently take only the fixed strings, you do not have to
> worry about many people writing the same things in different ways,
> confusing each other.

I do not agree with this line of reasoning at all. After all, do we have confusion about the case differences between:

  [COLOR]
  diff = true
  [color]
  UI = false

But I also do not overly care. Literally zero people have complained that "[log]date = RFC822" is not accepted, so it is probably not a big deal either way.

-Peff
Junio C Hamano· Mar 28, 2014, 17:12 UTC · re: Jeff King · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

Jeff King <peff@peff.net> writes:
> But I also do not overly care. Literally zero people have complained
> that "[log]date = RFC822" is not accepted, so it is probably not a big
> deal either way.

That is most likely because we do not advertise these enum values spelled in random cases in our documentation and people do not even attempt to upcase the examples they see. So you are right to say that it does not hurt anybody in practice if the code does not strcasecmp() input from them. We do not know if using strcasecmp() there and allowing them to feed the enums spelled in random cases would invite confusions in the longer run, so let's not risk it. There is no upside in using strcasecmp() here.

By the way, that is "rfc2822"---do we want "rfc822" as its synonym as well as "rfc", I wonder ;-)

Jeff King· Mar 28, 2014, 18:50 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

On Fri, Mar 28, 2014 at 10:12:15AM -0700, Junio C Hamano wrote:
> By the way, that is "rfc2822"---do we want "rfc822" as its synonym
> as well as "rfc", I wonder ;-)

Oops, I wrote that as I was literally looking at the code that said rfc2822 and didn't notice. On the other hand, I have never made the mistake when actually running git, so it is probably not a big deal.

Besides which, isn't it 5322 these days? I do not think we need to keep up with the treadmill.

-Peff
Christian Couder· Mar 28, 2014, 07:02 UTC · re: Jeff King · lore

Re: [PATCH v8 03/12] Move lower case functions into wrapper.c

From: Jeff King <peff@peff.net>
Subject: Re: [PATCH v8 03/12] Move lower case functions into wrapper.c
Date: Thu, 27 Mar 2014 18:34:06 -0400
Show 31 quoted lines
> On Thu, Mar 27, 2014 at 03:16:48PM -0700, Junio C Hamano wrote:
> 
>> > I wasn't looking at the caller (and I haven't).  I agree that, if
>> > you have to compare case-insensitive user input against known set of
>> > tokens, using strcasecmp() would be saner than making a downcased
>> > copy and the set of known tokens.  I do not know however you want to
>> > compare in a case-insensitive way in your application, though.
>>
>> It appears that one place this "lowercase" is used is to allow
>> rAnDOm casing in the configuration file, e.g.
>> 
>> 	[trailer "Signed-off-by"]
>> 		where = AfTEr
>> 
>> which I find is totally unnecessary.  Do we churn code to accept
>> such a nonsense input in other places?
> 
> I think we are very inconsistent.
> 
> All bool config values allow "tRuE". Ones that take "auto" often use
> strcasecmp (e.g., diff.*.binary). "blame.date" and "help.format" choose
> from a fixed set of tokens, but use strcmp.
> 
> Command line parameters are of course case-sensitive, and tokens used by
> them usually are, too (e.g., the date formats for "blame.date" or also
> the same ones taken by "--date=").
> 
> In general I do not see any reason _not_ to use strcasecmp for config
> values that are matching a fixed set. It's friendlier to the user, the
> extra CPU time is negligible, and the code is no harder to read than a
> strcmp.

I agree with this. I think it would be better to just use strcasecmp() for all the config values matching a fixed set. It is just much easier to explain to users how things work this way.

Even if no one ever complained about this on the mailing list, many users complain that Git is very inconsistent.

Show 5 quoted lines
> Just looking at the callers in patch 04/12, I think it would be
> better just used strcasecmp instead of making a lowercase copy. Not
> because the copy is wasteful (it is, but it almost certainly doesn't
> matter here), but because avoiding the copy is shorter and easier to
> follow (you don't have to wonder about memory ownership).

Yeah, that's also why I am not very happy to have to change things in this area.

Thanks, Christian.

Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 04/12] trailer: read and process config information

Read the configuration to get trailer information, and then process it and storing it in a doubly linked list.

The config information is stored in the list whose first item is pointed to by:

static struct trailer_item *first_conf_item;
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 trailer.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)
Show changes to trailer.c +153 −0
diff --git a/trailer.c b/trailer.c
index 52108c2..b5a0943 100644
--- a/trailer.c
+++ b/trailer.c
@@ -25,6 +25,8 @@ struct trailer_item {
 	struct conf_info conf;
 };
 
+static struct trailer_item *first_conf_item;
+
 static int same_token(struct trailer_item *a, struct trailer_item *b, int alnum_len)
 {
 	return !strncasecmp(a->token, b->token, alnum_len);
@@ -245,3 +247,154 @@ static void process_trailers_lists(struct trailer_item **in_tok_first,
 		apply_arg_if_missing(in_tok_first, in_tok_last, arg_tok);
 	}
 }
+
+static int set_where(struct conf_info *item, const char *value)
+{
+	if (!strcmp("after", value))
+		item->where = WHERE_AFTER;
+	else if (!strcmp("before", value))
+		item->where = WHERE_BEFORE;
+	else
+		return -1;
+	return 0;
+}
+
+static int set_if_exists(struct conf_info *item, const char *value)
+{
+	if (!strcmp("addifdifferent", value))
+		item->if_exists = EXISTS_ADD_IF_DIFFERENT;
+	else if (!strcmp("addifdifferentneighbor", value))
+		item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
+	else if (!strcmp("add", value))
+		item->if_exists = EXISTS_ADD;
+	else if (!strcmp("overwrite", value))
+		item->if_exists = EXISTS_OVERWRITE;
+	else if (!strcmp("donothing", value))
+		item->if_exists = EXISTS_DO_NOTHING;
+	else
+		return -1;
+	return 0;
+}
+
+static int set_if_missing(struct conf_info *item, const char *value)
+{
+	if (!strcmp("donothing", value))
+		item->if_missing = MISSING_DO_NOTHING;
+	else if (!strcmp("add", value))
+		item->if_missing = MISSING_ADD;
+	else
+		return -1;
+	return 0;
+}
+
+static struct trailer_item *get_conf_item(const char *name)
+{
+	struct trailer_item *item;
+	struct trailer_item *previous;
+
+	/* Look up item with same name */
+	for (previous = NULL, item = first_conf_item;
+	     item;
+	     previous = item, item = item->next) {
+		if (!strcasecmp(item->conf.name, name))
+			return item;
+	}
+
+	/* Item does not already exists, create it */
+	item = xcalloc(sizeof(struct trailer_item), 1);
+	item->conf.name = xstrdup(name);
+
+	if (!previous)
+		first_conf_item = item;
+	else {
+		previous->next = item;
+		item->previous = previous;
+	}
+
+	return item;
+}
+
+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
+			 TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
+
+static struct {
+	const char *name;
+	enum trailer_info_type type;
+} trailer_config_items[] = {
+	{ "key", TRAILER_KEY },
+	{ "command", TRAILER_COMMAND },
+	{ "where", TRAILER_WHERE },
+	{ "ifexists", TRAILER_IF_EXISTS },
+	{ "ifmissing", TRAILER_IF_MISSING }
+};
+
+static int git_trailer_config(const char *conf_key, const char *value, void *cb)
+{
+	const char *trailer_item, *variable_name;
+	struct trailer_item *item;
+	struct conf_info *conf;
+	char *name = NULL;
+	enum trailer_info_type type;
+	char *lowercase_value;
+	int i;
+
+	trailer_item = skip_prefix(conf_key, "trailer.");
+	if (!trailer_item)
+		return 0;
+
+	variable_name = strrchr(trailer_item, '.');
+	if (!variable_name) {
+		warning(_("two level trailer config variable %s"), conf_key);
+		return 0;
+	}
+
+	variable_name++;
+	for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
+		if (strcmp(trailer_config_items[i].name, variable_name))
+			continue;
+		name = xstrndup(trailer_item,  variable_name - trailer_item -1);
+		type = trailer_config_items[i].type;
+		break;
+	}
+
+	if (!name)
+		return 0;
+
+	item = get_conf_item(name);
+	conf = &item->conf;
+	free(name);
+
+	switch (type) {
+	case TRAILER_KEY:
+		if (conf->key)
+			warning(_("more than one %s"), conf_key);
+		conf->key = xstrdup(value);
+		break;
+	case TRAILER_COMMAND:
+		if (conf->command)
+			warning(_("more than one %s"), conf_key);
+		conf->command = xstrdup(value);
+		break;
+	case TRAILER_WHERE:
+		lowercase_value = xstrdup_tolower(value);
+		if (set_where(conf, lowercase_value))
+			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
+		free(lowercase_value);
+		break;
+	case TRAILER_IF_EXISTS:
+		lowercase_value = xstrdup_tolower(value);
+		if (set_if_exists(conf, lowercase_value))
+			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
+		free(lowercase_value);
+		break;
+	case TRAILER_IF_MISSING:
+		lowercase_value = xstrdup_tolower(value);
+		if (set_if_missing(conf, lowercase_value))
+			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
+		free(lowercase_value);
+		break;
+	default:
+		die("internal bug in trailer.c");
+	}
+	return 0;
+}
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 05/12] trailer: process command line trailer arguments

Parse the trailer command line arguments and put the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 trailer.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)
Show changes to trailer.c +103 −0
diff --git a/trailer.c b/trailer.c
index b5a0943..1871843 100644
--- a/trailer.c
+++ b/trailer.c
@@ -398,3 +398,106 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 	}
 	return 0;
 }
+
+static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
+{
+	size_t len = strcspn(trailer, "=:");
+	if (len == 0)
+		return error(_("empty trailer token in trailer '%s'"), trailer);
+	if (len < strlen(trailer)) {
+		strbuf_add(tok, trailer, len);
+		strbuf_trim(tok);
+		strbuf_addstr(val, trailer + len + 1);
+		strbuf_trim(val);
+	} else {
+		strbuf_addstr(tok, trailer);
+		strbuf_trim(tok);
+	}
+	return 0;
+}
+
+
+static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
+{
+	*dst = *src;
+	if (src->name)
+		dst->name = xstrdup(src->name);
+	if (src->key)
+		dst->key = xstrdup(src->key);
+	if (src->command)
+		dst->command = xstrdup(src->command);
+}
+
+static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
+					     char *tok, char *val)
+{
+	struct trailer_item *new = xcalloc(sizeof(*new), 1);
+	new->value = val;
+
+	if (conf_item) {
+		duplicate_conf(&new->conf, &conf_item->conf);
+		new->token = xstrdup(conf_item->conf.key);
+		free(tok);
+	} else
+		new->token = tok;
+
+	return new;
+}
+
+static struct trailer_item *create_trailer_item(const char *string)
+{
+	struct strbuf tok = STRBUF_INIT;
+	struct strbuf val = STRBUF_INIT;
+	struct trailer_item *item;
+	int tok_alnum_len;
+
+	if (parse_trailer(&tok, &val, string))
+		return NULL;
+
+	tok_alnum_len = alnum_len(tok.buf, tok.len);
+
+	/* Lookup if the token matches something in the config */
+	for (item = first_conf_item; item; item = item->next) {
+		if (!strncasecmp(tok.buf, item->conf.key, tok_alnum_len) ||
+		    !strncasecmp(tok.buf, item->conf.name, tok_alnum_len)) {
+			strbuf_release(&tok);
+			return new_trailer_item(item,
+						NULL,
+						strbuf_detach(&val, NULL));
+		}
+	}
+
+	return new_trailer_item(NULL,
+				strbuf_detach(&tok, NULL),
+				strbuf_detach(&val, NULL));
+}
+
+static void add_trailer_item(struct trailer_item **first,
+			     struct trailer_item **last,
+			     struct trailer_item *new)
+{
+	if (!new)
+		return;
+	if (!*last) {
+		*first = new;
+		*last = new;
+	} else {
+		(*last)->next = new;
+		new->previous = *last;
+		*last = new;
+	}
+}
+
+static struct trailer_item *process_command_line_args(int argc, const char **argv)
+{
+	int i;
+	struct trailer_item *arg_tok_first = NULL;
+	struct trailer_item *arg_tok_last = NULL;
+
+	for (i = 0; i < argc; i++) {
+		struct trailer_item *new = create_trailer_item(argv[i]);
+		add_trailer_item(&arg_tok_first, &arg_tok_last, new);
+	}
+
+	return arg_tok_first;
+}
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 06/12] trailer: parse trailers from stdin

Read trailers from stdin, parse them and put the result into a doubly linked list.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 trailer.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)
Show changes to trailer.c +76 −0
diff --git a/trailer.c b/trailer.c
index 1871843..3993840 100644
--- a/trailer.c
+++ b/trailer.c
@@ -50,6 +50,14 @@ static size_t alnum_len(const char *buf, size_t len)
 	return len;
 }
 
+static inline int contains_only_spaces(const char *str)
+{
+	const char *s = str;
+	while (*s && isspace(*s))
+		s++;
+	return !*s;
+}
+
 static void free_trailer_item(struct trailer_item *item)
 {
 	free(item->conf.name);
@@ -501,3 +509,71 @@ static struct trailer_item *process_command_line_args(int argc, const char **arg
 
 	return arg_tok_first;
 }
+
+static struct strbuf **read_stdin(void)
+{
+	struct strbuf **lines;
+	struct strbuf sb = STRBUF_INIT;
+
+	if (strbuf_read(&sb, fileno(stdin), 0) < 0)
+		die_errno(_("could not read from stdin"));
+
+	lines = strbuf_split(&sb, '\n');
+
+	strbuf_release(&sb);
+
+	return lines;
+}
+
+/*
+ * Return the the (0 based) index of the first trailer line
+ * or the line count if there are no trailers.
+ */
+static int find_trailer_start(struct strbuf **lines)
+{
+	int start, empty = 1, count = 0;
+
+	/* Get the line count */
+	while (lines[count])
+		count++;
+
+	/*
+	 * Get the start of the trailers by looking starting from the end
+	 * for a line with only spaces before lines with one ':'.
+	 */
+	for (start = count - 1; start >= 0; start--) {
+		if (contains_only_spaces(lines[start]->buf)) {
+			if (empty)
+				continue;
+			return start + 1;
+		}
+		if (strchr(lines[start]->buf, ':')) {
+			if (empty)
+				empty = 0;
+			continue;
+		}
+		return count;
+	}
+
+	return empty ? count : start + 1;
+}
+
+static void process_stdin(struct trailer_item **in_tok_first,
+			  struct trailer_item **in_tok_last)
+{
+	struct strbuf **lines = read_stdin();
+	int start = find_trailer_start(lines);
+	int i;
+
+	/* Print non trailer lines as is */
+	for (i = 0; lines[i] && i < start; i++)
+		printf("%s", lines[i]->buf);
+
+	/* Parse trailer lines */
+	for (i = start; lines[i]; i++) {
+		struct trailer_item *new = create_trailer_item(lines[i]->buf);
+		add_trailer_item(in_tok_first, in_tok_last, new);
+	}
+
+	strbuf_list_free(lines);
+}
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 07/12] trailer: put all the processing together and print

This patch adds the process_trailers() function that calls all the previously added processing functions and then prints the results on the standard output.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 trailer.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 trailer.h |  6 ++++++
 2 files changed, 55 insertions(+)
 create mode 100644 trailer.h
Show changes to 2 files +55 −0

trailer.c, trailer.h

diff --git a/trailer.c b/trailer.c
index 3993840..5bb29ae 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "trailer.h"
 /*
  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
  */
@@ -68,6 +69,26 @@ static void free_trailer_item(struct trailer_item *item)
 	free(item);
 }
 
+static void print_tok_val(const char *tok, const char *val)
+{
+	char c = tok[strlen(tok) - 1];
+	if (isalnum(c))
+		printf("%s: %s\n", tok, val);
+	else if (isspace(c) || c == '#')
+		printf("%s%s\n", tok, val);
+	else
+		printf("%s %s\n", tok, val);
+}
+
+static void print_all(struct trailer_item *first, int trim_empty)
+{
+	struct trailer_item *item;
+	for (item = first; item; item = item->next) {
+		if (!trim_empty || strlen(item->value) > 0)
+			print_tok_val(item->token, item->value);
+	}
+}
+
 static void add_arg_to_input_list(struct trailer_item *in_tok,
 				  struct trailer_item *arg_tok)
 {
@@ -577,3 +598,31 @@ static void process_stdin(struct trailer_item **in_tok_first,
 
 	strbuf_list_free(lines);
 }
+
+static void free_all(struct trailer_item **first)
+{
+	while (*first) {
+		struct trailer_item *item = remove_first(first);
+		free_trailer_item(item);
+	}
+}
+
+void process_trailers(int trim_empty, int argc, const char **argv)
+{
+	struct trailer_item *in_tok_first = NULL;
+	struct trailer_item *in_tok_last = NULL;
+	struct trailer_item *arg_tok_first;
+
+	git_config(git_trailer_config, NULL);
+
+	/* Print the non trailer part of stdin */
+	process_stdin(&in_tok_first, &in_tok_last);
+
+	arg_tok_first = process_command_line_args(argc, argv);
+
+	process_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first);
+
+	print_all(in_tok_first, trim_empty);
+
+	free_all(&in_tok_first);
+}
diff --git a/trailer.h b/trailer.h
new file mode 100644
index 0000000..9323b1e
--- /dev/null
+++ b/trailer.h
@@ -0,0 +1,6 @@
+#ifndef TRAILER_H
+#define TRAILER_H
+
+void process_trailers(int trim_empty, int argc, const char **argv);
+
+#endif /* TRAILER_H */
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 08/12] trailer: add interpret-trailers command

This patch adds the "git interpret-trailers" command. This command uses the previously added process_trailers() function in trailer.c.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 .gitignore                   |  1 +
 Makefile                     |  1 +
 builtin.h                    |  1 +
 builtin/interpret-trailers.c | 33 +++++++++++++++++++++++++++++++++
 git.c                        |  1 +
 5 files changed, 37 insertions(+)
 create mode 100644 builtin/interpret-trailers.c
Show changes to 5 files +37 −0

.gitignore, Makefile, builtin.h, builtin/interpret-trailers.c, git.c

diff --git a/.gitignore b/.gitignore
index dc600f9..c2a0b19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,6 +74,7 @@
 /git-index-pack
 /git-init
 /git-init-db
+/git-interpret-trailers
 /git-instaweb
 /git-log
 /git-ls-files
diff --git a/Makefile b/Makefile
index ec67ae1..94f7e95 100644
--- a/Makefile
+++ b/Makefile
@@ -947,6 +947,7 @@ BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
+BUILTIN_OBJS += builtin/interpret-trailers.o
 BUILTIN_OBJS += builtin/log.o
 BUILTIN_OBJS += builtin/ls-files.o
 BUILTIN_OBJS += builtin/ls-remote.o
diff --git a/builtin.h b/builtin.h
index c47c110..8ca0065 100644
--- a/builtin.h
+++ b/builtin.h
@@ -73,6 +73,7 @@ extern int cmd_hash_object(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
 extern int cmd_index_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_init_db(int argc, const char **argv, const char *prefix);
+extern int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
 extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
new file mode 100644
index 0000000..0c8ca72
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,33 @@
+/*
+ * Builtin "git interpret-trailers"
+ *
+ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
+ *
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "trailer.h"
+
+static const char * const git_interpret_trailers_usage[] = {
+	N_("git interpret-trailers [--trim-empty] [(<token>[(=|:)<value>])...]"),
+	NULL
+};
+
+int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
+{
+	int trim_empty = 0;
+
+	struct option options[] = {
+		OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options,
+			     git_interpret_trailers_usage, 0);
+
+	process_trailers(trim_empty, argc, argv);
+
+	return 0;
+}
diff --git a/git.c b/git.c
index 7cf2953..63a03eb 100644
--- a/git.c
+++ b/git.c
@@ -380,6 +380,7 @@ static struct cmd_struct commands[] = {
 	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
+	{ "interpret-trailers", cmd_interpret_trailers, RUN_SETUP },
 	{ "log", cmd_log, RUN_SETUP },
 	{ "ls-files", cmd_ls_files, RUN_SETUP },
 	{ "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 09/12] trailer: add tests for "git interpret-trailers"

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 t/t7513-interpret-trailers.sh | 336 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 336 insertions(+)
 create mode 100755 t/t7513-interpret-trailers.sh
Show changes to t/t7513-interpret-trailers.sh +336 −0
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
new file mode 100755
index 0000000..417a4f3
--- /dev/null
+++ b/t/t7513-interpret-trailers.sh
@@ -0,0 +1,336 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Christian Couder
+#
+
+test_description='git interpret-trailers'
+
+. ./test-lib.sh
+
+# When we want one trailing space at the end of each line, let's use sed
+# to make sure that these spaces are not removed by any automatic tool.
+
+test_expect_success 'setup' '
+	cat >basic_message <<-\EOF &&
+		subject
+
+		body
+	EOF
+	cat >complex_message_body <<-\EOF &&
+		my subject
+
+		my body which is long
+		and contains some special
+		chars like : = ? !
+
+	EOF
+	sed -e "s/ Z\$/ /" >complex_message_trailers <<-\EOF
+		Fixes: Z
+		Acked-by: Z
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+'
+
+test_expect_success 'without config' '
+	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
+		ack: Peff
+		Reviewed-by: Z
+		Acked-by: Johan
+	EOF
+	git interpret-trailers "ack = Peff" "Reviewed-by" "Acked-by: Johan" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--trim-empty without config' '
+	cat >expected <<-\EOF &&
+		ack: Peff
+		Acked-by: Johan
+	EOF
+	git interpret-trailers --trim-empty "ack = Peff" "Reviewed-by" "Acked-by: Johan" "sob:" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with config setup' '
+	git config trailer.ack.key "Acked-by: " &&
+	cat >expected <<-\EOF &&
+		Acked-by: Peff
+	EOF
+	git interpret-trailers --trim-empty "ack = Peff" >actual &&
+	test_cmp expected actual &&
+	git interpret-trailers --trim-empty "Acked-by = Peff" >actual &&
+	test_cmp expected actual &&
+	git interpret-trailers --trim-empty "Acked-by :Peff" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with config setup and = sign' '
+	git config trailer.ack.key "Acked-by= " &&
+	cat >expected <<-\EOF &&
+		Acked-by= Peff
+	EOF
+	git interpret-trailers --trim-empty "ack = Peff" >actual &&
+	test_cmp expected actual &&
+	git interpret-trailers --trim-empty "Acked-by= Peff" >actual &&
+	test_cmp expected actual &&
+	git interpret-trailers --trim-empty "Acked-by : Peff" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with config setup and # sign' '
+	git config trailer.bug.key "Bug #" &&
+	cat >expected <<-\EOF &&
+		Bug #42
+	EOF
+	git interpret-trailers --trim-empty "bug = 42" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with commit basic message' '
+	git interpret-trailers <basic_message >actual &&
+	test_cmp basic_message actual
+'
+
+test_expect_success 'with commit complex message' '
+	cat complex_message_body complex_message_trailers >complex_message &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Fixes: Z
+		Acked-by= Z
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with commit complex message and args' '
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+		Bug #42
+	EOF
+	git interpret-trailers "ack: Peff" "bug: 42" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with commit complex message, args and --trim-empty' '
+	cat complex_message_body >expected &&
+	cat >>expected <<-\EOF &&
+		Acked-by= Peff
+		Bug #42
+	EOF
+	git interpret-trailers --trim-empty "ack: Peff" "bug: 42" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "where = before"' '
+	git config trailer.bug.where "before" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "ack: Peff" "bug: 42" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "where = before" for a token in the middle of the message' '
+	git config trailer.review.key "Reviewed-by:" &&
+	git config trailer.review.where "before" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Reviewed-by: Johan
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "ack: Peff" "bug: 42" "review: Johan" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "where = before" and --trim-empty' '
+	cat complex_message_body >expected &&
+	cat >>expected <<-\EOF &&
+		Bug #46
+		Bug #42
+		Acked-by= Peff
+		Reviewed-by: Johan
+	EOF
+	git interpret-trailers --trim-empty "ack: Peff" "bug: 42" "review: Johan" "Bug: 46" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'the default is "ifExists = addIfDifferent"' '
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "ack: Peff" "review:" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifExists = addIfDifferent"' '
+	git config trailer.review.ifExists "addIfDifferent" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "ack: Peff" "review:" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifExists = addIfDifferentNeighbor"' '
+	git config trailer.ack.ifExists "addIfDifferentNeighbor" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "ack: Peff" "review:" "ack: Junio" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifExists = addIfDifferentNeighbor" and --trim-empty' '
+	git config trailer.ack.ifExists "addIfDifferentNeighbor" &&
+	cat complex_message_body >expected &&
+	cat >>expected <<-\EOF &&
+		Bug #42
+		Acked-by= Peff
+		Acked-by= Junio
+		Acked-by= Peff
+	EOF
+	git interpret-trailers --trim-empty "ack: Peff" "Acked-by= Peff" "review:" "ack: Junio" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifExists = add"' '
+	git config trailer.ack.ifExists "add" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Peff
+		Acked-by= Peff
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "ack: Peff" "Acked-by= Peff" "review:" "ack: Junio" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifExists = overwrite"' '
+	git config trailer.fix.key "Fixes:" &&
+	git config trailer.fix.ifExists "overwrite" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: 22
+		Acked-by= Z
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "review:" "fix=53" "ack: Junio" "fix=22" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifExists = doNothing"' '
+	git config trailer.fix.ifExists "doNothing" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "review:" "fix=53" "ack: Junio" "fix=22" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'the default is "ifMissing = add"' '
+	git config trailer.cc.key "Cc: " &&
+	git config trailer.cc.where "before" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Cc: Linus
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "review:" "fix=53" "cc=Linus" "ack: Junio" "fix=22" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifMissing = add"' '
+	git config trailer.cc.ifMissing "add" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Cc: Linus
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "review:" "fix=53" "ack: Junio" "fix=22" "bug: 42" "cc=Linus" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'using "ifMissing = doNothing"' '
+	git config trailer.cc.ifMissing "doNothing" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Bug #42
+		Fixes: Z
+		Acked-by= Z
+		Acked-by= Junio
+		Acked-by= Peff
+		Reviewed-by: Z
+		Signed-off-by: Z
+	EOF
+	git interpret-trailers "review:" "fix=53" "cc=Linus" "ack: Junio" "fix=22" "bug: 42" "ack: Peff" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 10/12] trailer: execute command from 'trailer.<name>.command'

Let the user specify a command that will give on its standard output the value to use for the specified trailer.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 trailer.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)
Show changes to trailer.c +63 −0
diff --git a/trailer.c b/trailer.c
index 5bb29ae..eeb2f86 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "run-command.h"
 #include "trailer.h"
 /*
  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
@@ -13,11 +14,14 @@ struct conf_info {
 	char *name;
 	char *key;
 	char *command;
+	unsigned command_uses_arg : 1;
 	enum action_where where;
 	enum action_if_exists if_exists;
 	enum action_if_missing if_missing;
 };
 
+#define TRAILER_ARG_STRING "$ARG"
+
 struct trailer_item {
 	struct trailer_item *previous;
 	struct trailer_item *next;
@@ -59,6 +63,13 @@ static inline int contains_only_spaces(const char *str)
 	return !*s;
 }
 
+static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b)
+{
+	const char *ptr = strstr(sb->buf, a);
+	if (ptr)
+		strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));
+}
+
 static void free_trailer_item(struct trailer_item *item)
 {
 	free(item->conf.name);
@@ -403,6 +414,7 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 		if (conf->command)
 			warning(_("more than one %s"), conf_key);
 		conf->command = xstrdup(value);
+		conf->command_uses_arg = !!strstr(conf->command, TRAILER_ARG_STRING);
 		break;
 	case TRAILER_WHERE:
 		lowercase_value = xstrdup_tolower(value);
@@ -445,6 +457,44 @@ static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *tra
 	return 0;
 }
 
+static int read_from_command(struct child_process *cp, struct strbuf *buf)
+{
+	if (run_command(cp))
+		return error("running trailer command '%s' failed", cp->argv[0]);
+	if (strbuf_read(buf, cp->out, 1024) < 1)
+		return error("reading from trailer command '%s' failed", cp->argv[0]);
+	strbuf_trim(buf);
+	return 0;
+}
+
+static const char *apply_command(const char *command, const char *arg)
+{
+	struct strbuf cmd = STRBUF_INIT;
+	struct strbuf buf = STRBUF_INIT;
+	struct child_process cp;
+	const char *argv[] = {NULL, NULL};
+	const char *result = "";
+
+	strbuf_addstr(&cmd, command);
+	if (arg)
+		strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+
+	argv[0] = cmd.buf;
+	memset(&cp, 0, sizeof(cp));
+	cp.argv = argv;
+	cp.env = local_repo_env;
+	cp.no_stdin = 1;
+	cp.out = -1;
+	cp.use_shell = 1;
+
+	if (read_from_command(&cp, &buf))
+		strbuf_release(&buf);
+	else
+		result = strbuf_detach(&buf, NULL);
+
+	strbuf_release(&cmd);
+	return result;
+}
 
 static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
 {
@@ -467,6 +517,10 @@ static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
 		duplicate_conf(&new->conf, &conf_item->conf);
 		new->token = xstrdup(conf_item->conf.key);
 		free(tok);
+		if (conf_item->conf.command_uses_arg || !val) {
+			new->value = apply_command(conf_item->conf.command, val);
+			free(val);
+		}
 	} else
 		new->token = tok;
 
@@ -522,12 +576,21 @@ static struct trailer_item *process_command_line_args(int argc, const char **arg
 	int i;
 	struct trailer_item *arg_tok_first = NULL;
 	struct trailer_item *arg_tok_last = NULL;
+	struct trailer_item *item;
 
 	for (i = 0; i < argc; i++) {
 		struct trailer_item *new = create_trailer_item(argv[i]);
 		add_trailer_item(&arg_tok_first, &arg_tok_last, new);
 	}
 
+	/* Add conf commands that don't use $ARG */
+	for (item = first_conf_item; item; item = item->next) {
+		if (item->conf.command && !item->conf.command_uses_arg) {
+			struct trailer_item *new = new_trailer_item(item, NULL, NULL);
+			add_trailer_item(&arg_tok_first, &arg_tok_last, new);
+		}
+	}
+
 	return arg_tok_first;
 }
 
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 11/12] trailer: add tests for commands in config file

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 t/t7513-interpret-trailers.sh | 71 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)
Show changes to t/t7513-interpret-trailers.sh +71 −0
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 417a4f3..0a1f3b6 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -333,4 +333,75 @@ test_expect_success 'using "ifMissing = doNothing"' '
 	test_cmp expected actual
 '
 
+test_expect_success 'with simple command' '
+	git config trailer.sign.key "Signed-off-by: " &&
+	git config trailer.sign.where "after" &&
+	git config trailer.sign.ifExists "addIfDifferentNeighbor" &&
+	git config trailer.sign.command "echo \"A U Thor <author@example.com>\"" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Fixes: Z
+		Acked-by= Z
+		Reviewed-by: Z
+		Signed-off-by: Z
+		Signed-off-by: A U Thor <author@example.com>
+	EOF
+	git interpret-trailers "review:" "fix=22" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with command using commiter information' '
+	git config trailer.sign.ifExists "addIfDifferent" &&
+	git config trailer.sign.command "echo \"\$GIT_COMMITTER_NAME <\$GIT_COMMITTER_EMAIL>\"" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Fixes: Z
+		Acked-by= Z
+		Reviewed-by: Z
+		Signed-off-by: Z
+		Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	git interpret-trailers "review:" "fix=22" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'with command using author information' '
+	git config trailer.sign.key "Signed-off-by: " &&
+	git config trailer.sign.where "after" &&
+	git config trailer.sign.ifExists "addIfDifferentNeighbor" &&
+	git config trailer.sign.command "echo \"\$GIT_AUTHOR_NAME <\$GIT_AUTHOR_EMAIL>\"" &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-\EOF &&
+		Fixes: Z
+		Acked-by= Z
+		Reviewed-by: Z
+		Signed-off-by: Z
+		Signed-off-by: A U Thor <author@example.com>
+	EOF
+	git interpret-trailers "review:" "fix=22" <complex_message >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'setup a commit' '
+	echo "Content of the first commit." > a.txt &&
+	git add a.txt &&
+	git commit -m "Add file a.txt"
+'
+
+test_expect_success 'with command using $ARG' '
+	git config trailer.fix.ifExists "overwrite" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" &&
+	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
+		Fixes: $FIXED
+		Acked-by= Z
+		Reviewed-by: Z
+		Signed-off-by: Z
+		Signed-off-by: A U Thor <author@example.com>
+	EOF
+	git interpret-trailers "review:" "fix=HEAD" <complex_message >actual &&
+	test_cmp expected actual
+'
+
 test_done
-- 
1.9.0.164.g3aa33cd.dirty
Christian Couder· Mar 26, 2014, 22:15 UTC · re: Christian Couder · lore

[PATCH v8 12/12] Documentation: add documentation for 'git interpret-trailers'

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 Documentation/git-interpret-trailers.txt | 123 +++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 Documentation/git-interpret-trailers.txt
Show changes to Documentation/git-interpret-trailers.txt +123 −0
diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
new file mode 100644
index 0000000..75ae386
--- /dev/null
+++ b/Documentation/git-interpret-trailers.txt
@@ -0,0 +1,123 @@
+git-interpret-trailers(1)
+=========================
+
+NAME
+----
+git-interpret-trailers - help add stuctured information into commit messages
+
+SYNOPSIS
+--------
+[verse]
+'git interpret-trailers' [--trim-empty] [(<token>[(=|:)<value>])...]
+
+DESCRIPTION
+-----------
+Help add RFC 822-like headers, called 'trailers', at the end of the
+otherwise free-form part of a commit message.
+
+This command is a filter. It reads the standard input for a commit
+message and applies the `token` arguments, if any, to this
+message. The resulting message is emited on the standard output.
+
+Some configuration variables control the way the `token` arguments are
+applied to the message and the way any existing trailer in the message
+is changed. They also make it possible to automatically add some
+trailers.
+
+By default, a 'token=value' or 'token:value' argument will be added
+only if no trailer with the same (token, value) pair is already in the
+message. The 'token' and 'value' parts will be trimmed to remove
+starting and trailing whitespace, and the resulting trimmed 'token'
+and 'value' will appear in the message like this:
+
+------------------------------------------------
+token: value
+------------------------------------------------
+
+By default, if there are already trailers with the same 'token', the
+new trailer will appear just after the last trailer with the same
+'token'. Otherwise it will appear at the end of the message.
+
+Note that 'trailers' do not follow and are not intended to follow many
+rules that are in RFC 822. For example they do not follow the line
+breaking rules, the encoding rules and probably many other rules.
+
+OPTIONS
+-------
+--trim-empty::
+	If the 'value' part of any trailer contains only whitespace,
+	the whole trailer will be removed from the resulting message.
+
+CONFIGURATION VARIABLES
+-----------------------
+
+trailer.<token>.key::
+	This 'key' will be used instead of 'token' in the
+	trailer. After some alphanumeric characters, it can contain
+	some non alphanumeric characters like ':', '=' or '#' that will
+	be used instead of ':' to separate the token from the value in
+	the trailer, though the default ':' is more standard.
+
+trailer.<token>.where::
+	This can be either `after`, which is the default, or
+	`before`. If it is `before`, then a trailer with the specified
+	token, will appear before, instead of after, other trailers
+	with the same token, or otherwise at the beginning, instead of
+	at the end, of all the trailers.
+
+trailer.<token>.ifexist::
+	This option makes it possible to choose what action will be
+	performed when there is already at least one trailer with the
+	same token in the message.
++
+The valid values for this option are: `addIfDifferent` (this is the
+default), `addIfDifferentNeighbor`, `add`, `overwrite` or `doNothing`.
++
+With `addIfDifferent`, a new trailer will be added only if no trailer
+with the same (token, value) pair is already in the message.
++
+With `addIfDifferentNeighbor`, a new trailer will be added only if no
+trailer with the same (token, value) pair is above or below the line
+where the new trailer will be added.
++
+With `add`, a new trailer will be added, even if some trailers with
+the same (token, value) pair are already in the message.
++
+With `overwrite`, the new trailer will overwrite an existing trailer
+with the same token.
++
+With `doNothing`, nothing will be done, that is no new trailer will be
+added if there is already one with the same token in the message.
+
+trailer.<token>.ifmissing::
+	This option makes it possible to choose what action will be
+	performed when there is not yet any trailer with the same
+	token in the message.
++
+The valid values for this option are: `add` (this is the default) and
+`doNothing`.
++
+With `add`, a new trailer will be added.
++
+With `doNothing`, nothing will be done.
+
+trailer.<token>.command::
+	This option can be used to specify a shell command that will
+	be used to automatically add or modify a trailer with the
+	specified 'token'.
++
+When this option is specified, it is like if a special 'token=value'
+argument is added at the end of the command line, where 'value' will
+be given by the standard output of the specified command.
++
+If the command contains the `$ARG` string, this string will be
+replaced with the 'value' part of an existing trailer with the same
+token, if any, before the command is launched.
+
+SEE ALSO
+--------
+linkgit:git-commit[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
1.9.0.164.g3aa33cd.dirty
Junio C Hamano· Mar 26, 2014, 23:05 UTC · re: Christian Couder · lore

Re: [PATCH v8 00/12] Add interpret-trailers builtin

Christian Couder <chriscool@tuxfamily.org> writes:
Show 7 quoted lines
> Until now git commit has only supported the well known
> "Signed-off-by: " trailer, that is used by many projects like
> the Linux kernel and Git.
>
> It is better to implement features for these trailers first in a
> new command rather than in builtin/commit.c, because this way the
> prepare-commit-msg and commit-msg hooks can reuse this command.
The "first" is somewhat questionable.

It is better to keep builtin/commit.c uncontaminated by any more hard-wired logic, like what we have for the signed-off-by line. Any new things can and should be doable in hooks, and this filter would help writing these hooks.

And that is why the design goal of the filter is to make it at least as powerful as the built-in logic we have for signed-off-by lines; that would allow us to later eject the hard-wired logic for signed-off-by line from the main codepath, if/when we wanted to.

Alternatively, we could build a library-ish API around this filter code and replace the hard-wired logic for signed-off-by line with a call into that API, if/when we wanted to, but that requires (in addition to the "at least as powerful as the built-in logic") that the implementation of this stand-alone filter can be cleanly made into a reusable library, so that is a bit higher bar to cross than "everything can be doable with hooks" alternative.

Show 8 quoted lines
> 3) Changes since version 7, thanks to Junio:
>
> * improved handling of empty trailer token
> * clearer way to create 'expected' files in tests
> * other small test cleanups
> * improved commit message
> * new way to parse config keys
> * strcasecmp() is not used anymore in some config related functions

It is unclear which of the 12 patches are unchanged since the last round. Are reviewers expected to re-read all of them?

> Some values from the config file are lowercased instead.
> To enable that a new patch (3/12) is introduced to rationalize
> lowercase related functions. I am not very happy with these
> changes.

I can see why you are not very happy. Perhaps it may make you happier if you did not move lowercase() at all, did the xstrdup_tolower() in a cleaner and more efficient way, and only used the latter in the code?

Christian Couder· Mar 27, 2014, 07:53 UTC · re: Junio C Hamano · lore

Re: [PATCH v8 00/12] Add interpret-trailers builtin

From: Junio C Hamano <gitster@pobox.com>
Show 29 quoted lines
> Christian Couder <chriscool@tuxfamily.org> writes:
> 
>> Until now git commit has only supported the well known
>> "Signed-off-by: " trailer, that is used by many projects like
>> the Linux kernel and Git.
>>
>> It is better to implement features for these trailers first in a
>> new command rather than in builtin/commit.c, because this way the
>> prepare-commit-msg and commit-msg hooks can reuse this command.
> 
> The "first" is somewhat questionable.
> 
> It is better to keep builtin/commit.c uncontaminated by any more
> hard-wired logic, like what we have for the signed-off-by line.  Any
> new things can and should be doable in hooks, and this filter would
> help writing these hooks.
> 
> And that is why the design goal of the filter is to make it at least
> as powerful as the built-in logic we have for signed-off-by lines;
> that would allow us to later eject the hard-wired logic for
> signed-off-by line from the main codepath, if/when we wanted to.
> 
> Alternatively, we could build a library-ish API around this filter
> code and replace the hard-wired logic for signed-off-by line with a
> call into that API, if/when we wanted to, but that requires (in
> addition to the "at least as powerful as the built-in logic") that
> the implementation of this stand-alone filter can be cleanly made
> into a reusable library, so that is a bit higher bar to cross than
> "everything can be doable with hooks" alternative.
Ok, I will try to improve this part of the Rationale section.

Thanks, Christian.

← back to recent threads