[PATCH v5 09/14] trailer: if no input file is passed, read from stdin

2014-02-06 Thread Christian Couder
It is simpler and more natural if the git interpret-trailers
is made a filter as its output already goes to sdtout.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 builtin/interpret-trailers.c  |  2 +-
 t/t7513-interpret-trailers.sh |  7 +++
 trailer.c | 15 +--
 3 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 04b0ae2..ae8e561 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -23,7 +23,7 @@ int cmd_interpret_trailers(int argc, const char **argv, const 
char *prefix)
 
struct option options[] = {
OPT_BOOL(0, trim-empty, trim_empty, N_(trim empty 
trailers)),
-   OPT_FILENAME(0, infile, infile, N_(use message from file)),
+   OPT_FILENAME(0, infile, infile, N_(use message from file, 
instead of stdin)),
OPT_END()
};
 
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 8be333c..f5ef81f 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -205,4 +205,11 @@ test_expect_success 'using ifMissing = doNothing' '
test_cmp expected actual
 '
 
+test_expect_success 'with input from stdin' '
+   cat complex_message_body expected 
+   printf Bug #42\nFixes: \nAcked-by= \nAcked-by= Junio\nAcked-by= 
Peff\nReviewed-by: \nSigned-off-by: \n expected 
+   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
diff --git a/trailer.c b/trailer.c
index 186316f..108e104 100644
--- a/trailer.c
+++ b/trailer.c
@@ -483,8 +483,13 @@ static struct strbuf **read_input_file(const char *infile)
 {
struct strbuf sb = STRBUF_INIT;
 
-   if (strbuf_read_file(sb, infile, 0)  0)
-   die_errno(_(could not read input file '%s'), infile);
+   if (infile) {
+   if (strbuf_read_file(sb, infile, 0)  0)
+   die_errno(_(could not read input file '%s'), infile);
+   } else {
+   if (strbuf_read(sb, fileno(stdin), 0)  0)
+   die_errno(_(could not read from stdin));
+   }
 
return strbuf_split(sb, '\n');
 }
@@ -551,10 +556,8 @@ void process_trailers(const char *infile, int trim_empty, 
int argc, const char *
 
git_config(git_trailer_config, NULL);
 
-   /* Print the non trailer part of infile */
-   if (infile) {
-   process_input_file(infile, infile_tok_first, infile_tok_last);
-   }
+   /* Print the non trailer part of infile (or stdin if infile is NULL) */
+   process_input_file(infile, infile_tok_first, infile_tok_last);
 
arg_tok_first = process_command_line_args(argc, argv);
 
-- 
1.8.5.2.206.g98f5689.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-06 Thread Christian Couder
This patch implements the logic that process trailers
from file and arguments.

At the beginning trailers from file are in their own
infile_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 infile_tok list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 187 ++
 1 file changed, 187 insertions(+)

diff --git a/trailer.c b/trailer.c
index f129b5a..ba0cfe0 100644
--- a/trailer.c
+++ b/trailer.c
@@ -46,3 +46,190 @@ static inline size_t alnum_len(const char *buf, int len)
while (--len = 0  !isalnum(buf[len]));
return (size_t) len + 1;
 }
+
+static void add_arg_to_infile(struct trailer_item *infile_tok,
+ struct trailer_item *arg_tok)
+{
+   if (arg_tok-conf-where == WHERE_AFTER) {
+   arg_tok-next = infile_tok-next;
+   infile_tok-next = arg_tok;
+   arg_tok-previous = infile_tok;
+   if (arg_tok-next)
+   arg_tok-next-previous = arg_tok;
+   } else {
+   arg_tok-previous = infile_tok-previous;
+   infile_tok-previous = arg_tok;
+   arg_tok-next = infile_tok;
+   if (arg_tok-previous)
+   arg_tok-previous-next = arg_tok;
+   }
+}
+
+static int check_if_different(struct trailer_item *infile_tok,
+ struct trailer_item *arg_tok,
+ int alnum_len, int check_all)
+{
+   enum action_where where = arg_tok-conf-where;
+   do {
+   if (!infile_tok)
+   return 1;
+   if (same_trailer(infile_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
+*/
+   infile_tok = (where == WHERE_AFTER) ? infile_tok-previous : 
infile_tok-next;
+   } while (check_all);
+   return 1;
+}
+
+static void apply_arg_if_exist(struct trailer_item *infile_tok,
+  struct trailer_item *arg_tok,
+  int alnum_len)
+{
+   switch (arg_tok-conf-if_exist) {
+   case EXIST_DO_NOTHING:
+   free(arg_tok);
+   break;
+   case EXIST_OVERWRITE:
+   free((char *)infile_tok-value);
+   infile_tok-value = xstrdup(arg_tok-value);
+   free(arg_tok);
+   break;
+   case EXIST_ADD:
+   add_arg_to_infile(infile_tok, arg_tok);
+   break;
+   case EXIST_ADD_IF_DIFFERENT:
+   if (check_if_different(infile_tok, arg_tok, alnum_len, 1))
+   add_arg_to_infile(infile_tok, arg_tok);
+   else
+   free(arg_tok);
+   break;
+   case EXIST_ADD_IF_DIFFERENT_NEIGHBOR:
+   if (check_if_different(infile_tok, arg_tok, alnum_len, 0))
+   add_arg_to_infile(infile_tok, arg_tok);
+   else
+   free(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_infile_tok(struct trailer_item *infile_tok,
+  struct trailer_item **arg_tok_first,
+  enum action_where where)
+{
+   struct trailer_item *arg_tok;
+   struct trailer_item *next_arg;
+
+   int tok_alnum_len = alnum_len(infile_tok-token, 
strlen(infile_tok-token));
+   for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
+   next_arg = arg_tok-next;
+   if (same_token(infile_tok, arg_tok, tok_alnum_len) 
+   arg_tok-conf-where == where) {
+   remove_from_list(arg_tok, arg_tok_first);
+   apply_arg_if_exist(infile_tok, arg_tok, tok_alnum_len);
+   /*
+* If arg has been added to infile,
+* then we need to process it too now.
+*/
+   if ((where == WHERE_AFTER ? infile_tok-next : 
infile_tok-previous) == arg_tok

[PATCH v5 05/14] trailer: parse trailers from input file

2014-02-06 Thread Christian Couder
This patch reads trailers from an input file, parses
them and puts the result into a doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 71 +++
 1 file changed, 71 insertions(+)

diff --git a/trailer.c b/trailer.c
index 3fde21d..f59efd1 100644
--- a/trailer.c
+++ b/trailer.c
@@ -49,6 +49,13 @@ static inline size_t alnum_len(const char *buf, int len)
return (size_t) len + 1;
 }
 
+static inline int contains_only_spaces(const char *str)
+{
+   const char *s;
+   for (s = str; *s  isspace(*s); s++);
+   return !*s;
+}
+
 static void add_arg_to_infile(struct trailer_item *infile_tok,
  struct trailer_item *arg_tok)
 {
@@ -451,3 +458,67 @@ static struct trailer_item *process_command_line_args(int 
argc, const char **arg
 
return arg_tok_first;
 }
+
+static struct strbuf **read_input_file(const char *infile)
+{
+   struct strbuf sb = STRBUF_INIT;
+
+   if (strbuf_read_file(sb, infile, 0)  0)
+   die_errno(_(could not read input file '%s'), infile);
+
+   return strbuf_split(sb, '\n');
+}
+
+/*
+ * 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 count, start, empty = 1;
+
+   /* Get the line count */
+   for (count = 0; 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_input_file(const char *infile,
+  struct trailer_item **infile_tok_first,
+  struct trailer_item **infile_tok_last)
+{
+   struct strbuf **lines = read_input_file(infile);
+   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(infile_tok_first, infile_tok_last, new);
+   }
+
+   strbuf_list_free(lines);
+}
-- 
1.8.5.2.206.g98f5689.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 10/14] trailer: execute command from 'trailer.name.command'

2014-02-06 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 63 +++
 1 file changed, 63 insertions(+)

diff --git a/trailer.c b/trailer.c
index 108e104..98187fc 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include cache.h
+#include run-command.h
 /*
  * Copyright (c) 2013 Christian Couder chrisc...@tuxfamily.org
  */
@@ -12,11 +13,14 @@ struct conf_info {
char *name;
char *key;
char *command;
+   unsigned command_uses_arg : 1;
enum action_where where;
enum action_if_exist if_exist;
enum action_if_missing if_missing;
 };
 
+#define TRAILER_ARG_STRING $ARG
+
 struct trailer_item {
struct trailer_item *previous;
struct trailer_item *next;
@@ -56,6 +60,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 print_tok_val(const char *tok, const char *val)
 {
char c = tok[strlen(tok) - 1];
@@ -375,6 +386,7 @@ static int git_trailer_config(const char *conf_key, const 
char *value, void *cb)
if (conf-command)
warning(_(more than one %s), orig_conf_key);
conf-command = xstrdup(value);
+   conf-command_uses_arg = !!strstr(conf-command, 
TRAILER_ARG_STRING);
break;
case TRAILER_WHERE:
if (set_where(conf, value))
@@ -411,6 +423,45 @@ static void parse_trailer(struct strbuf *tok, struct 
strbuf *val, const char *tr
}
 }
 
+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 struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
 const char* tok, const char* val)
 {
@@ -420,6 +471,8 @@ static struct trailer_item *new_trailer_item(struct 
trailer_item *conf_item,
if (conf_item) {
new-conf = conf_item-conf;
new-token = xstrdup(conf_item-conf-key);
+   if (conf_item-conf-command_uses_arg || !val)
+   new-value = apply_command(conf_item-conf-command, 
val);
} else {
new-conf = xcalloc(sizeof(struct conf_info), 1);
new-token = tok;
@@ -470,12 +523,22 @@ 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.8.5.2.206.g98f5689.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v5 04/14] trailer: process command line trailer arguments

2014-02-06 Thread Christian Couder
This patch parses the trailer command line arguments
and put the result into an arg_tok doubly linked
list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 84 +++
 1 file changed, 84 insertions(+)

diff --git a/trailer.c b/trailer.c
index f844f46..3fde21d 100644
--- a/trailer.c
+++ b/trailer.c
@@ -367,3 +367,87 @@ static int git_trailer_config(const char *conf_key, const 
char *value, void *cb)
}
return 0;
 }
+
+static void parse_trailer(struct strbuf *tok, struct strbuf *val, const char 
*trailer)
+{
+   const char *end = strchr(trailer, '=');
+   if (!end)
+   end = strchr(trailer, ':');
+   if (end) {
+   strbuf_add(tok, trailer, end - trailer);
+   strbuf_trim(tok);
+   strbuf_addstr(val, end + 1);
+   strbuf_trim(val);
+   } else {
+   strbuf_addstr(tok, trailer);
+   strbuf_trim(tok);
+   }
+}
+
+static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
+const char* tok, const char* val)
+{
+   struct trailer_item *new = xcalloc(sizeof(*new), 1);
+   new-value = val;
+
+   if (conf_item) {
+   new-conf = conf_item-conf;
+   new-token = xstrdup(conf_item-conf-key);
+   } else {
+   new-conf = xcalloc(sizeof(struct conf_info), 1);
+   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;
+
+   parse_trailer(tok, val, string);
+
+   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 (!*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.8.5.2.206.g98f5689.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 07/14] trailer: add interpret-trailers command

2014-02-07 Thread Christian Couder
On Fri, Feb 7, 2014 at 1:10 AM, Junio C Hamano gits...@pobox.com wrote:
 Christian Couder chrisc...@tuxfamily.org writes:

 diff --git a/git.c b/git.c
 index 3799514..1420b58 100644
 --- a/git.c
 +++ b/git.c
 @@ -383,6 +383,7 @@ static void handle_internal_command(int argc, const char 
 **argv)
   { 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 },

 Does this even need to have a git repository?  What is the RUN_SETUP
 for?

It needs to read git config files, but it could work without reading them too.
I will have another look at it.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-02-09 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder chrisc...@tuxfamily.org writes:
 
 +enum action_where { WHERE_AFTER, WHERE_BEFORE };
 +enum action_if_exist { EXIST_ADD_IF_DIFFERENT, 
 EXIST_ADD_IF_DIFFERENT_NEIGHBOR,
 +   EXIST_ADD, EXIST_OVERWRITE, EXIST_DO_NOTHING };
 +enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
 
 All these names and conf_info below are not named to be specific
 to this little tool.  Can I assume that these will never be exposed
 to the rest of the system?  If so, they are fine.

Yeah, I don't plan them to be exposed to other files.
 
 +struct conf_info {
 +char *name;
 +char *key;
 +char *command;
 +enum action_where where;
 +enum action_if_exist if_exist;
 +enum action_if_missing if_missing;
 
 It still feels somewhat strange.  It is true that an item can be
 either exist or missing and it is understandable that it tempts
 you to split that into two, but EXIST_OVERWRITE will not trigger
 either WHERE_AFTER or WHERE_BEFORE action.

Yeah, it's true that WHERE_AFTER/WHERE_BEFORE does not make sense for
EXIST_OVERWRITE, EXIST_DO_NOTHING and MISSING_DO_NOTHING, but it's a
fact of life that sometimes some options do not make sense with
others.

 +static inline int same_token(struct trailer_item *a, struct trailer_item 
 *b, int alnum_len)
 +{
 +return !strncasecmp(a-token, b-token, alnum_len);
 +}
 +
 +static inline int same_value(struct trailer_item *a, struct trailer_item *b)
 +{
 +return !strcasecmp(a-value, b-value);
 +}
 +
 +static inline 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);
 +}
 
 All these inlines look premature optimization that can be
 delegated to any decent compiler, don't they?

Yeah, but as Eric suggested to add them like in header files and you
did not reply to him, I thought you agreed with him.
I will remove them.

 +/* Get the length of buf from its beginning until its last alphanumeric 
 character */
 +static inline size_t alnum_len(const char *buf, int len)
 +{
 +while (--len = 0  !isalnum(buf[len]));
 
 Style:
 
   while (--len = 0  !isalnum(buf[len]))
   ;
 
 You may add a comment on the empty statement to make it stand out
 even more, i.e.
 
   ; /* nothing */

Ok, I will do that.

 +return (size_t) len + 1;
 
 This is somewhat unfortunate.  if the caller wants to receive
 size_t, perhaps it should be passing in size_t (or ssize_t) to the
 function?  Hard to guess without an actual caller, though.

Ok, I will make it return an int.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-02-09 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder chrisc...@tuxfamily.org writes:
 
 +enum action_if_exist if_exist;
 +enum action_if_missing if_missing;
 
 Probably if_exists is more gramatically correct.
 
   if (x-if_exists) {
   ... do this ...
   }
 
 would read well, but not x-if_exist.

Ok, I will use if_exists instead of if_exist and also:

enum action_if_exists { EXISTS_ADD_IF_DIFFERENT, 
EXISTS_ADD_IF_DIFFERENT_NEIGHBOR,
EXISTS_ADD, EXISTS_OVERWRITE, EXISTS_DO_NOTHING };

instead of:

enum action_if_exist { EXIST_ADD_IF_DIFFERENT, EXIST_ADD_IF_DIFFERENT_NEIGHBOR,
EXIST_ADD, EXIST_OVERWRITE, EXIST_DO_NOTHING };

to be consistent.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-09 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder chrisc...@tuxfamily.org writes:
 
 +static void apply_arg_if_exist(struct trailer_item *infile_tok,
 +   struct trailer_item *arg_tok,
 +   int alnum_len)
 +{
 +switch (arg_tok-conf-if_exist) {
 +case EXIST_DO_NOTHING:
 +free(arg_tok);
 +break;
 +case EXIST_OVERWRITE:
 +free((char *)infile_tok-value);
 +infile_tok-value = xstrdup(arg_tok-value);
 +free(arg_tok);
 +break;
 +case EXIST_ADD:
 +add_arg_to_infile(infile_tok, arg_tok);
 +break;
 +case EXIST_ADD_IF_DIFFERENT:
 +if (check_if_different(infile_tok, arg_tok, alnum_len, 1))
 +add_arg_to_infile(infile_tok, arg_tok);
 +else
 +free(arg_tok);
 +break;
 +case EXIST_ADD_IF_DIFFERENT_NEIGHBOR:
 +if (check_if_different(infile_tok, arg_tok, alnum_len, 0))
 +add_arg_to_infile(infile_tok, arg_tok);
 +else
 +free(arg_tok);
 +break;
 
 Makes me wonder if people want a rule to say if the same key
 already exists, regardless of the value.

This is what if_exists and if_missing are all about.

Either:

the same key already exists regardless of the value

and, in this case, what happens depends on what has been specified using
the if_exists configuration variable.

Or:

the same key DOES NOT already exists regardless of the value

and in this case, what happens depends on what has been specified
using the if_missing configuration variable.

 +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;
 +}
 
 Will callers free the item that now is not on the list?

Yes, or the item will be inserted into another list.

 +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_infile_tok(struct trailer_item *infile_tok,
 +   struct trailer_item **arg_tok_first,
 +   enum action_where where)
 +{
 +struct trailer_item *arg_tok;
 +struct trailer_item *next_arg;
 +
 +int tok_alnum_len = alnum_len(infile_tok-token, 
 strlen(infile_tok-token));
 +for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
 +next_arg = arg_tok-next;
 +if (same_token(infile_tok, arg_tok, tok_alnum_len) 
 +arg_tok-conf-where == where) {
 +remove_from_list(arg_tok, arg_tok_first);
 +apply_arg_if_exist(infile_tok, arg_tok, tok_alnum_len);
 +/*
 + * If arg has been added to infile,
 + * then we need to process it too now.
 + */
 +if ((where == WHERE_AFTER ? infile_tok-next : 
 infile_tok-previous) == arg_tok)
 +infile_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 **infile_tok_first,
 + struct trailer_item **infile_tok_last,
 + struct trailer_item *arg_tok)
 +{
 
 Makes me wonder if it would make the code simpler to keep an anchor
 item struct trailer_item that is off heap, and pass that single
 anchor item around, using its next/prev fields as the first and the
 last.  Wouldn't it let you remove the special cases for the first
 and last item?

Yeah, that could work. On the other hand the other fields of this
special item would not be used for anything.
I will have a look at it.

 +struct trailer_item **infile_tok;
 +enum action_where where;
 +
 +switch (arg_tok-conf-if_missing) {
 +case MISSING_DO_NOTHING:
 +free(arg_tok);
 +break;
 +case MISSING_ADD:
 +where = arg_tok-conf-where;
 +infile_tok = (where == WHERE_AFTER) ? infile_tok_last : 
 infile_tok_first;
 +if (*infile_tok) {
 +add_arg_to_infile(*infile_tok, arg_tok);
 +*infile_tok = arg_tok;
 +} else

Re: [PATCH v4 10/17] trailer: if no input file is passed, read from stdin

2014-02-09 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com
Subject: Re: [PATCH v4 10/17] trailer: if no input file is passed, read from 
stdin
Date: Thu, 06 Feb 2014 13:51:36 -0800

 Christian Couder chrisc...@tuxfamily.org writes:
 
 It is simpler and more natural if the git interpret-trailers
 is made a filter as its output already goes to sdtout.
 
 sdtout???

Sorry, I have a hard time spelling porprelly these days :-)

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 04/14] trailer: process command line trailer arguments

2014-02-09 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com
Subject: Re: [PATCH v5 04/14] trailer: process command line trailer arguments
Date: Thu, 06 Feb 2014 16:08:24 -0800

 Christian Couder chrisc...@tuxfamily.org writes:
 
 This patch parses the trailer command line arguments
 and put the result into an arg_tok doubly linked
 list.
 
 No the patch doesn't parse anything ;-).
 
 Parse the command line arguments and 
 
 +static void parse_trailer(struct strbuf *tok, struct strbuf *val, const 
 char *trailer)
 +{
 +const char *end = strchr(trailer, '=');
 +if (!end)
 +end = strchr(trailer, ':');
 
 How would you explain the behaviour of the above code for this
 input?
 
   frotz: nitfol=xyzzy
 
 Perhaps strcspn()?

Ok to use strcspn().

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-10 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com
Subject: Re: [PATCH v5 02/14] trailer: process trailers from file and arguments
Date: Mon, 10 Feb 2014 10:14:34 -0800

 Christian Couder chrisc...@tuxfamily.org writes:
 
 This is what if_exists and if_missing are all about.

 Either:

  the same key already exists regardless of the value

 and, in this case, what happens depends on what has been specified using
 the if_exists configuration variable.

 Or:

  the same key DOES NOT already exists regardless of the value

 and in this case, what happens depends on what has been specified
 using the if_missing configuration variable.
 
 Hmm, how should things like signed-off-by be handled?  You may want
 to allow many entries with the same key with distinct values, but
 duplicated values may want to be handled differently (currently we
 only avoid to place a duplicate key, value consecutively, but keys
 with different semantics (e.g. fixes-bug: #bugid) may want to say
 unless the same key with the same value exists, append it at the
 end.

Many entries with the same key but distinct values can be configured
using:

if_exists = add_if_different
if_missing = add

Many entries with the same key but values that can be the same can be
configured using:

if_exists = add
if_missing = add

The place where the trailers are added, if they should be added, can
be selected using either where = after, the default, or where =
before.

where = after means just after trailers with the same key, or, if
there are no trailers with the same key, after all the existing
trailers.

where = before means just before trailers with the same key, or, if
there are no trailers with the same key, before all the existing
trailers.

I think that this is enough to handle most of the usual cases, but we
can add other more complex options later, as I said in the last
message of this thread:

http://thread.gmane.org/gmane.comp.version-control.git/237278/

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-11 Thread Christian Couder
On Mon, Feb 10, 2014 at 9:51 PM, Junio C Hamano gits...@pobox.com wrote:
 Christian Couder chrisc...@tuxfamily.org writes:

 Many entries with the same key but distinct values can be configured
 using:

 if_exists = add_if_different
 if_missing = add

 Many entries with the same key but values that can be the same can be
 configured using:

 if_exists = add
 if_missing = add

 While the above certainly can express the combinations, I am still
 puzzled about the value of having under this condition (i.e.
 if-exists/if-missing) and do this (i.e. add/add-if-different) as
 two separate concepts.

It is because there are many possible conditions under which the user
might want to do different things, and the syntax of our configuration
files is not a programming language, so it is not well suited to
nicely express what the user might want under different conditions.

That's what I tried to show in my last message in the thread I sent
the link to in my previous message.

(http://thread.gmane.org/gmane.comp.version-control.git/237278/)

So we have to choose a few conditions to avoid possible combinatorial
explosion in the number of possible values.

 If you do not have an existing entry with the same key, no new entry
 can be the same as an existing one---therefore, the former allow
 only distinct values for the same key can just say

   trailer.Fixes.action = add_if_different

 or something, without any if_exists/if_missing.  In a similar way,
 the latter duplicated values allowed for the same key can say

   trailer.Sob.action = add

 You can throw into the mix other actions like add_if_missing, you
 can also express only one value allowed for the same key---the
 first one wins, replace to mean replace if there is one with the
 same key, and replace_or_add to mean same as 'replace', but add
 if there is no existing entries with the same key.  Will we lose
 expressiveness by simplifying the semantics, getting rid of this
 under this condition part and instead making do this part more
 intelligent?

I don't think we will lose expressiveness, but I think there might be
a combinatorial explosion in the number of choices.

Right now there are 2 choices for if_missing and 5 choices for
if_exists. This means that if we use only one action config
variable and want to have the same expressiveness, we need 10 choices.

It doesn't seem a big difference now, but, if we add more choices,
then the difference will increase a lot.

 Even if we assume, for the sake of discussion, that it *is* a good
 idea to separate under this condition part and do this part, I
 do not think the above is the only or the best way to express
 distinct values allowed for the same key.  How do we argue that
 this from your example

   if_exists = add_if_different
   if_missing = add

 is a better design than this, for example?

   if_key_value_exists = ignore ; exact matching key,val exists
   if_key_exists = add  ; otherwise
   if_missing = add

The question is what happens if we want to say if the same key,
value pair exists but not near where we would add.

With the solution I implemented, that is:

if_exists = add_if_different_neighbor

With the solution you suggest, should we have:

  if_key_value_exists_not_neighbor = add ; exact matching key,val
exists but not near where we would add
  if_key_value_exists = ignore ; exact matching key,val exists
  if_key_exists = add  ; otherwise
  if_missing = add

or:

  if_key_value_exists = ignore_if_neighbor ; exact matching key,val exists
  if_key_exists = add  ; otherwise
  if_missing = add

And what happens if we want to say if key exists and value matches
regex, how do we express that then?
Or what happens when we want to say if key exists and command succeeds?

With what I suggest, we can still say:

if_exists = add_if_values_matches regex

or

if_exists = add_if_succeeds cmd

With the solution you suggest, should it be:

  if_key_value_exists = ignore
  if_key_exists = add_if_values_matches regex

and

  if_key_value_exists = ignore
  if_key_exists = add_if_succeeds cmd

?

Doesn't it look like redondant between the 2 lines?

And then people might ask if they can also use something like this:

  if_key_value_exists = add_if_succeeds cmd1
  if_key_exists = add_if_succeeds cmd2
  if_key_missing = add_if_succeeds cmd3

and it will look like we are trying too much to do what should be done
in only one script.

 The latter splits remaining conditional logic from do this part
 (no more add_if_different that causes add action to see if there
 is the same key and act differently) and moves it to under this
 condition part.

 I am trying to illustrate that the line to split the under this
 condition and do this looks arbitrary and fuzzy here, and because
 of this arbitrary-ness and fuzziness, it is not very obvious to me
 why it is a good idea to have under this condition as a separate
 concept from do this settings.

 What is the advantage of having such an extra axis?  Does

Re: [RFH] hackday and GSoC topic suggestions

2014-02-13 Thread Christian Couder
On Thu, Feb 13, 2014 at 9:50 AM, Jeff King p...@peff.net wrote:
 On Thu, Feb 06, 2014 at 10:51:54AM +0100, Matthieu Moy wrote:

  Some of Matthieu's students worked on it a few years ago but didn't finish.

 Right. There was still quite some work to do, but this is most likely
 too small for a GSoC project. But that could be a part of it. I'm not
 sure how google welcomes GSoC projects made of multiple small tasks, but
 my experience with students is that it's much better than a single (too)
 big task, and I think that was the general feeling on this list when we
 discussed it last year.

 I think Google leaves it up to us to decide. I'd be OK with a project
 made of multiple small tasks, as I think it would be an interesting
 experiment.  I'd rather not do all of them like that, though. And bonus
 points if they are on a theme that will let the student use the ramp-up
 time from one for another.

Yeah, a student working on the git bisect fix/unfixed feature, could
fix git bisect testing too many merge bases, and if there is still
time work on moving more code from shell to C.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-14 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 These numerous questions you have to ask are indications why
 choosing this condition goes to the left hand side of the equal
 sign (e.g. exists) and this condition goes to the right hand side
 (e.g. do-this-if_neighbor) is not working well.  The user has to
 remember which conditions go to the variable name and which
 conditions go to the action part.

If we limit it to if_exists and if_missing, the user can remember
that without things becoming too complex.

 That is, not splitting the logic into two parts like you did,
 i.e. if_X = do_Y_if_Z, which invites why is it not if_true =
 do_Y_if_X_and_Z, if_X_and_Z = do_Y, if_Z = do_Y_if_X?

It perhaps invite it, but there are reasons why splitting the logic
too much is not a good idea and why limiting the split to if_exists
and if_missing is a good tradeoff.

 One possible way to avoid the confusion is to say do_Y_if_X_and_Z
 without making the rule split into conditions and actions---I am
 NOT saying that is _better_, there may be other solutions to avoid
 this two-part logic in a cleaner way.

This has been discussed first last November and I don't think that a
solution better than what I implemented has been suggested.

The problem is we not only want to say:

action = do_Y_if_X_and_Z

but we also want to say:

action = do_Y_if_X_and_Z AND do_U_if_V

For example some people might want:

if_exists = overwrite
if_missing = add

while others might want:

if_exists = overwrite
if_missing = do_nothing

and I don't see how we can say that with just:

action = do_Y_if_X_and_Z

Thanks,
Christian.

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-14 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder chrisc...@tuxfamily.org writes:
 
 For example some people might want:

 if_exists = overwrite
 if_missing = add

 while others might want:

 if_exists = overwrite
 if_missing = do_nothing

 and I don't see how we can say that with just:

 action = do_Y_if_X_and_Z
 
 Yes, but then we go back to my original question: why exists and
 missing are so special,

Because they are completely disjoint, easy to understand, and they can
avoid a lot of combinatorial explosion we would have if we used only
one action variable, while still providing lot of expressiveness.

They are just a good tradeoff for the special problem we have.

 and why there aren't two kinds of exists
 (i.e. there exists an entry with the same key, value vs there
 exists an entry with the same key).

Because it doesn't improve expressiveness much, doesn't remove much
combinatorial explosion and make it significantly more difficult to
understand, compared to only if_exists and if_missing.

 I would have understood your
 this is not too hard to understand for users if you had three
 (i.e. missing, in addition to these two flavours of exists), but
 with only two, I do not see how it is useful in a hypothetical
 situation like above.

You mean that you do not see why:

 if_exists = overwrite
 if_missing = do_nothing

is simple and expressive?

 For example, how would you express something like this only with
 if-exists vs if-missing?
 
   if_exists_exactly = ignore
 if_exists_with_different_value = append
 if_missng = prepend_to_the_beginning

First, previously in the discussion you said that you didn't want us
to talk about the where = (after | before) part, because you could
see that it was orthogonal to the other stuff, but now it looks like
you want again to put that on the table.

Then yes, it is not possible to express the above with what I
implemented. But it could be possible with only if-exists vs
if-missing like this:

if_exists = append_if_different
if_missing = prepend

And yes I think it is much better than:

if_exists_exactly = ignore
if_exists_with_different_value = append
if_missng = prepend_to_the_beginning

because we can still easily express things like:

if_exists = append_if_different_neighbor
if_missing = prepend

while it would be more difficult to understand with
if_exists_exactly, if_exists_with_different_value and
if_missing.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v5 02/14] trailer: process trailers from file and arguments

2014-02-23 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Junio C Hamano gits...@pobox.com writes:

 Having said all that, it appears that nobody seems to be able to
 come up with a saner arrangement that would not paint us into a
 tough corner that we would not be able to later escape from without
 being backward incompatible---I certainly didn't.
 
 So... let's take this from your earlier message:
 
 If we limit it to if_exists and if_missing, the user can remember
 that without things becoming too complex.
 
 and go with the semantics the posted patches (I believe I have the
 latest from you on 'pu') attempt to implement, at least for now.
 
 IOW, when re-rolling, let's not try changing the arrangement to use
 if-exists/if-missing (configuration variable names) for keys'
 existence and include chosen set of conditions on values as
 modifiers to the action (i.e. X in do_Y_in_X).

Ok, will re-roll soon.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH 1/6] Add docstrings for lookup_replace_object() and do_lookup_replace_object()

2014-02-24 Thread Christian Couder
On Fri, Feb 21, 2014 at 5:32 PM, Michael Haggerty mhag...@alum.mit.edu wrote:
 Signed-off-by: Michael Haggerty mhag...@alum.mit.edu
 ---
  cache.h | 16 
  1 file changed, 16 insertions(+)

 diff --git a/cache.h b/cache.h
 index dc040fb..0ecd1c8 100644
 --- a/cache.h
 +++ b/cache.h
 @@ -788,13 +788,29 @@ static inline void *read_sha1_file(const unsigned char 
 *sha1, enum object_type *
  {
 return read_sha1_file_extended(sha1, type, size, 
 LOOKUP_REPLACE_OBJECT);
  }
 +
 +/*
 + * If a replacement for object sha1 has been set up, return the
 + * replacement object's name (replaced recursively, if necessary).
 + * The return value is either sha1 or a pointer to a
 + * permanently-allocated value.  This function always respects replace
 + * references, regardless of the value of check_replace_refs.

Here you talk about check_replace_refs ...

 + */
  extern const unsigned char *do_lookup_replace_object(const unsigned char 
 *sha1);
 +
 +/*
 + * If object sha1 should be replaced, return the replacement object's
 + * name.  This function is similar to do_lookup_replace_object(),
 + * except that it when object replacement is suppressed, it always
 + * returns its argument unchanged.
 + */
  static inline const unsigned char *lookup_replace_object(const unsigned char 
 *sha1)
  {
 if (!read_replace_refs)

... but here read_replace_refs is used.

 return sha1;
 return do_lookup_replace_object(sha1);
  }

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: An idea for git bisect and a GSoC enquiry

2014-02-27 Thread Christian Couder
Hi,

On Wed, Feb 26, 2014 at 9:28 AM, Jacopo Notarstefano
jacopo.notarstef...@gmail.com wrote:
 Hey everyone,

 my name is Jacopo, a student developer from Italy, and I'm interested
 in applying to this years' Google Summer of Code. I set my eyes on the
 project called git-bisect improvements, in particular the subtask
 about swapping the good and bad labels when looking for a
 bug-fixing release.

 I have a very simple proposal for that: add a new mark subcommand.
 Here is an example of how it should work:

 1) A developer wants to find in which commit a past regression was
 fixed. She start bisecting as usual with git bisect start.
 2) The current HEAD has the bugfix, so she marks it as fixed with git
 bisect mark fixed.
 3) She knows that HEAD~100 had the regression, so she marks it as
 unfixed with git bisect mark unfixed.
 4) Now that git knows what the two labels are, it starts bisecting as usual.

 For compatibility with already written scripts, git bisect good and
 git bisect bad will alias to git bisect mark good and git bisect
 mark bad respectively.

 Does this make sense? Did I overlook some details?

As Junio said adding a command mark doesn't by itself solve the
difficult problems related to this project.
(By the way I think it is misleading to state that this GSoC is easy.)

 There were already several proposals on this topic, among which those
 listed at 
 https://git.wiki.kernel.org/index.php/SmallProjectsIdeas#git_bisect_fix.2Funfixed.
 I'm interested in contacting the prospective mentor, Christian Couder,
 to go over these. What's the proper way to ask for an introduction?

As Michael said, you can just CC me or send me a private email.

But I think the most important thing right now is first to gather as
much information as you can from the previous discussions on this
topic on this mainling list.
Perhaps you should also gather information on how git bisect works.

It will help you understand what are the difficult problems.

One of the problems, for example, is that git bisect can work using a
good commit that is not an ancestor of the bad commit.
In this case it will checkout the merge bases between the good and the
bad commit. (And by the way this is related to the bug that should
also be fixed as part of this project.)

Then you are welcome to come back and ask questions, or suggest solutions.

 I tried asking on IRC, but had no success.

Sorry but I don't use IRC.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 00/11] Add interpret-trailers builtin

2014-03-04 Thread Christian Couder
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 5, thanks to Junio and Eric:

* the --infile file option has been removed 
* many small functions are back to just 'static' instead of 'static inline'
* alnum_len() has been adjust to have a size_t len parameter and a size_t
  return value again
* strcspn() is used in void parse_trailer()
* some test setup commands have been moved in some proper tests
* some commit messages have been improved
* a patch to setup env variables for commands has been removed
* all the memory leaks should have been fixed


Christian Couder (11):
  Add data structures and basic functions for commit trailers
  trailer: process trailers from stdin and arguments
  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 ++
 git.c|   1 +
 t/t7513-interpret-trailers.sh| 261 
 trailer.c| 661 +++
 trailer.h|   6 +
 9 files changed, 1089 insertions(+)
 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.8.5.2.204.gcfe299d.dirty

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 07/11] trailer: add interpret-trailers command

2014-03-04 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 .gitignore   |  1 +
 Makefile |  1 +
 builtin.h|  1 +
 builtin/interpret-trailers.c | 33 +
 git.c|  1 +
 trailer.h|  6 ++
 6 files changed, 43 insertions(+)
 create mode 100644 builtin/interpret-trailers.c
 create mode 100644 trailer.h

diff --git a/.gitignore b/.gitignore
index b5f9def..c870ada 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 ec90feb..a91465e 100644
--- a/Makefile
+++ b/Makefile
@@ -935,6 +935,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 d4afbfe..30f4c30 100644
--- a/builtin.h
+++ b/builtin.h
@@ -71,6 +71,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 000..0c8ca72
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,33 @@
+/*
+ * Builtin git interpret-trailers
+ *
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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 3799514..1420b58 100644
--- a/git.c
+++ b/git.c
@@ -383,6 +383,7 @@ static void handle_internal_command(int argc, const char 
**argv)
{ 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 },
diff --git a/trailer.h b/trailer.h
new file mode 100644
index 000..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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 03/11] trailer: read and process config information

2014-03-04 Thread Christian Couder
This patch implements reading the configuration
to get trailer information, and then processing
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 chrisc...@tuxfamily.org
---
 trailer.c | 134 ++
 1 file changed, 134 insertions(+)

diff --git a/trailer.c b/trailer.c
index a0124f2..5b8e28b 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);
@@ -244,3 +246,135 @@ 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 (!strcasecmp(after, value))
+   item-where = WHERE_AFTER;
+   else if (!strcasecmp(before, value))
+   item-where = WHERE_BEFORE;
+   else
+   return 1;
+   return 0;
+}
+
+static int set_if_exists(struct conf_info *item, const char *value)
+{
+   if (!strcasecmp(addIfDifferent, value))
+   item-if_exists = EXISTS_ADD_IF_DIFFERENT;
+   else if (!strcasecmp(addIfDifferentNeighbor, value))
+   item-if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
+   else if (!strcasecmp(add, value))
+   item-if_exists = EXISTS_ADD;
+   else if (!strcasecmp(overwrite, value))
+   item-if_exists = EXISTS_OVERWRITE;
+   else if (!strcasecmp(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 (!strcasecmp(doNothing, value))
+   item-if_missing = MISSING_DO_NOTHING;
+   else if (!strcasecmp(add, value))
+   item-if_missing = MISSING_ADD;
+   else
+   return 1;
+   return 0;
+}
+
+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
+TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
+
+static int set_name_and_type(const char *conf_key, const char *suffix,
+enum trailer_info_type type,
+char **pname, enum trailer_info_type *ptype)
+{
+   int ret = ends_with(conf_key, suffix);
+   if (ret) {
+   *pname = xstrndup(conf_key, strlen(conf_key) - strlen(suffix));
+   *ptype = type;
+   }
+   return ret;
+}
+
+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;
+}
+
+static int git_trailer_config(const char *conf_key, const char *value, void 
*cb)
+{
+   if (starts_with(conf_key, trailer.)) {
+   const char *orig_conf_key = conf_key;
+   struct trailer_item *item;
+   struct conf_info *conf;
+   char *name;
+   enum trailer_info_type type;
+
+   conf_key += 8;
+   if (!set_name_and_type(conf_key, .key, TRAILER_KEY, name, 
type) 
+   !set_name_and_type(conf_key, .command, TRAILER_COMMAND, 
name, type) 
+   !set_name_and_type(conf_key, .where, TRAILER_WHERE, 
name, type) 
+   !set_name_and_type(conf_key, .ifexists, 
TRAILER_IF_EXISTS, name, type) 
+   !set_name_and_type(conf_key, .ifmissing, 
TRAILER_IF_MISSING, name, type))
+   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), orig_conf_key);
+   conf-key = xstrdup(value);
+   break;
+   case TRAILER_COMMAND:
+   if (conf-command)
+   warning(_(more than one %s), orig_conf_key

[PATCH v6 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-03-04 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 Documentation/git-interpret-trailers.txt | 123 +++
 1 file changed, 123 insertions(+)
 create mode 100644 Documentation/git-interpret-trailers.txt

diff --git a/Documentation/git-interpret-trailers.txt 
b/Documentation/git-interpret-trailers.txt
new file mode 100644
index 000..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

[PATCH v6 04/11] trailer: process command line trailer arguments

2014-03-04 Thread Christian Couder
Parse the trailer command line arguments and put
the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 93 +++
 1 file changed, 93 insertions(+)

diff --git a/trailer.c b/trailer.c
index 5b8e28b..5d69c00 100644
--- a/trailer.c
+++ b/trailer.c
@@ -378,3 +378,96 @@ static int git_trailer_config(const char *conf_key, const 
char *value, void *cb)
}
return 0;
 }
+
+static void parse_trailer(struct strbuf *tok, struct strbuf *val, const char 
*trailer)
+{
+   size_t len = strcspn(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);
+   }
+}
+
+
+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;
+
+   parse_trailer(tok, val, string);
+
+   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 (!*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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 06/11] trailer: put all the processing together and print

2014-03-04 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 48 
 1 file changed, 48 insertions(+)

diff --git a/trailer.c b/trailer.c
index e0e066f..ab93c16 100644
--- a/trailer.c
+++ b/trailer.c
@@ -67,6 +67,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)
 {
@@ -547,3 +567,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);
+}
-- 
1.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 10/11] trailer: add tests for commands in config file

2014-03-04 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 47 +++
 1 file changed, 47 insertions(+)

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 3223b12..0badd0e 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -211,4 +211,51 @@ 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 
aut...@example.com\ 
+   cat complex_message_body expected 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: A U Thor aut...@example.com\n expected 
+   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 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: C O Mitter commit...@example.com\n expected 
+   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 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: A U Thor aut...@example.com\n expected 
+   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 
+   printf Fixes: $FIXED\nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: A U Thor aut...@example.com\n expected 
+   git interpret-trailers review: fix=HEAD complex_message actual 
+   test_cmp expected actual
+'
+
 test_done
-- 
1.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 01/11] Add data structures and basic functions for commit trailers

2014-03-04 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 Makefile  |  1 +
 trailer.c | 49 +
 2 files changed, 50 insertions(+)
 create mode 100644 trailer.c

diff --git a/Makefile b/Makefile
index b4af1e2..ec90feb 100644
--- a/Makefile
+++ b/Makefile
@@ -871,6 +871,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 000..db93a63
--- /dev/null
+++ b/trailer.c
@@ -0,0 +1,49 @@
+#include cache.h
+/*
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 09/11] trailer: execute command from 'trailer.name.command'

2014-03-04 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 64 +++
 1 file changed, 64 insertions(+)

diff --git a/trailer.c b/trailer.c
index ab93c16..67e7baf 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include cache.h
+#include run-command.h
 /*
  * Copyright (c) 2013, 2014 Christian Couder chrisc...@tuxfamily.org
  */
@@ -12,11 +13,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;
@@ -57,6 +61,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);
@@ -386,6 +397,7 @@ static int git_trailer_config(const char *conf_key, const 
char *value, void *cb)
if (conf-command)
warning(_(more than one %s), orig_conf_key);
conf-command = xstrdup(value);
+   conf-command_uses_arg = !!strstr(conf-command, 
TRAILER_ARG_STRING);
break;
case TRAILER_WHERE:
if (set_where(conf, value))
@@ -420,6 +432,44 @@ static void parse_trailer(struct strbuf *tok, struct 
strbuf *val, const char *tr
}
 }
 
+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)
 {
@@ -442,6 +492,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;
 
@@ -490,12 +544,22 @@ 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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v6 02/11] trailer: process trailers from stdin and arguments

2014-03-04 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 197 ++
 1 file changed, 197 insertions(+)

diff --git a/trailer.c b/trailer.c
index db93a63..a0124f2 100644
--- a/trailer.c
+++ b/trailer.c
@@ -47,3 +47,200 @@ 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 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) 
+   arg_tok-conf.where == where) {
+   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

[PATCH v6 05/11] trailer: parse trailers from stdin

2014-03-04 Thread Christian Couder
Read trailers from stdin, parse them and put the result into a doubly linked
list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 76 +++
 1 file changed, 76 insertions(+)

diff --git a/trailer.c b/trailer.c
index 5d69c00..e0e066f 100644
--- a/trailer.c
+++ b/trailer.c
@@ -50,6 +50,13 @@ 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;
+   for (s = str; *s  isspace(*s); s++);
+   return !*s;
+}
+
 static void free_trailer_item(struct trailer_item *item)
 {
free(item-conf.name);
@@ -471,3 +478,72 @@ 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 count, start, empty = 1;
+
+   /* Get the line count */
+   for (count = 0; lines[count]; count++)
+   ; /* Nothing to do */
+
+   /*
+* 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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] disable grafts during fetch/push/bundle

2014-03-06 Thread Christian Couder
On Thu, Mar 6, 2014 at 9:42 AM, Michael Haggerty mhag...@alum.mit.edu wrote:
 On 03/05/2014 08:18 PM, Junio C Hamano wrote:
 Jeff King p...@peff.net writes:

 On Wed, Mar 05, 2014 at 10:49:24AM -0800, Junio C Hamano wrote:

 ... the plan, at least in my mind, has always been exactly that: grafts
 were a nice little attempt but is broken---if you really wanted to
 muck with the history without rewriting (which is still discouraged,
 by the way), do not use graft, but use replace.

 I certainly had in the back of my mind that grafts were a lesser form of
 replace, and that eventually we could get rid of the former. Perhaps
 my question should have been: why haven't we deprecated grafts yet?.

 Given that we discourage grafts strongly and replace less so
 (but still discourage it), telling the users that biting the bullet
 and rewriting the history is _the_ permanent solution, I think it is
 understandable why nobody has bothered to.

 Replace objects are better than grafts in *almost* every dimension.  The
 exception is that it is dead simple to create grafts, whereas I always
 have to break open the man pages to remember how to create a replace
 object that does the same thing.

 So I think a helpful step towards deprecating grafts would be to offer a
 couple of convenience features to help people kick the grafts habit:

 * A tool that converts grafts (i.e., the grafts read from
 $GIT_DIR/info/grafts) into the equivalent replacements.

Yeah, I sent a kind of rough draft of a script to do that last year to
the mailing list, but I didn't take the time to convert it to a real
script or command.

 * A tool that creates a new replacement object that is the equivalent of
 a graft.  I.e., it should do, using replace references, the equivalent
 of the following command:

   echo SHA1 [PARENT1...] $GIT_DIR/info/grafts

Yeah, maybe it can be a git create-replace-ref command and it could
have a --convert-graft-file option to convert an existing graft file.

There have been discussions about such a command already some time ago.

 These features could be added to git replace or could be built into a
 new git grafts command.

I think Junio previously said that it was better if such features were
not part of git replace. But maybe I misunderstood his subtle
saying.

And I don't think git grafts is a good name. It looks too much like
we are encouraging people to use grafts.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v6 02/11] trailer: process trailers from stdin and arguments

2014-03-06 Thread Christian Couder
On Wed, Mar 5, 2014 at 11:52 PM, Junio C Hamano gits...@pobox.com wrote:

 This round is marked as the sixth, but I still see quite a many
 style issues, which I expect not to see from long timers without
 being told.  Somewhat disappointing...

Yeah, I don't know why, but these days I find it very hard to review
style issues in my own code without being distracted.
And by the way is there a good script to check them?

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 03/11] trailer: read and process config information

2014-03-06 Thread Christian Couder
This patch implements reading the configuration
to get trailer information, and then processing
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 chrisc...@tuxfamily.org
---
 trailer.c | 134 ++
 1 file changed, 134 insertions(+)

diff --git a/trailer.c b/trailer.c
index 52108c2..7a6d35d 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,135 @@ 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 (!strcasecmp(after, value))
+   item-where = WHERE_AFTER;
+   else if (!strcasecmp(before, value))
+   item-where = WHERE_BEFORE;
+   else
+   return 1;
+   return 0;
+}
+
+static int set_if_exists(struct conf_info *item, const char *value)
+{
+   if (!strcasecmp(addIfDifferent, value))
+   item-if_exists = EXISTS_ADD_IF_DIFFERENT;
+   else if (!strcasecmp(addIfDifferentNeighbor, value))
+   item-if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
+   else if (!strcasecmp(add, value))
+   item-if_exists = EXISTS_ADD;
+   else if (!strcasecmp(overwrite, value))
+   item-if_exists = EXISTS_OVERWRITE;
+   else if (!strcasecmp(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 (!strcasecmp(doNothing, value))
+   item-if_missing = MISSING_DO_NOTHING;
+   else if (!strcasecmp(add, value))
+   item-if_missing = MISSING_ADD;
+   else
+   return 1;
+   return 0;
+}
+
+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
+TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
+
+static int set_name_and_type(const char *conf_key, const char *suffix,
+enum trailer_info_type type,
+char **pname, enum trailer_info_type *ptype)
+{
+   int ret = ends_with(conf_key, suffix);
+   if (ret) {
+   *pname = xstrndup(conf_key, strlen(conf_key) - strlen(suffix));
+   *ptype = type;
+   }
+   return ret;
+}
+
+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;
+}
+
+static int git_trailer_config(const char *conf_key, const char *value, void 
*cb)
+{
+   if (starts_with(conf_key, trailer.)) {
+   const char *orig_conf_key = conf_key;
+   struct trailer_item *item;
+   struct conf_info *conf;
+   char *name;
+   enum trailer_info_type type;
+
+   conf_key += 8;
+   if (!set_name_and_type(conf_key, .key, TRAILER_KEY, name, 
type) 
+   !set_name_and_type(conf_key, .command, TRAILER_COMMAND, 
name, type) 
+   !set_name_and_type(conf_key, .where, TRAILER_WHERE, 
name, type) 
+   !set_name_and_type(conf_key, .ifexists, 
TRAILER_IF_EXISTS, name, type) 
+   !set_name_and_type(conf_key, .ifmissing, 
TRAILER_IF_MISSING, name, type))
+   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), orig_conf_key);
+   conf-key = xstrdup(value);
+   break;
+   case TRAILER_COMMAND:
+   if (conf-command)
+   warning(_(more than one %s), orig_conf_key

[PATCH v7 00/11] Add interpret-trailers builtin

2014-03-06 Thread Christian Couder
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 6, thanks to Junio and Ramsey:

* many style fixes
* clearer and nicer setup tests
* trailer.h is included at the beginning of trailer.c


Christian Couder (11):
  Add data structures and basic functions for commit trailers
  trailer: process trailers from stdin and arguments
  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 ++
 git.c|   1 +
 t/t7513-interpret-trailers.sh| 261 
 trailer.c| 667 +++
 trailer.h|   6 +
 9 files changed, 1095 insertions(+)
 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.8.5.2.204.gcfe299d.dirty

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 08/11] trailer: add tests for git interpret-trailers

2014-03-06 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 214 ++
 1 file changed, 214 insertions(+)
 create mode 100755 t/t7513-interpret-trailers.sh

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
new file mode 100755
index 000..1c5ed81
--- /dev/null
+++ b/t/t7513-interpret-trailers.sh
@@ -0,0 +1,214 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Christian Couder
+#
+
+test_description='git interpret-trailers'
+
+. ./test-lib.sh
+
+test_expect_success 'setup 1' '
+   cat basic_message -\EOF
+   subject
+
+   body
+   EOF
+'
+
+test_expect_success 'setup 2' '
+   cat complex_message_body -\EOF
+   my subject
+
+   my body which is long
+   and contains some special
+   chars like : = ? !
+
+   EOF
+'
+
+# 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 3' '
+   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' '
+   printf ack: Peff\nReviewed-by: \nAcked-by: Johan\n expected 
+   git interpret-trailers ack = Peff Reviewed-by Acked-by: Johan 
actual 
+   test_cmp expected actual
+'
+
+test_expect_success '--trim-empty without config' '
+   printf ack: Peff\nAcked-by: Johan\n expected 
+   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:  
+   printf Acked-by: Peff\n expected 
+   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=  
+   printf Acked-by= Peff\n expected 
+   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 # 
+   printf Bug #42\n expected 
+   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 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: \n 
expected 
+   git interpret-trailers complex_message actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with commit complex message and args' '
+   cat complex_message_body expected 
+   printf Fixes: \nAcked-by= \nAcked-by= Peff\nReviewed-by: 
\nSigned-off-by: \nBug #42\n expected 
+   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 
+   printf Acked-by= Peff\nBug #42\n expected 
+   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 
+   printf Bug #42\nFixes: \nAcked-by= \nAcked-by= Peff\nReviewed-by: 
\nSigned-off-by: \n expected 
+   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 
+   printf Bug #42\nFixes: \nAcked-by= \nAcked-by= Peff\nReviewed-by: 
Johan\nReviewed-by: \nSigned-off-by: \n expected 
+   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 
+   printf Bug #46\nBug #42\nAcked-by= Peff\nReviewed-by: Johan\n 
expected 
+   git interpret

[PATCH v7 05/11] trailer: parse trailers from stdin

2014-03-06 Thread Christian Couder
Read trailers from stdin, parse them and put the result into a doubly linked
list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 76 +++
 1 file changed, 76 insertions(+)

diff --git a/trailer.c b/trailer.c
index 43cbf10..910eddb 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);
@@ -476,3 +484,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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 10/11] trailer: add tests for commands in config file

2014-03-06 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 47 +++
 1 file changed, 47 insertions(+)

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 1c5ed81..07e1b60 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -211,4 +211,51 @@ 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 
aut...@example.com\ 
+   cat complex_message_body expected 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: A U Thor aut...@example.com\n expected 
+   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 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: C O Mitter commit...@example.com\n expected 
+   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 
+   printf Fixes: \nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: A U Thor aut...@example.com\n expected 
+   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 
+   printf Fixes: $FIXED\nAcked-by= \nReviewed-by: \nSigned-off-by: 
\nSigned-off-by: A U Thor aut...@example.com\n expected 
+   git interpret-trailers review: fix=HEAD complex_message actual 
+   test_cmp expected actual
+'
+
 test_done
-- 
1.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 06/11] trailer: put all the processing together and print

2014-03-06 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 49 +
 trailer.h |  6 ++
 2 files changed, 55 insertions(+)
 create mode 100644 trailer.h

diff --git a/trailer.c b/trailer.c
index 910eddb..cc87918 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include cache.h
+#include trailer.h
 /*
  * Copyright (c) 2013, 2014 Christian Couder chrisc...@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)
 {
@@ -552,3 +573,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 000..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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 09/11] trailer: execute command from 'trailer.name.command'

2014-03-06 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 63 +++
 1 file changed, 63 insertions(+)

diff --git a/trailer.c b/trailer.c
index cc87918..97a0fe7 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 chrisc...@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);
@@ -389,6 +400,7 @@ static int git_trailer_config(const char *conf_key, const 
char *value, void *cb)
if (conf-command)
warning(_(more than one %s), orig_conf_key);
conf-command = xstrdup(value);
+   conf-command_uses_arg = !!strstr(conf-command, 
TRAILER_ARG_STRING);
break;
case TRAILER_WHERE:
if (set_where(conf, value))
@@ -423,6 +435,44 @@ static void parse_trailer(struct strbuf *tok, struct 
strbuf *val, const char *tr
}
 }
 
+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)
 {
@@ -445,6 +495,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;
 
@@ -497,12 +551,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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 04/11] trailer: process command line trailer arguments

2014-03-06 Thread Christian Couder
Parse the trailer command line arguments and put
the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 97 +++
 1 file changed, 97 insertions(+)

diff --git a/trailer.c b/trailer.c
index 7a6d35d..43cbf10 100644
--- a/trailer.c
+++ b/trailer.c
@@ -379,3 +379,100 @@ static int git_trailer_config(const char *conf_key, const 
char *value, void *cb)
}
return 0;
 }
+
+static void parse_trailer(struct strbuf *tok, struct strbuf *val, const char 
*trailer)
+{
+   size_t len = strcspn(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);
+   }
+}
+
+
+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;
+
+   parse_trailer(tok, val, string);
+
+   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 (!*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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-03-06 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 Documentation/git-interpret-trailers.txt | 123 +++
 1 file changed, 123 insertions(+)
 create mode 100644 Documentation/git-interpret-trailers.txt

diff --git a/Documentation/git-interpret-trailers.txt 
b/Documentation/git-interpret-trailers.txt
new file mode 100644
index 000..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

[PATCH v7 02/11] trailer: process trailers from stdin and arguments

2014-03-06 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 198 ++
 1 file changed, 198 insertions(+)

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

[PATCH v7 01/11] Add data structures and basic functions for commit trailers

2014-03-06 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 Makefile  |  1 +
 trailer.c | 49 +
 2 files changed, 50 insertions(+)
 create mode 100644 trailer.c

diff --git a/Makefile b/Makefile
index b4af1e2..ec90feb 100644
--- a/Makefile
+++ b/Makefile
@@ -871,6 +871,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 000..db93a63
--- /dev/null
+++ b/trailer.c
@@ -0,0 +1,49 @@
+#include cache.h
+/*
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v7 07/11] trailer: add interpret-trailers command

2014-03-06 Thread Christian Couder
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 chrisc...@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

diff --git a/.gitignore b/.gitignore
index b5f9def..c870ada 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 ec90feb..a91465e 100644
--- a/Makefile
+++ b/Makefile
@@ -935,6 +935,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 d4afbfe..30f4c30 100644
--- a/builtin.h
+++ b/builtin.h
@@ -71,6 +71,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 000..0c8ca72
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,33 @@
+/*
+ * Builtin git interpret-trailers
+ *
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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 3799514..1420b58 100644
--- a/git.c
+++ b/git.c
@@ -383,6 +383,7 @@ static void handle_internal_command(int argc, const char 
**argv)
{ 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.8.5.2.204.gcfe299d.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH] disable grafts during fetch/push/bundle

2014-03-06 Thread Christian Couder
On Fri, Mar 7, 2014 at 12:39 AM, Junio C Hamano gits...@pobox.com wrote:
 Michael Haggerty mhag...@alum.mit.edu writes:

 I didn't mean to insult all Windows users in general.  I was only
 referring to the fact that since the default Windows command line is not
 a POSIX shell, even an experienced Windows user might have trouble
 figuring out how to execute a shell loop.  Putting this functionality in
 a git command or script, by contrast, would make it work universally, no
 fuss, no muss.

 ;-)

 Be it graft or replace, I do not think we want to invite people to
 use these mechansims too lightly to locally rewrite their history
 willy-nilly without fixing their mistakes at the object layer with
 commit --amend, rebase, bfg, etc. in the longer term.  So in
 that sense, adding a command to make it easy is not something I am
 enthusiastic about.

 On the other hand, if the user does need to use graft or replace
 (perhaps to prepare for casting the fixed history in stone with
 filter-branch), it would be good to help them avoid making mistakes
 while doing so and tool support may be a way to do so.

 So, ... I am of two minds.


Maybe if we add a new command (or maybe a script) with a name long and
cryptic-looking enough like git create-replacement-object it will
scare casual users from touching it, while power users will be happy
to benefit from it.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v6 02/11] trailer: process trailers from stdin and arguments

2014-03-07 Thread Christian Couder
On Fri, Mar 7, 2014 at 9:08 AM, Dan Carpenter dan.carpen...@oracle.com wrote:
 On Fri, Mar 07, 2014 at 07:19:15AM +0100, Christian Couder wrote:

 Yeah, I don't know why, but these days I find it very hard to review
 style issues in my own code without being distracted.
 And by the way is there a good script to check them?

 Many of these would have been caught with kernel.org's checkpatch.pl
 script.

 http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/scripts/checkpatch.pl

Yeah, I already thought about using it in the past, but I was not sure
about its status regarding the git source code because it's use is not
suggested in CodingGuidelines, and the discussions about adding it
there went nowhere:

http://thread.gmane.org/gmane.comp.version-control.git/223698/focus=224653

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: What's cooking in git.git (Mar 2014, #06; Tue, 25)

2014-03-26 Thread Christian Couder
HI,

On Tue, Mar 25, 2014 at 9:18 PM, Junio C Hamano gits...@pobox.com wrote:

 * cc/interpret-trailers (2014-03-07) 11 commits
  - Documentation: add documentation for 'git interpret-trailers'
  - trailer: add tests for commands in config file
  - trailer: execute command from 'trailer.name.command'
  - trailer: add tests for git interpret-trailers
  - trailer: add interpret-trailers command
  - trailer: put all the processing together and print
  - trailer: parse trailers from stdin
  - trailer: process command line trailer arguments
  - trailer: read and process config information
  - trailer: process trailers from stdin and arguments
  - trailers: add data structures and basic functions

  A new filter to programatically edit the tail end of the commit log
  messages.

  Will merge to 'next'.

I have a new version of this series, but I am not very happy with it.
Especially, I am not very happy about not using strcasecmp() and
instead lowercasing some parameters read from the config file.
That's because it adds some lines and one more patch as I feel that I
have to refactor lowercasing functions.

I will send this new series soon anyway and we can choose if we want
these lowercasing changes or not.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 config.c  |  6 --
 daemon.c  |  8 
 git-compat-util.h |  4 
 wrapper.c | 14 ++
 4 files changed, 18 insertions(+), 14 deletions(-)

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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
Read trailers from stdin, parse them and put the result into a doubly linked
list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 76 +++
 1 file changed, 76 insertions(+)

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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 153 ++
 1 file changed, 153 insertions(+)

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

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

2014-03-26 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 49 +
 trailer.h |  6 ++
 2 files changed, 55 insertions(+)
 create mode 100644 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 chrisc...@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 000..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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 63 +++
 1 file changed, 63 insertions(+)

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 chrisc...@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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 198 ++
 1 file changed, 198 insertions(+)

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

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

2014-03-26 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 Documentation/git-interpret-trailers.txt | 123 +++
 1 file changed, 123 insertions(+)
 create mode 100644 Documentation/git-interpret-trailers.txt

diff --git a/Documentation/git-interpret-trailers.txt 
b/Documentation/git-interpret-trailers.txt
new file mode 100644
index 000..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

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

2014-03-26 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 71 +++
 1 file changed, 71 insertions(+)

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 
aut...@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 aut...@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 commit...@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 aut...@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 aut...@example.com
+   EOF
+   git interpret-trailers review: fix=HEAD complex_message actual 
+   test_cmp expected actual
+'
+
 test_done
-- 
1.9.0.164.g3aa33cd.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
Parse the trailer command line arguments and put
the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 103 ++
 1 file changed, 103 insertions(+)

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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 Makefile  |  1 +
 trailer.c | 49 +
 2 files changed, 50 insertions(+)
 create mode 100644 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 000..db93a63
--- /dev/null
+++ b/trailer.c
@@ -0,0 +1,49 @@
+#include cache.h
+/*
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
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 chrisc...@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

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 000..0c8ca72
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,33 @@
+/*
+ * Builtin git interpret-trailers
+ *
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-26 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 336 ++
 1 file changed, 336 insertions(+)
 create mode 100755 t/t7513-interpret-trailers.sh

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
new file mode 100755
index 000..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

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

2014-03-27 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder chrisc...@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.

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-27 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder chrisc...@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.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-27 Thread Christian Couder
From: Junio C Hamano gits...@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

 Christian Couder chrisc...@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.

 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.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-03-28 Thread Christian Couder
From: Jeff King p...@peff.net
Subject: Re: [PATCH v8 03/12] Move lower case functions into wrapper.c
Date: Thu, 27 Mar 2014 18:34:06 -0400

 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.

 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.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 03/11] trailer: read and process config information

2014-04-01 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 146 ++
 1 file changed, 146 insertions(+)

diff --git a/trailer.c b/trailer.c
index 52108c2..f7714d5 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,147 @@ 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;
+   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:
+   if (set_where(conf, value

[PATCH v9 08/11] trailer: add tests for git interpret-trailers

2014-04-01 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 336 ++
 1 file changed, 336 insertions(+)
 create mode 100755 t/t7513-interpret-trailers.sh

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
new file mode 100755
index 000..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

[PATCH v9 02/11] trailer: process trailers from stdin and arguments

2014-04-01 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 198 ++
 1 file changed, 198 insertions(+)

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

[PATCH v9 10/11] trailer: add tests for commands in config file

2014-04-01 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 71 +++
 1 file changed, 71 insertions(+)

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 
aut...@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 aut...@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 commit...@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 aut...@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 aut...@example.com
+   EOF
+   git interpret-trailers review: fix=HEAD complex_message actual 
+   test_cmp expected actual
+'
+
 test_done
-- 
1.9.0.164.g3aa33cd.dirty


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-04-01 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 Documentation/git-interpret-trailers.txt | 123 +++
 1 file changed, 123 insertions(+)
 create mode 100644 Documentation/git-interpret-trailers.txt

diff --git a/Documentation/git-interpret-trailers.txt 
b/Documentation/git-interpret-trailers.txt
new file mode 100644
index 000..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

[PATCH v9 07/11] trailer: add interpret-trailers command

2014-04-01 Thread Christian Couder
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 chrisc...@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

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 000..0c8ca72
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,33 @@
+/*
+ * Builtin git interpret-trailers
+ *
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 00/11] Add interpret-trailers builtin

2014-04-01 Thread Christian Couder
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 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.

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 8, thanks to Junio:

* improve the rationale section above
* improve commit message in patch 1/11
* remove patch (3/12) that moved some lowercase
  functions to wrapper.c
* use strcmp() instead of strcasecmp() to compare
  some values from the config file matching a fixed set

This means code changes only in patch 3/11 and commit
message changes only in patch 1/11.


Christian Couder (11):
  trailer: add data structures and basic functions
  trailer: process trailers from stdin and arguments
  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 ++
 git.c|   1 +
 t/t7513-interpret-trailers.sh| 407 ++
 trailer.c| 684 +++
 trailer.h|   6 +
 9 files changed, 1258 insertions(+)
 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

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 06/11] trailer: put all the processing together and print

2014-04-01 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 trailer.c | 49 +
 trailer.h |  6 ++
 2 files changed, 55 insertions(+)
 create mode 100644 trailer.h

diff --git a/trailer.c b/trailer.c
index 953ae51..5d2b3ad 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include cache.h
+#include trailer.h
 /*
  * Copyright (c) 2013, 2014 Christian Couder chrisc...@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)
 {
@@ -570,3 +591,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 000..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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 01/11] trailer: add data structures and basic functions

2014-04-01 Thread Christian Couder
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 chrisc...@tuxfamily.org
---
 Makefile  |  1 +
 trailer.c | 49 +
 2 files changed, 50 insertions(+)
 create mode 100644 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 000..db93a63
--- /dev/null
+++ b/trailer.c
@@ -0,0 +1,49 @@
+#include cache.h
+/*
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 04/11] trailer: process command line trailer arguments

2014-04-01 Thread Christian Couder
Parse the trailer command line arguments and put
the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 103 ++
 1 file changed, 103 insertions(+)

diff --git a/trailer.c b/trailer.c
index f7714d5..743ff23 100644
--- a/trailer.c
+++ b/trailer.c
@@ -391,3 +391,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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v9 05/11] trailer: parse trailers from stdin

2014-04-01 Thread Christian Couder
Read trailers from stdin, parse them and put the result into a doubly linked
list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 trailer.c | 76 +++
 1 file changed, 76 insertions(+)

diff --git a/trailer.c b/trailer.c
index 743ff23..953ae51 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);
@@ -494,3 +502,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


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-04-02 Thread Christian Couder
Hi,

On Wed, Apr 2, 2014 at 2:39 AM, Jonathan Nieder jrnie...@gmail.com wrote:
 (culling cc list)
 Hi,

 Christian Couder wrote:

 [Subject: Documentation: add documentation for 'git interpret-trailers']

 Signed-off-by: Christian Couder chrisc...@tuxfamily.org

 This should be squashed into the patch that introduces the
 interpret-trailers command, IMHO (or if it should be reviewed
 separately, it can be an earlier patch).  That way, someone looking at
 when the command was introduced and wanting to understand what it was
 originally meant to do has the information close by.

Well, the series is not very long, so this patch is quite close to the
beginning anyway.

 Thanks for picking up the 'git commit --fixes' topic and your steady
 work improving the series.

 [...]
 --- /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.

 Do you have an example?  Does it work like this?

 $ git interpret-trailers 'signoff=Jonathan Nieder 
 jrnie...@gmail.com' EOF
  foo bar baz qux
  EOF
 foo bar baz qux

 Signed-off-by: Jonathan Nieder jrnie...@gmail.com
 $

Yeah, that's the idea. But you need to run something like:

$ git config trailer.signoff.key Signed-off-by:

to configure it properly first.

By the way trying your example, I found that it is not currently
adding an empty line before the s-o-b.
I will have a look.

 A short EXAMPLES section could help.

Yeah, it is planned, but not yet implemented, as written in patch 0/11:

The following features are planned but not yet implemented:
- add more tests related to commands
- add examples in documentation
- integration with git commit

 If I am understanding it correctly, would a name like 'git add-trailers'
 work?

It could work but it can modify, not just add trailers.

 How do I read back the trailers later?

Why do you want to read them back?
Right now it should be used in hooks to modify commit messages.

 [...]
 +By default, a 'token=value' or 'token:value' argument will be added
 +only if

 Why support both '=' and ':'?  Using just one would make it easier to
 grep through scripts to see who is adding signoffs.

That was already discussed previously.
The reason is that people are used to token=value for command line
arguments, but trailers appears in the result as token: value, so it
is better for the user if we support both.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v9 08/11] trailer: add tests for git interpret-trailers

2014-04-02 Thread Christian Couder
On Wed, Apr 2, 2014 at 12:54 AM, Junio C Hamano gits...@pobox.com wrote:

 +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
 +'

 Let's avoid these overlong and unreadable lines by doing something
 like this (just one hunk shown for illustration):

 ack: Peff
 Acked-by: Johan
 EOF
 -   git interpret-trailers --trim-empty ack = Peff Reviewed-by 
 Acked-by: Johan sob: actual 
 +   git interpret-trailers --trim-empty ack = Peff \
 +   Reviewed-by Acked-by: Johan sob: actual 
 test_cmp expected actual
  '

Ok.

 I've queued the series on 'pu' with the 'chop-overlong-lines' and
 another minor fix squashed in; hopefully we can merge to 'next'
 soonish.

Great!

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-04-07 Thread Christian Couder
On Sat, Apr 5, 2014 at 12:42 AM, Junio C Hamano gits...@pobox.com wrote:
 Junio C Hamano gits...@pobox.com writes:

 Christian Couder christian.cou...@gmail.com writes:
 The following features are planned but not yet implemented:
 - add more tests related to commands
 - add examples in documentation
 - integration with git commit

 I was planning to merge the series to 'next', but perhaps we should
 wait at least for the first two items (I do not think the third one
 is necessary to block the series).

I will send soon a new version of the series with more tests and fixes.
It will also contains a patch that adds an empty line before the
trailers in the output if there is not already one.
After that I plan to work on adding examples to the documentation.

 Why support both '=' and ':'?  Using just one would make it easier to
 grep through scripts to see who is adding signoffs.

 That was already discussed previously.

 I do recall it was discussed previously, but given that a new reader
 posed the same question, I am not sure if the end result in this
 patch under discussion sufficiently answers the question in a
 satisfactory way.

 The reason is that people are used to token=value for command line
 arguments, but trailers appears in the result as token: value, so it
 is better for the user if we support both.

 While I do understand the part before , so on the second line, I
 do not see why that leads to the conclusion at all.

 Yes, because it is a well-established convention to separate option
 name with its parameter with '=', accepting --option=parameter
 makes sense.  That may result in a string Option: parameter added
 to the output from the command.  I am not sure why that output has
 anything to do with how the command line should be specified.

First accepting both ':' and '=' means one can see the git
interpret-trailers as acting on trailers only. Not just on trailers
from the intput message and option parameters from the command line.
But from trailers both from the input message and being passed as
arguments.
In my opinion it is good if it can be seen this way, as it may
simplifies the user's mental model of how the command works.

And second there is also a practical advantage, as the user can
copy-paste trailers directly from other messages into the command line
to pass them as arguments to git interpret-trailers without the need
to replace the ':' with '='. Even if this command is not often used
directly by users, it might simplify scripts using it.

Third there is a technical advantage which is that the code that
parses arguments from the command line can be the same as the code
that parses trailers from the input message.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-04-07 Thread Christian Couder
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 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.

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 31 tests (4 more than in version 9)
- there is some documentation

The following features are planned but not yet implemented:
- add examples in documentation

Possible improvements:
- integration with git commit
- support GIT_COMMIT_PROTO env variable in commands

3) Changes since version 9, thanks to Jonathan and Junio:

* added 1 test with empty trailers in patch 10/12
* fixed bugs when there was no 'key' in the config in patch
  4/12 and added 2 related tests in patch 10/12
* fixed bug when command failed in patch 9/12 and added 1
  related test in patch 10/12
* added patch 12/12 which add one blank line before the
  trailers if there is not one already

This means code changes only in patches 4/12, 9/12, 10/12
and 12/12.


Christian Couder (12):
  trailer: add data structures and basic functions
  trailer: process trailers from stdin and arguments
  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'
  trailer: add blank line before the trailers if needed

 .gitignore   |   1 +
 Documentation/git-interpret-trailers.txt | 123 ++
 Makefile |   2 +
 builtin.h|   1 +
 builtin/interpret-trailers.c |  33 ++
 git.c|   1 +
 t/t7513-interpret-trailers.sh| 477 +
 trailer.c| 709 +++
 trailer.h|   6 +
 9 files changed, 1353 insertions(+)
 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.163.g8ca203c

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v10 06/12] trailer: put all the processing together and print

2014-04-07 Thread Christian Couder
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 chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 49 +
 trailer.h |  6 ++
 2 files changed, 55 insertions(+)
 create mode 100644 trailer.h

diff --git a/trailer.c b/trailer.c
index 6d2da32..16465e5 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include cache.h
+#include trailer.h
 /*
  * Copyright (c) 2013, 2014 Christian Couder chrisc...@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)
 {
@@ -584,3 +605,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 000..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.163.g8ca203c


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v10 09/12] trailer: execute command from 'trailer.name.command'

2014-04-07 Thread Christian Couder
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 chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 64 +++
 1 file changed, 64 insertions(+)

diff --git a/trailer.c b/trailer.c
index 16465e5..09db2c2 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 chrisc...@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);
@@ -402,6 +413,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:
if (set_where(conf, value))
@@ -438,6 +450,45 @@ 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);
+   result = xstrdup();
+   } else
+   result = strbuf_detach(buf, NULL);
+
+   strbuf_release(cmd);
+   return result;
+}
 
 static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
 {
@@ -468,6 +519,10 @@ static struct trailer_item *new_trailer_item(struct 
trailer_item *conf_item,
duplicate_conf(new-conf, conf_item-conf);
new-token = xstrdup(token_from_item(conf_item));
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;
 
@@ -529,12 +584,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.163.g8ca203c


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v10 12/12] trailer: add blank line before the trailers if needed

2014-04-07 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
---
 t/t7513-interpret-trailers.sh | 12 +++-
 trailer.c | 26 ++
 2 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 262f7bf..44a7131 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -34,6 +34,7 @@ test_expect_success 'setup' '
 
 test_expect_success 'without config' '
sed -e s/ Z\$/ / expected -\EOF 
+
ack: Peff
Reviewed-by: Z
Acked-by: Johan
@@ -44,6 +45,7 @@ test_expect_success 'without config' '
 
 test_expect_success '--trim-empty without config' '
cat expected -\EOF 
+
ack: Peff
Acked-by: Johan
EOF
@@ -55,6 +57,7 @@ test_expect_success '--trim-empty without config' '
 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 
@@ -68,6 +71,7 @@ test_expect_success 'with config setup' '
 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 
@@ -81,6 +85,7 @@ test_expect_success 'with config setup and = sign' '
 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 
@@ -88,8 +93,10 @@ test_expect_success 'with config setup and # sign' '
 '
 
 test_expect_success 'with commit basic message' '
+   cat basic_message expected 
+   echo expected 
git interpret-trailers basic_message actual 
-   test_cmp basic_message actual
+   test_cmp expected actual
 '
 
 test_expect_success 'with commit complex message' '
@@ -436,6 +443,7 @@ test_expect_success 'with failing command using $ARG' '
 
 test_expect_success 'with empty tokens' '
cat expected -EOF 
+
Signed-off-by: A U Thor aut...@example.com
EOF
git interpret-trailers : :test actual -EOF 
@@ -446,6 +454,7 @@ test_expect_success 'with empty tokens' '
 test_expect_success 'with command but no key' '
git config --unset trailer.sign.key 
cat expected -EOF 
+
sign: A U Thor aut...@example.com
EOF
git interpret-trailers actual -EOF 
@@ -456,6 +465,7 @@ test_expect_success 'with command but no key' '
 test_expect_success 'with no command and no key' '
git config --unset trailer.review.key 
cat expected -EOF 
+
review: Junio
sign: A U Thor aut...@example.com
EOF
diff --git a/trailer.c b/trailer.c
index 09db2c2..639f657 100644
--- a/trailer.c
+++ b/trailer.c
@@ -618,12 +618,14 @@ static struct strbuf **read_stdin(void)
 }
 
 /*
- * Return the the (0 based) index of the first trailer line
+ * Return the (0 based) index of the first trailer line
  * or the line count if there are no trailers.
+ * The has_blank_line parameter tells if there is a blank
+ * line before the trailers.
  */
-static int find_trailer_start(struct strbuf **lines)
+static int find_trailer_start(struct strbuf **lines, int *has_blank_line)
 {
-   int start, empty = 1, count = 0;
+   int start, only_spaces = 1, count = 0;
 
/* Get the line count */
while (lines[count])
@@ -635,32 +637,40 @@ static int find_trailer_start(struct strbuf **lines)
 */
for (start = count - 1; start = 0; start--) {
if (contains_only_spaces(lines[start]-buf)) {
-   if (empty)
+   if (only_spaces)
continue;
+   *has_blank_line = 1;
return start + 1;
}
if (strchr(lines[start]-buf, ':')) {
-   if (empty)
-   empty = 0;
+   if (only_spaces)
+   only_spaces = 0;
continue;
}
+   *has_blank_line = start == count - 1 ?
+ 0 : contains_only_spaces(lines[start + 1]-buf);
return count;
}
 
-   return empty ? count : start + 1;
+   *has_blank_line = only_spaces ? count  0 : 0;
+   return only_spaces ? 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 has_blank_line;
+   int start = find_trailer_start(lines, has_blank_line);
int i

[PATCH v10 05/12] trailer: parse trailers from stdin

2014-04-07 Thread Christian Couder
Read trailers from stdin, parse them and put the result into a doubly linked
list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 76 +++
 1 file changed, 76 insertions(+)

diff --git a/trailer.c b/trailer.c
index 89ebff1..6d2da32 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);
@@ -508,3 +516,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.163.g8ca203c


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v10 08/12] trailer: add tests for git interpret-trailers

2014-04-07 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 t/t7513-interpret-trailers.sh | 351 ++
 1 file changed, 351 insertions(+)
 create mode 100755 t/t7513-interpret-trailers.sh

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
new file mode 100755
index 000..0e5d57f
--- /dev/null
+++ b/t/t7513-interpret-trailers.sh
@@ -0,0 +1,351 @@
+#!/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

[PATCH v10 04/12] trailer: process command line trailer arguments

2014-04-07 Thread Christian Couder
Parse the trailer command line arguments and put
the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 117 ++
 1 file changed, 117 insertions(+)

diff --git a/trailer.c b/trailer.c
index c7c0f54..89ebff1 100644
--- a/trailer.c
+++ b/trailer.c
@@ -391,3 +391,120 @@ 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 const char *token_from_item(struct trailer_item *item)
+{
+   if (item-conf.key)
+   return item-conf.key;
+
+   return item-conf.name;
+}
+
+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(token_from_item(conf_item));
+   free(tok);
+   } else
+   new-token = tok;
+
+   return new;
+}
+
+static int token_matches_item(const char *tok, struct trailer_item *item, int 
alnum_len)
+{
+   if (!strncasecmp(tok, item-conf.name, alnum_len))
+   return 1;
+   return item-conf.key ? !strncasecmp(tok, item-conf.key, alnum_len) : 
0;
+}
+
+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 (token_matches_item(tok.buf, item, 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.163.g8ca203c


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v10 10/12] trailer: add tests for commands in config file

2014-04-07 Thread Christian Couder
And add a few other tests for some special cases.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 t/t7513-interpret-trailers.sh | 116 ++
 1 file changed, 116 insertions(+)

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 0e5d57f..262f7bf 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -348,4 +348,120 @@ 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 
aut...@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 aut...@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 commit...@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 aut...@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 aut...@example.com
+   EOF
+   git interpret-trailers review: fix=HEAD complex_message actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with failing command using $ARG' '
+   git config trailer.fix.ifExists overwrite 
+   git config trailer.fix.command false \$ARG 
+   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 aut...@example.com
+   EOF
+   git interpret-trailers review: fix=HEAD complex_message actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with empty tokens' '
+   cat expected -EOF 
+   Signed-off-by: A U Thor aut...@example.com
+   EOF
+   git interpret-trailers : :test actual -EOF 
+   EOF
+   test_cmp expected actual
+'
+
+test_expect_success 'with command but no key' '
+   git config --unset trailer.sign.key 
+   cat expected -EOF 
+   sign: A U Thor aut...@example.com
+   EOF
+   git interpret-trailers actual -EOF 
+   EOF
+   test_cmp expected actual
+'
+
+test_expect_success 'with no command and no key' '
+   git config --unset trailer.review.key 
+   cat expected -EOF 
+   review: Junio
+   sign: A U Thor aut...@example.com
+   EOF
+   git interpret-trailers review:Junio actual -EOF 
+   EOF
+   test_cmp expected actual
+'
+
 test_done
-- 
1.9.0.163.g8ca203c


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v10 01/12] trailer: add data structures and basic functions

2014-04-07 Thread Christian Couder
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 chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 Makefile  |  1 +
 trailer.c | 49 +
 2 files changed, 50 insertions(+)
 create mode 100644 trailer.c

diff --git a/Makefile b/Makefile
index c5316a3..179be0a 100644
--- a/Makefile
+++ b/Makefile
@@ -879,6 +879,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 000..db93a63
--- /dev/null
+++ b/trailer.c
@@ -0,0 +1,49 @@
+#include cache.h
+/*
+ * Copyright (c) 2013, 2014 Christian Couder chrisc...@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.163.g8ca203c


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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

2014-04-07 Thread Christian Couder
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 chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 198 ++
 1 file changed, 198 insertions(+)

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

Re: [PATCH v9 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-04-07 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 Christian Couder christian.cou...@gmail.com writes:
 
 First accepting both ':' and '=' means one can see the git
 interpret-trailers as acting on trailers only. Not just on trailers
 from the intput message and option parameters from the command line.
 
 Sorry, you lost me.  What does acting on trailers only really
 mean?

It means that the command can seen as processing only trailers, (from
stdin and from its arguments), sorry if I used the wrong verb.

 Do you mean the command should/can be run without any command
 line options, pick up the existing Signed-off-by: and friends in
 its input and emit its output, somehow taking these existing ones as
 its instruction regarding how to transform the input to its output?

 And second there is also a practical advantage, as the user can
 copy-paste trailers directly from other messages into the command line
 to pass them as arguments to git interpret-trailers without the need
 to replace the ':' with '='. Even if this command is not often used
 directly by users, it might simplify scripts using it.

 Third there is a technical advantage which is that the code that
 parses arguments from the command line can be the same as the code
 that parses trailers from the input message.
 
 I do not see these two as valid arguments to make the command line
 more complex to the end users

I don't think that it makes the command more complex to the end users.

 ---who now need to know that only this
 command treats its command line in a funny way, accepting a colon in
 place of an equal sign.

It accepts both. So if they think that it is like a regular command,
which uses '=' for (key, value) pairs, it will work, and if they think
it works on trailers, which use ':' for (key, value) pairs, it will
also work.

 A different way to sell a colon, e.g.
 
 Consider the instruction sed takes on its command line.
 (e.g. sed 's/frotz/nitfol/' xyzzy).  In the most general
 form, you would always give it as the value of an '-e' option
 (e.g. sed -e 's/frotz/nitfol' xyzzy), but you are allowed to
 be loose in limited occassions.  Key:value is like that, and
 in the most general form, it actually needs to be spelled as
 -e 'Key:value'.
 
 is possible, but I do not think it is a particularly good analogy,
 because what you have as the alternative is Key=value, and not
 -e 'Key:value', or --Key=value (the last would probably be the 
 most natural way to express this).

The analogy that I would use is rather that Perl lets people use
's:foo:bar:' as well as 's=foo=bar=' instead of 's/foo/bar/' if they
prefer.

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [PATCH v10 11/12] Documentation: add documentation for 'git interpret-trailers'

2014-04-08 Thread Christian Couder
On Tue, Apr 8, 2014 at 9:30 AM, Michael Haggerty mhag...@alum.mit.edu wrote:

 +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.

 s/emited/emitted/

Ok.

 +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.

 How are existing trailers recognized in the input commit message?  Do
 trailers have to be configured to be recognized?  Or are all lines
 matching a specific pattern considered trailers?  If so, it might be
 helpful to include a regexp here that describes the trailer syntax.

The trailers are recognized in the input commit message using the
following rules:
 - only lines that contains a ':' are considered trailers,
 - the trailer lines must all be next to each other,
 - after them it's only possible to have some lines that contain only spaces,
 - before them there must be at least one line with only spaces

 What about blank lines?  I see that you try to add a blank line before
 new trailers.  But what about on input?

One line with only spaces has to be before the trailers. Some can be
after the trailers.

 Do the trailer lines have to be
 separated from the free-form comment by a blank line to be recognized?

Yes.

 What if there are blank lines between trailer lines, or after them?

After them is ok. Between is not ok (only the trailers after the blank
lines will be recognized).

 Is it allowed to have non-trailer lines between or after trailer lines?

No except lines with spaces after the trailers lines.

 +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.

 Does this apply to existing trailers, new trailers, or both?

Both.

 If it applies to existing trailers, then it seems a bit dangerous, in the
 sense that the command might end up changing trailers that are unrelated
 to the one that the command is trying to add.

The command is not just for adding trailers.
But there could be an option to just trim trailers that are added.

 +CONFIGURATION VARIABLES
 +---
 +
 +trailer.token.key::
 + This 'key' will be used instead of 'token' in the
 + trailer. After some alphanumeric characters, it can contain

 Trailer keys can also contain '-', right?

Yes.
I should have written after the last alphanumeric character.
I will fix that.

 + 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.

 Above it looks like the default separator is not ':' but rather ': '
 (with a space).  Is the space always added regardless of the value of
 this configuration variable, or should the configuration value include
 the trailing space if it is desired?  Is there any way to get a trailer
 that doesn't include a space, like

 foo=bar

 ?  (Changing this to foo= bar would look pretty ugly.)

I will have a look, but I think that:

- a space is always added after ':' or '=',
- a space is never added after '#',
- it doesn't matter if there is a space or not in the configured key.

 If a commit message containing trailer lines with separators other than
 ':' is input to the program, will it recognize them as trailer lines?

No, '=' and '#' are not supported in the input message, only in the output.

 Do such trailer lines have to have the same separator as the one listed
 in this configuration setting to be recognized?

No they need to have ':' as a separator.

The reason why only ':' is supported is because it is the cannonical
trailer separator and it could create problems with many input
messages if other separators where supported.

Maybe we could detect a special line like the following:

# TRAILERS START

in the input message and consider everyhting after 

Re: [PATCH v10 12/12] trailer: add blank line before the trailers if needed

2014-04-08 Thread Christian Couder
On Mon, Apr 7, 2014 at 11:38 PM, Junio C Hamano gits...@pobox.com wrote:

 Hmph, this is more fixing a mistake made earlier in the series at
 the end than adding a new feature or something.  Can you start from
 a version that does not have the mistake from the beginning?

Ok, I will squash this patch in other previous patches.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: On interpret-trailers standalone tool

2014-04-12 Thread Christian Couder
From: Junio C Hamano gits...@pobox.com

 So far I've mostly been ignoring how the command line would look
 like,

I don't really feel this way ;-)

 because the intermediate goal to my mind was to have it as a
 hook that are added by people better versed with Git than an average
 end-user, and if the command line interface had to change then they
 are capable of updating it, so it is more acceptable than the usual
 end-user tools to break compatibility between an early prototype and
 later versions, and because the final goal would be to libify the
 internal logic and integrate it into places we would invoke hooks,
 making the standalone command irrelevant.
 
 However, I started to care ;-)  For example, wouldn't it be nice if
 you can do
 
 $ git format-patch -5 --cover-letter -o +my-series/ my-topic
 $ git interpret-trailers some args ./+my-series/0*.patch
 
 to fix-up the trailers portion of the proposed log message in the
 formatted patches?  There may be other possible uses that having a
 standalone tool would be helpful, even after we removed the need for
 such a tool from commit, rebase, etc. by integrating the internal
 logic to the implementation of these commands.
 
 However, I am wondering if the current everything on the command
 line is instruction to the command is too limiting to allow the use
 of the tool both as a filter and as a tool that can work on one or
 more files named on the command line.  If we start from there, the
 only way to later add these arguments are names of the files to be
 operated on is to add --file file1 --file file2... options,
 which feels quite backwards as a UNIX tool.

Yeah, except that we could add for example a '-o' option that would
take a directory as argument and that would mean that the command
should operate on all the files in this directory. It would be like
the -o option of the format-patch command.

 It would be easier to explain and understand if the command line
 option set is modeled after things like cat or sed, where
 non-option arguments are filenames, instructions are given in the
 form of --option arg (e.g. -e 's/foo/bar/' given to sed), and
 having no non-option arguments on the command line signals that the
 tool is working as a filter.

Yeah, that's an interesting idea. I am not against making yet another
number of changes to git interpret-trailers to make something like
the above possible. But I think there are a few things that should be
discussed first.

First, if you think that the command might often be used along with
format-patch, then it might be interesting for the user to have the
command behave like format-patch instead of like cat or sed. This
means that we could add the -o option I suggest above. And we don't
need to do it now. We could add this option later instead of having to
make the command work on many files now.

Second, if the command should accept a patch as input instead of just
a commit message, or both, this means that the command should have a
way to tell if it is passed a patch, and then locate the commit
message part in the patch. This means yet other changes to the
command. Maybe these changes could be made later, in another series,
or when the need arises to use it on full patches.

Third, if trailers arguments are passed to the command using an option
like -z token=value or -z token:value, it would be nice to the
user for consistency if the same option could be used when passing the
same arguments to git commit and perhaps other commands like git
rebase, git cherry-pick and so on. This means that we now have to
choose carefully the name of this option. Perhaps we can just give it
a long name now like --trailer and care later about a short name, but
I think it would not be very nice to the user to only have a long name
for this option as it will very often be used.

Fourth, some users might want the command to be passed some files as
input, but they might not want the command to modify these input
files. They might prefer the command to write its ouput into another
set of output files. Maybe a syntax like cat or sed is not very well
suited for this kind of use, while having a -o option for the output
directory and a -i option for the input directory (if different from
the output dir) would be nicer.

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: On interpret-trailers standalone tool

2014-04-16 Thread Christian Couder
On Mon, Apr 14, 2014 at 11:41 PM, Junio C Hamano gits...@pobox.com wrote:
 Christian Couder chrisc...@tuxfamily.org writes:

 Yeah, except that we could add for example a '-o' option that would
 take a directory as argument and that would mean that the command
 should operate on all the files in this directory. It would be like
 the -o option of the format-patch command.

 For output for which users do not know offhand what files are to be
 produced, giving a single directory with -o makes tons of sense, but
 for input, naming each individual file (and with help with shell
 globs *) is a lot more natural UNIX tool way, I would think.

Yeah, but the git interpret-trailers command is a special, because,
if it takes files as arguments, then it is logical that its output
would be also files and not stdout. (See also at the end of this
message.)

 Take
 everything from this directory cannot be substitute for that, even
 though the reverse (i.e. by naming the input files with dir/*) is
 true.  It is not a viable replacement.

 First, if you think that the command might often be used along with
 format-patch,

 ... I am not singling out format-patch output.  Any text file/stream
 that has the commit log message may benefit from the trailers filter,
 and format-patch output is merely one very obvious example.  As to
 the detection of the end of commit log message, the current EOF is
 where the log message ends (but we would remote trailing blank line)
 can easily be updated to EOF or the first three-dash line.

Ok, I think that it's an interesting feature anyway, so I can add it
now instead of later.

 Third, if trailers arguments are passed to the command using an
 option like -z token=value or -z token:value, it would be nice
 to the user for consistency if the same option could be used when
 passing the same arguments to git commit and perhaps other
 commands like git rebase, git cherry-pick and so on. This
 means that we now have to choose carefully the name of this
 option. Perhaps we can just give it a long name now like --trailer
 and care later about a short name,...

 Absolutely.  That is a very sensible way to go.

Ok, I will use --trailer then. As I said in my previous message,
this unfortunately means that the command will not be very user
friendly until we integrate it with other commands like git commit
and find a short option name that hopefully work for all the commands.

 Fourth, some users might want the command to be passed some files as
 input, but they might not want the command to modify these input
 files. They might prefer the command to write its ouput into another
 set of output files. Maybe a syntax like cat or sed is not very well
 suited for this kind of use, while having a -o option for the output
 directory and a -i option for the input directory (if different from
 the output dir) would be nicer.

 Sure.  I would expect we would require something like Perl's '-i'
 (in-place rewrite) option for this sequence to really work:

 git format-patch -o there -5
 git that-command --options -i there/*

 and without, I would expect the output to come to its standard
 output.

If the input comes from stdin, then I agree that the command should be
a filter, so its output should be on stdout. But if the input comes
from files given as arguments, then I would say that the command
should behave as an editor and by default it should edit its input
file inplace. Its input and output files should be different only if
it is given one or more special option,

Otherwise the example you gave:

$ git format-patch -5 --cover-letter -o +my-series/ my-topic
$ git interpret-trailers some args ./+my-series/0*.patch

would result in having on stdout all the patches edited by git
interpret-trailers.
How would people could then easily send these edited patches?

Thanks,
Christian.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v11 02/11] trailer: process trailers from input message and arguments

2014-04-25 Thread Christian Couder
Implement the logic to process trailers from the input message
and from arguments.

At the beginning trailers from the input message 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 chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 198 ++
 1 file changed, 198 insertions(+)

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

[PATCH v11 04/11] trailer: process command line trailer arguments

2014-04-25 Thread Christian Couder
Parse the trailer command line arguments and put
the result into an arg_tok doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 118 ++
 1 file changed, 118 insertions(+)

diff --git a/trailer.c b/trailer.c
index f376be5..f79a369 100644
--- a/trailer.c
+++ b/trailer.c
@@ -1,4 +1,5 @@
 #include cache.h
+#include string-list.h
 /*
  * Copyright (c) 2013, 2014 Christian Couder chrisc...@tuxfamily.org
  */
@@ -391,3 +392,120 @@ 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 const char *token_from_item(struct trailer_item *item)
+{
+   if (item-conf.key)
+   return item-conf.key;
+
+   return item-conf.name;
+}
+
+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(token_from_item(conf_item));
+   free(tok);
+   } else
+   new-token = tok;
+
+   return new;
+}
+
+static int token_matches_item(const char *tok, struct trailer_item *item, int 
alnum_len)
+{
+   if (!strncasecmp(tok, item-conf.name, alnum_len))
+   return 1;
+   return item-conf.key ? !strncasecmp(tok, item-conf.key, alnum_len) : 
0;
+}
+
+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 (token_matches_item(tok.buf, item, 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(struct string_list 
*trailers)
+{
+   struct trailer_item *arg_tok_first = NULL;
+   struct trailer_item *arg_tok_last = NULL;
+   struct string_list_item *tr;
+
+   for_each_string_list_item(tr, trailers) {
+   struct trailer_item *new = create_trailer_item(tr-string);
+   add_trailer_item(arg_tok_first, arg_tok_last, new);
+   }
+
+   return arg_tok_first;
+}
-- 
1.9.1.636.g20d5f34


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v11 03/11] trailer: read and process config information

2014-04-25 Thread Christian Couder
Read the configuration to get trailer information, and then process
it and store 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 chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 146 ++
 1 file changed, 146 insertions(+)

diff --git a/trailer.c b/trailer.c
index 52108c2..f376be5 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,147 @@ 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 (!strcasecmp(after, value))
+   item-where = WHERE_AFTER;
+   else if (!strcasecmp(before, value))
+   item-where = WHERE_BEFORE;
+   else
+   return -1;
+   return 0;
+}
+
+static int set_if_exists(struct conf_info *item, const char *value)
+{
+   if (!strcasecmp(addIfDifferent, value))
+   item-if_exists = EXISTS_ADD_IF_DIFFERENT;
+   else if (!strcasecmp(addIfDifferentNeighbor, value))
+   item-if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
+   else if (!strcasecmp(add, value))
+   item-if_exists = EXISTS_ADD;
+   else if (!strcasecmp(overwrite, value))
+   item-if_exists = EXISTS_OVERWRITE;
+   else if (!strcasecmp(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 (!strcasecmp(doNothing, value))
+   item-if_missing = MISSING_DO_NOTHING;
+   else if (!strcasecmp(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;
+   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

[PATCH v11 05/11] trailer: parse trailers from file or stdin

2014-04-25 Thread Christian Couder
Read trailers from a file or from stdin, parse the trailers and then
put the result into a doubly linked list.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 trailer.c | 116 ++
 1 file changed, 116 insertions(+)

diff --git a/trailer.c b/trailer.c
index f79a369..4ca9157 100644
--- a/trailer.c
+++ b/trailer.c
@@ -51,6 +51,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);
@@ -509,3 +517,111 @@ static struct trailer_item 
*process_command_line_args(struct string_list *traile
 
return arg_tok_first;
 }
+
+static struct strbuf **read_input_file(const char *file)
+{
+   struct strbuf **lines;
+   struct strbuf sb = STRBUF_INIT;
+
+   if (file) {
+   if (strbuf_read_file(sb, file, 0)  0)
+   die_errno(_(could not read input file '%s'), file);
+   } else {
+   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 (0 based) index of the start of the patch or the line
+ * count if there is no patch in the message.
+ */
+static int find_patch_start(struct strbuf **lines, int count)
+{
+   int i;
+
+   /* Get the start of the patch part if any */
+   for (i = 0; i  count; i++) {
+   if (starts_with(lines[i]-buf, ---))
+   return i;
+   }
+
+   return count;
+}
+
+/*
+ * Return the (0 based) index of the first trailer line or count if
+ * there are no trailers. Trailers are searched only in the lines from
+ * index (count - 1) down to index 0. The has_blank_line parameter
+ * tells if there is a blank line before the trailers.
+ */
+static int find_trailer_start(struct strbuf **lines, int count, int 
*has_blank_line)
+{
+   int start, only_spaces = 1;
+
+   /*
+* 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 (only_spaces)
+   continue;
+   *has_blank_line = 1;
+   return start + 1;
+   }
+   if (strchr(lines[start]-buf, ':')) {
+   if (only_spaces)
+   only_spaces = 0;
+   continue;
+   }
+   *has_blank_line = start == count - 1 ?
+ 0 : contains_only_spaces(lines[start + 1]-buf);
+   return count;
+   }
+
+   *has_blank_line = only_spaces ? count  0 : 0;
+   return only_spaces ? count : start + 1;
+}
+
+static void print_lines(struct strbuf **lines, int start, int end)
+{
+   int i;
+   for (i = start; lines[i]  i  end; i++)
+   printf(%s, lines[i]-buf);
+}
+
+static int process_input_file(struct strbuf **lines,
+ struct trailer_item **in_tok_first,
+ struct trailer_item **in_tok_last)
+{
+   int count = 0;
+   int patch_start, trailer_start, has_blank_line, i;
+
+   /* Get the line count */
+   while (lines[count])
+   count++;
+
+   patch_start = find_patch_start(lines, count);
+   trailer_start = find_trailer_start(lines, patch_start, has_blank_line);
+
+   /* Print lines before the trailers as is */
+   print_lines(lines, 0, trailer_start);
+
+   if (!has_blank_line)
+   printf(\n);
+
+   /* Parse trailer lines */
+   for (i = trailer_start; i  patch_start; i++) {
+   struct trailer_item *new = create_trailer_item(lines[i]-buf);
+   add_trailer_item(in_tok_first, in_tok_last, new);
+   }
+
+   return patch_start;
+}
-- 
1.9.1.636.g20d5f34


--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[PATCH v11 11/11] Documentation: add documentation for 'git interpret-trailers'

2014-04-25 Thread Christian Couder
While at it add git-interpret-trailers to command-list.txt.

Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 Documentation/git-interpret-trailers.txt | 143 +++
 command-list.txt |   1 +
 2 files changed, 144 insertions(+)
 create mode 100644 Documentation/git-interpret-trailers.txt

diff --git a/Documentation/git-interpret-trailers.txt 
b/Documentation/git-interpret-trailers.txt
new file mode 100644
index 000..450ec54
--- /dev/null
+++ b/Documentation/git-interpret-trailers.txt
@@ -0,0 +1,143 @@
+git-interpret-trailers(1)
+=
+
+NAME
+
+git-interpret-trailers - help add stuctured information into commit messages
+
+SYNOPSIS
+
+[verse]
+'git interpret-trailers' [--trim-empty] [(--trailer token[(=|:)value])...] 
[file...]
+
+DESCRIPTION
+---
+Help adding 'trailers' lines, that look similar to RFC 822 e-mail
+headers, at the end of the otherwise free-form part of a commit
+message.
+
+This command reads some patches or commit messages from either the
+file arguments or the standard input if no file is specified. Then
+this command applies the arguments passed using the `--trailer`
+option, if any, to the commit message part of each input file. The
+result is emitted on the standard output.
+
+Some configuration variables control the way the `--trailer` arguments
+are applied to each commit message and the way any existing trailer in
+the commit message is changed. They also make it possible to
+automatically add some trailers.
+
+By default, a 'token=value' or 'token:value' argument given
+using `--trailer` 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.
+
+The trailers are recognized in the input commit message using the
+following rules:
+
+* only lines that contains a ':' are considered trailers,
+* the trailer lines must all be next to each other,
+* after them it's only possible to have some lines that contain only spaces,
+* before them there must be at least one line with only spaces.
+
+Note that 'trailers' do not follow and are not intended to follow many
+rules for RFC 822 headers. For example they do not follow the line
+folding 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.
+   This apply to existing trailers as well as new trailers.
+
+--trailer token[(=|:)value]::
+   Specify a (token, value) pair that should be applied as a
+   trailer to the input messages. See the description of this
+   command.
+
+CONFIGURATION VARIABLES
+---
+
+trailer.token.key::
+   This `key` will be used instead of token in the
+   trailer. After the last alphanumeric character, 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

[PATCH v11 00/11] Add interpret-trailers builtin

2014-04-25 Thread Christian Couder
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 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.

2) Current state:

Currently the usage string of this command is:

git interpret-trailers [--trim-empty] [(--trailer token[(=|:)value])...] 
[file...]

The following features are implemented:

- the result is printed on stdout
- the --trailer arguments are interpreted
- messages read from file... (new) or stdin are 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
- messages can contain a patch (new)
- there are 34 tests
- there is some documentation

The following features are planned but not yet implemented:
- add examples in documentation

Possible improvements:
- integration with git commit
- support GIT_COMMIT_PROTO env variable in commands

3) Changes since version 10, thanks to Michael and Junio:

* changed the usage string and the interface
* detect a patch in the input and print it if any
* adapt tests and add some related to the above changes
* improved documentation
* squashed patch to add blank lines before trailers into
  other pathes
* added git-interpret-trailers to command-list.txt
* use strcasecmp() instead of strcmp() to compare
  some values from the config file matching a fixed set

This means that only patches 1/11, 2/11, 3/11, 9/11 and 10/11
mostly did not change. I say mostly because there are still
at least a commit message change in 2/11 and
s/strcmp/strcasecmp/ in 3/11.

Christian Couder (11):
  trailer: add data structures and basic functions
  trailer: process trailers from input message and arguments
  trailer: read and process config information
  trailer: process command line trailer arguments
  trailer: parse trailers from file or 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 | 143 ++
 Makefile |   2 +
 builtin.h|   1 +
 builtin/interpret-trailers.c |  44 ++
 command-list.txt |   1 +
 git.c|   1 +
 t/t7513-interpret-trailers.sh| 542 ++
 trailer.c| 750 +++
 trailer.h|   6 +
 10 files changed, 1491 insertions(+)
 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.1.636.g20d5f34

--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo

[PATCH v11 08/11] trailer: add tests for git interpret-trailers

2014-04-25 Thread Christian Couder
Signed-off-by: Christian Couder chrisc...@tuxfamily.org
Signed-off-by: Junio C Hamano gits...@pobox.com
---
 t/t7513-interpret-trailers.sh | 418 ++
 1 file changed, 418 insertions(+)
 create mode 100755 t/t7513-interpret-trailers.sh

diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
new file mode 100755
index 000..4506e18
--- /dev/null
+++ b/t/t7513-interpret-trailers.sh
@@ -0,0 +1,418 @@
+#!/bin/sh
+#
+# Copyright (c) 2013, 2014 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
+   cat basic_patch -\EOF
+   ---
+foo.txt | 2 +-
+1 file changed, 1 insertion(+), 1 deletion(-)
+
+   diff --git a/foo.txt b/foo.txt
+   index 0353767..1d91aa1 100644
+   --- a/foo.txt
+   +++ b/foo.txt
+   @@ -1,3 +1,3 @@
+
+   -bar
+   +baz
+
+   --
+   1.9.rc0.11.ga562ddc
+
+   EOF
+'
+
+test_expect_success 'without config' '
+   sed -e s/ Z\$/ / expected -\EOF 
+
+   ack: Peff
+   Reviewed-by: Z
+   Acked-by: Johan
+   EOF
+   git interpret-trailers --trailer ack = Peff --trailer Reviewed-by \
+   --trailer 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 --trailer ack = Peff \
+   --trailer Reviewed-by --trailer Acked-by: Johan \
+   --trailer 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 --trailer ack = Peff actual 
+   test_cmp expected actual 
+   git interpret-trailers --trim-empty --trailer Acked-by = Peff actual 

+   test_cmp expected actual 
+   git interpret-trailers --trim-empty --trailer 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 --trailer ack = Peff actual 
+   test_cmp expected actual 
+   git interpret-trailers --trim-empty --trailer Acked-by= Peff actual 

+   test_cmp expected actual 
+   git interpret-trailers --trim-empty --trailer 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 --trailer bug = 42 actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with commit basic message' '
+   cat basic_message expected 
+   echo expected 
+   git interpret-trailers basic_message actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with basic patch' '
+   cat basic_message input 
+   cat basic_patch input 
+   cat basic_message expected 
+   echo expected 
+   cat basic_patch expected 
+   git interpret-trailers input actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with commit complex message as argument' '
+   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 2 files arguments' '
+   cat basic_message expected 
+   echo expected 
+   cat basic_patch expected 
+   git interpret-trailers complex_message input actual 
+   test_cmp expected actual
+'
+
+test_expect_success 'with commit complex message and trailer args' '
+   cat complex_message_body expected 
+   sed -e s/ Z

<    1   2   3   4   5   6   7   8   9   10   >