Merge branch 'jt/use-trailer-api-in-commands'
authorJunio C Hamano <gitster@pobox.com>
Mon, 19 Dec 2016 22:45:29 +0000 (14:45 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 19 Dec 2016 22:45:29 +0000 (14:45 -0800)
Commands that operate on a log message and add lines to the trailer
blocks, such as "format-patch -s", "cherry-pick (-x|-s)", and
"commit -s", have been taught to use the logic of and share the
code with "git interpret-trailer".

* jt/use-trailer-api-in-commands:
sequencer: use trailer's trailer layout
trailer: have function to describe trailer layout
trailer: avoid unnecessary splitting on lines
commit: make ignore_non_trailer take buf/len
trailer: be stricter in parsing separators

builtin/commit.c
commit.c
commit.h
sequencer.c
t/t3511-cherry-pick-x.sh
t/t4014-format-patch.sh
t/t7501-commit.sh
trailer.c
trailer.h
index 8976c3d29bf817be789f983697f7052fe5769f39..887ccc7577a12a16b40e67eb2819356e888f1965 100644 (file)
@@ -790,7 +790,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                strbuf_stripspace(&sb, 0);
 
        if (signoff)
-               append_signoff(&sb, ignore_non_trailer(&sb), 0);
+               append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
 
        if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
                die_errno(_("could not write commit template"));
index 856fd4aeeff654c45ba0a6149f9db2c22806e0cb..2cf85158b4899b664a3cbae8d0777f5a9e473318 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1649,7 +1649,7 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len
 }
 
 /*
- * Inspect sb and determine the true "end" of the log message, in
+ * Inspect the given string and determine the true "end" of the log message, in
  * order to find where to put a new Signed-off-by: line.  Ignored are
  * trailing comment lines and blank lines, and also the traditional
  * "Conflicts:" block that is not commented out, so that we can use
@@ -1659,37 +1659,37 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len
  * Returns the number of bytes from the tail to ignore, to be fed as
  * the second parameter to append_signoff().
  */
-int ignore_non_trailer(struct strbuf *sb)
+int ignore_non_trailer(const char *buf, size_t len)
 {
        int boc = 0;
        int bol = 0;
        int in_old_conflicts_block = 0;
 
-       while (bol < sb->len) {
-               char *next_line;
+       while (bol < len) {
+               const char *next_line = memchr(buf + bol, '\n', len - bol);
 
-               if (!(next_line = memchr(sb->buf + bol, '\n', sb->len - bol)))
-                       next_line = sb->buf + sb->len;
+               if (!next_line)
+                       next_line = buf + len;
                else
                        next_line++;
 
-               if (sb->buf[bol] == comment_line_char || sb->buf[bol] == '\n') {
+               if (buf[bol] == comment_line_char || buf[bol] == '\n') {
                        /* is this the first of the run of comments? */
                        if (!boc)
                                boc = bol;
                        /* otherwise, it is just continuing */
-               } else if (starts_with(sb->buf + bol, "Conflicts:\n")) {
+               } else if (starts_with(buf + bol, "Conflicts:\n")) {
                        in_old_conflicts_block = 1;
                        if (!boc)
                                boc = bol;
-               } else if (in_old_conflicts_block && sb->buf[bol] == '\t') {
+               } else if (in_old_conflicts_block && buf[bol] == '\t') {
                        ; /* a pathname in the conflicts block */
                } else if (boc) {
                        /* the previous was not trailing comment */
                        boc = 0;
                        in_old_conflicts_block = 0;
                }
-               bol = next_line - sb->buf;
+               bol = next_line - buf;
        }
-       return boc ? sb->len - boc : 0;
+       return boc ? len - boc : 0;
 }
index afd14f318c0c4d18f3a03781d3229f2fdc0f64c6..9c12abb91110151e3a982c4d8baf8753d0382441 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -355,7 +355,7 @@ extern const char *find_commit_header(const char *msg, const char *key,
                                      size_t *out_len);
 
 /* Find the end of the log message, the right place for a new trailer. */
-extern int ignore_non_trailer(struct strbuf *sb);
+extern int ignore_non_trailer(const char *buf, size_t len);
 
 typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
                                 void *cb_data);
index 30b10ba143959bf2618872427f730a9f0e30d369..050e28bbc3d136509e8b7d909b27e9d954fa7754 100644 (file)
@@ -16,6 +16,7 @@
 #include "refs.h"
 #include "argv-array.h"
 #include "quote.h"
+#include "trailer.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -56,30 +57,6 @@ static const char *get_todo_path(const struct replay_opts *opts)
        return git_path_todo_file();
 }
 
-static int is_rfc2822_line(const char *buf, int len)
-{
-       int i;
-
-       for (i = 0; i < len; i++) {
-               int ch = buf[i];
-               if (ch == ':')
-                       return 1;
-               if (!isalnum(ch) && ch != '-')
-                       break;
-       }
-
-       return 0;
-}
-
-static int is_cherry_picked_from_line(const char *buf, int len)
-{
-       /*
-        * We only care that it looks roughly like (cherry picked from ...)
-        */
-       return len > strlen(cherry_picked_prefix) + 1 &&
-               starts_with(buf, cherry_picked_prefix) && buf[len - 1] == ')';
-}
-
 /*
  * Returns 0 for non-conforming footer
  * Returns 1 for conforming footer
@@ -89,49 +66,25 @@ static int is_cherry_picked_from_line(const char *buf, int len)
 static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
        int ignore_footer)
 {
-       char prev;
-       int i, k;
-       int len = sb->len - ignore_footer;
-       const char *buf = sb->buf;
-       int found_sob = 0;
-
-       /* footer must end with newline */
-       if (!len || buf[len - 1] != '\n')
-               return 0;
+       struct trailer_info info;
+       int i;
+       int found_sob = 0, found_sob_last = 0;
 
-       prev = '\0';
-       for (i = len - 1; i > 0; i--) {
-               char ch = buf[i];
-               if (prev == '\n' && ch == '\n') /* paragraph break */
-                       break;
-               prev = ch;
-       }
+       trailer_info_get(&info, sb->buf);
 
-       /* require at least one blank line */
-       if (prev != '\n' || buf[i] != '\n')
+       if (info.trailer_start == info.trailer_end)
                return 0;
 
-       /* advance to start of last paragraph */
-       while (i < len - 1 && buf[i] == '\n')
-               i++;
-
-       for (; i < len; i = k) {
-               int found_rfc2822;
-
-               for (k = i; k < len && buf[k] != '\n'; k++)
-                       ; /* do nothing */
-               k++;
+       for (i = 0; i < info.trailer_nr; i++)
+               if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {
+                       found_sob = 1;
+                       if (i == info.trailer_nr - 1)
+                               found_sob_last = 1;
+               }
 
-               found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
-               if (found_rfc2822 && sob &&
-                   !strncmp(buf + i, sob->buf, sob->len))
-                       found_sob = k;
+       trailer_info_release(&info);
 
-               if (!(found_rfc2822 ||
-                     is_cherry_picked_from_line(buf + i, k - i - 1)))
-                       return 0;
-       }
-       if (found_sob == i)
+       if (found_sob_last)
                return 3;
        if (found_sob)
                return 2;
index 9cce5ae8815a11e3453f5669b0592f7f7659009b..bf0a5c9887235afb5e336a34815d6bb3d64875d8 100755 (executable)
@@ -25,9 +25,8 @@ Signed-off-by: B.U. Thor <buthor@example.com>"
 
 mesg_broken_footer="$mesg_no_footer
 
-The signed-off-by string should begin with the words Signed-off-by followed
-by a colon and space, and then the signers name and email address. e.g.
-Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+This is not recognized as a footer because Myfooter is not a recognized token.
+Myfooter: A.U. Thor <author@example.com>"
 
 mesg_with_footer_sob="$mesg_with_footer
 Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
@@ -112,6 +111,17 @@ test_expect_success 'cherry-pick -s inserts blank line after non-conforming foot
        test_cmp expect actual
 '
 
+test_expect_success 'cherry-pick -s recognizes trailer config' '
+       pristine_detach initial &&
+       git -c "trailer.Myfooter.ifexists=add" cherry-pick -s mesg-broken-footer &&
+       cat <<-EOF >expect &&
+               $mesg_broken_footer
+               Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+       EOF
+       git log -1 --pretty=format:%B >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' '
        pristine_detach initial &&
        sha1=$(git rev-parse mesg-no-footer^0) &&
index ba4902df2b605f89ec2abcac50abefc2f23fc9bf..482112ca339f05fc31e6f9c2c6168971a1121e87 100755 (executable)
@@ -1294,8 +1294,7 @@ EOF
 4:Subject: [PATCH] subject
 8:
 10:Signed-off-by: example happens to be wrapped here.
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
+11:Signed-off-by: C O Mitter <committer@example.com>
 EOF
        test_cmp expected actual
 '
@@ -1368,7 +1367,7 @@ EOF
        test_cmp expected actual
 '
 
-test_expect_success 'signoff: detect garbage in non-conforming footer' '
+test_expect_success 'signoff: tolerate garbage in conforming footer' '
        append_signoff <<\EOF >actual &&
 subject
 
@@ -1383,8 +1382,36 @@ EOF
 8:
 10:
 13:Signed-off-by: C O Mitter <committer@example.com>
-14:
-15:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'signoff: respect trailer config' '
+       append_signoff <<\EOF >actual &&
+subject
+
+Myfooter: x
+Some Trash
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+11:
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+       test_cmp expected actual &&
+
+       test_config trailer.Myfooter.ifexists add &&
+       append_signoff <<\EOF >actual &&
+subject
+
+Myfooter: x
+Some Trash
+EOF
+       cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+11:Signed-off-by: C O Mitter <committer@example.com>
 EOF
        test_cmp expected actual
 '
index d84897a67a3c365e280f88b36f43fc49e1ac9d7b..4003a27e6aa117e30446bca2d91f230f144ae8a9 100755 (executable)
@@ -460,6 +460,42 @@ $alt" &&
        test_cmp expected actual
 '
 
+test_expect_success 'signoff respects trailer config' '
+
+       echo 5 >positive &&
+       git add positive &&
+       git commit -s -m "subject
+
+non-trailer line
+Myfooter: x" &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+       (
+               echo subject
+               echo
+               echo non-trailer line
+               echo Myfooter: x
+               echo
+               echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+       ) >expected &&
+       test_cmp expected actual &&
+
+       echo 6 >positive &&
+       git add positive &&
+       git -c "trailer.Myfooter.ifexists=add" commit -s -m "subject
+
+non-trailer line
+Myfooter: x" &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" > actual &&
+       (
+               echo subject
+               echo
+               echo non-trailer line
+               echo Myfooter: x
+               echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+       ) >expected &&
+       test_cmp expected actual
+'
+
 test_expect_success 'multiple -m' '
 
        >negative &&
index f0ecde2d2f8b35c565ab176c76882a5fee8d8fb2..11f0b9fb40bcedbfcd3111fa8076ce0d9e1eb20f 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -46,6 +46,8 @@ static LIST_HEAD(conf_head);
 
 static char *separators = ":";
 
+static int configured;
+
 #define TRAILER_ARG_STRING "$ARG"
 
 static const char *git_generated_prefixes[] = {
@@ -102,12 +104,12 @@ static int same_trailer(struct trailer_item *a, struct arg_item *b)
        return same_token(a, b) && same_value(a, b);
 }
 
-static inline int contains_only_spaces(const char *str)
+static inline int is_blank_line(const char *str)
 {
        const char *s = str;
-       while (*s && isspace(*s))
+       while (*s && *s != '\n' && isspace(*s))
                s++;
-       return !*s;
+       return !*s || *s == '\n';
 }
 
 static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b)
@@ -546,6 +548,17 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
        return 0;
 }
 
+static void ensure_configured(void)
+{
+       if (configured)
+               return;
+
+       /* Default config must be setup first */
+       git_config(git_trailer_default_config, NULL);
+       git_config(git_trailer_config, NULL);
+       configured = 1;
+}
+
 static const char *token_from_item(struct arg_item *item, char *tok)
 {
        if (item->conf.key)
@@ -563,15 +576,32 @@ static int token_matches_item(const char *tok, struct arg_item *item, int tok_le
 }
 
 /*
- * Return the location of the first separator in line, or -1 if there is no
- * separator.
+ * If the given line is of the form
+ * "<token><optional whitespace><separator>..." or "<separator>...", return the
+ * location of the separator. Otherwise, return -1.  The optional whitespace
+ * is allowed there primarily to allow things like "Bug #43" where <token> is
+ * "Bug" and <separator> is "#".
+ *
+ * The separator-starts-line case (in which this function returns 0) is
+ * distinguished from the non-well-formed-line case (in which this function
+ * returns -1) because some callers of this function need such a distinction.
  */
 static int find_separator(const char *line, const char *separators)
 {
-       int loc = strcspn(line, separators);
-       if (!line[loc])
-               return -1;
-       return loc;
+       int whitespace_found = 0;
+       const char *c;
+       for (c = line; *c; c++) {
+               if (strchr(separators, *c))
+                       return c - line;
+               if (!whitespace_found && (isalnum(*c) || *c == '-'))
+                       continue;
+               if (c != line && (*c == ' ' || *c == '\t')) {
+                       whitespace_found = 1;
+                       continue;
+               }
+               break;
+       }
+       return -1;
 }
 
 /*
@@ -685,51 +715,71 @@ static void process_command_line_args(struct list_head *arg_head,
        free(cl_separators);
 }
 
-static struct strbuf **read_input_file(const char *file)
+static void read_input_file(struct strbuf *sb, const char *file)
 {
-       struct strbuf **lines;
-       struct strbuf sb = STRBUF_INIT;
-
        if (file) {
-               if (strbuf_read_file(&sb, file, 0) < 0)
+               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)
+               if (strbuf_read(sb, fileno(stdin), 0) < 0)
                        die_errno(_("could not read from stdin"));
        }
+}
 
-       lines = strbuf_split(&sb, '\n');
+static const char *next_line(const char *str)
+{
+       const char *nl = strchrnul(str, '\n');
+       return nl + !!*nl;
+}
 
-       strbuf_release(&sb);
+/*
+ * Return the position of the start of the last line. If len is 0, return -1.
+ */
+static int last_line(const char *buf, size_t len)
+{
+       int i;
+       if (len == 0)
+               return -1;
+       if (len == 1)
+               return 0;
+       /*
+        * Skip the last character (in addition to the null terminator),
+        * because if the last character is a newline, it is considered as part
+        * of the last line anyway.
+        */
+       i = len - 2;
 
-       return lines;
+       for (; i >= 0; i--) {
+               if (buf[i] == '\n')
+                       return i + 1;
+       }
+       return 0;
 }
 
 /*
- * Return the (0 based) index of the start of the patch or the line
- * count if there is no patch in the message.
+ * Return the position of the start of the patch or the length of str if there
+ * is no patch in the message.
  */
-static int find_patch_start(struct strbuf **lines, int count)
+static int find_patch_start(const char *str)
 {
-       int i;
+       const char *s;
 
-       /* Get the start of the patch part if any */
-       for (i = 0; i < count; i++) {
-               if (starts_with(lines[i]->buf, "---"))
-                       return i;
+       for (s = str; *s; s = next_line(s)) {
+               if (starts_with(s, "---"))
+                       return s - str;
        }
 
-       return count;
+       return s - str;
 }
 
 /*
- * 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.
+ * Return the position of the first trailer line or len if there are no
+ * trailers.
  */
-static int find_trailer_start(struct strbuf **lines, int count)
+static int find_trailer_start(const char *buf, size_t len)
 {
-       int start, end_of_title, only_spaces = 1;
+       const char *s;
+       int end_of_title, l, only_spaces = 1;
        int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;
        /*
         * Number of possible continuation lines encountered. This will be
@@ -741,13 +791,13 @@ static int find_trailer_start(struct strbuf **lines, int count)
        int possible_continuation_lines = 0;
 
        /* The first paragraph is the title and cannot be trailers */
-       for (start = 0; start < count; start++) {
-               if (lines[start]->buf[0] == comment_line_char)
+       for (s = buf; s < buf + len; s = next_line(s)) {
+               if (s[0] == comment_line_char)
                        continue;
-               if (contains_only_spaces(lines[start]->buf))
+               if (is_blank_line(s))
                        break;
        }
-       end_of_title = start;
+       end_of_title = s - buf;
 
        /*
         * Get the start of the trailers by looking starting from the end for a
@@ -755,30 +805,33 @@ static int find_trailer_start(struct strbuf **lines, int count)
         * trailers, or (ii) contains at least one Git-generated trailer and
         * consists of at least 25% trailers.
         */
-       for (start = count - 1; start >= end_of_title; start--) {
+       for (l = last_line(buf, len);
+            l >= end_of_title;
+            l = last_line(buf, l)) {
+               const char *bol = buf + l;
                const char **p;
                int separator_pos;
 
-               if (lines[start]->buf[0] == comment_line_char) {
+               if (bol[0] == comment_line_char) {
                        non_trailer_lines += possible_continuation_lines;
                        possible_continuation_lines = 0;
                        continue;
                }
-               if (contains_only_spaces(lines[start]->buf)) {
+               if (is_blank_line(bol)) {
                        if (only_spaces)
                                continue;
                        non_trailer_lines += possible_continuation_lines;
                        if (recognized_prefix &&
                            trailer_lines * 3 >= non_trailer_lines)
-                               return start + 1;
-                       if (trailer_lines && !non_trailer_lines)
-                               return start + 1;
-                       return count;
+                               return next_line(bol) - buf;
+                       else if (trailer_lines && !non_trailer_lines)
+                               return next_line(bol) - buf;
+                       return len;
                }
                only_spaces = 0;
 
                for (p = git_generated_prefixes; *p; p++) {
-                       if (starts_with(lines[start]->buf, *p)) {
+                       if (starts_with(bol, *p)) {
                                trailer_lines++;
                                possible_continuation_lines = 0;
                                recognized_prefix = 1;
@@ -786,8 +839,8 @@ static int find_trailer_start(struct strbuf **lines, int count)
                        }
                }
 
-               separator_pos = find_separator(lines[start]->buf, separators);
-               if (separator_pos >= 1 && !isspace(lines[start]->buf[0])) {
+               separator_pos = find_separator(bol, separators);
+               if (separator_pos >= 1 && !isspace(bol[0])) {
                        struct list_head *pos;
 
                        trailer_lines++;
@@ -797,13 +850,13 @@ static int find_trailer_start(struct strbuf **lines, int count)
                        list_for_each(pos, &conf_head) {
                                struct arg_item *item;
                                item = list_entry(pos, struct arg_item, list);
-                               if (token_matches_item(lines[start]->buf, item,
+                               if (token_matches_item(bol, item,
                                                       separator_pos)) {
                                        recognized_prefix = 1;
                                        break;
                                }
                        }
-               } else if (isspace(lines[start]->buf[0]))
+               } else if (isspace(bol[0]))
                        possible_continuation_lines++;
                else {
                        non_trailer_lines++;
@@ -814,97 +867,64 @@ static int find_trailer_start(struct strbuf **lines, int count)
                ;
        }
 
-       return count;
-}
-
-/* Get the index of the end of the trailers */
-static int find_trailer_end(struct strbuf **lines, int patch_start)
-{
-       struct strbuf sb = STRBUF_INIT;
-       int i, ignore_bytes;
-
-       for (i = 0; i < patch_start; i++)
-               strbuf_addbuf(&sb, lines[i]);
-       ignore_bytes = ignore_non_trailer(&sb);
-       strbuf_release(&sb);
-       for (i = patch_start - 1; i >= 0 && ignore_bytes > 0; i--)
-               ignore_bytes -= lines[i]->len;
-
-       return i + 1;
+       return len;
 }
 
-static int has_blank_line_before(struct strbuf **lines, int start)
+/* Return the position of the end of the trailers. */
+static int find_trailer_end(const char *buf, size_t len)
 {
-       for (;start >= 0; start--) {
-               if (lines[start]->buf[0] == comment_line_char)
-                       continue;
-               return contains_only_spaces(lines[start]->buf);
-       }
-       return 0;
+       return len - ignore_non_trailer(buf, len);
 }
 
-static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end)
+static int ends_with_blank_line(const char *buf, size_t len)
 {
-       int i;
-       for (i = start; lines[i] && i < end; i++)
-               fprintf(outfile, "%s", lines[i]->buf);
+       int ll = last_line(buf, len);
+       if (ll < 0)
+               return 0;
+       return is_blank_line(buf + ll);
 }
 
 static int process_input_file(FILE *outfile,
-                             struct strbuf **lines,
+                             const char *str,
                              struct list_head *head)
 {
-       int count = 0;
-       int patch_start, trailer_start, trailer_end, i;
+       struct trailer_info info;
        struct strbuf tok = STRBUF_INIT;
        struct strbuf val = STRBUF_INIT;
-       struct trailer_item *last = NULL;
-
-       /* Get the line count */
-       while (lines[count])
-               count++;
+       int i;
 
-       patch_start = find_patch_start(lines, count);
-       trailer_end = find_trailer_end(lines, patch_start);
-       trailer_start = find_trailer_start(lines, trailer_end);
+       trailer_info_get(&info, str);
 
        /* Print lines before the trailers as is */
-       print_lines(outfile, lines, 0, trailer_start);
+       fwrite(str, 1, info.trailer_start - str, outfile);
 
-       if (!has_blank_line_before(lines, trailer_start - 1))
+       if (!info.blank_line_before_trailer)
                fprintf(outfile, "\n");
 
-       /* Parse trailer lines */
-       for (i = trailer_start; i < trailer_end; i++) {
+       for (i = 0; i < info.trailer_nr; i++) {
                int separator_pos;
-               if (lines[i]->buf[0] == comment_line_char)
+               char *trailer = info.trailers[i];
+               if (trailer[0] == comment_line_char)
                        continue;
-               if (last && isspace(lines[i]->buf[0])) {
-                       struct strbuf sb = STRBUF_INIT;
-                       strbuf_addf(&sb, "%s\n%s", last->value, lines[i]->buf);
-                       strbuf_strip_suffix(&sb, "\n");
-                       free(last->value);
-                       last->value = strbuf_detach(&sb, NULL);
-                       continue;
-               }
-               separator_pos = find_separator(lines[i]->buf, separators);
+               separator_pos = find_separator(trailer, separators);
                if (separator_pos >= 1) {
-                       parse_trailer(&tok, &val, NULL, lines[i]->buf,
+                       parse_trailer(&tok, &val, NULL, trailer,
                                      separator_pos);
-                       last = add_trailer_item(head,
-                                               strbuf_detach(&tok, NULL),
-                                               strbuf_detach(&val, NULL));
+                       add_trailer_item(head,
+                                        strbuf_detach(&tok, NULL),
+                                        strbuf_detach(&val, NULL));
                } else {
-                       strbuf_addbuf(&val, lines[i]);
+                       strbuf_addstr(&val, trailer);
                        strbuf_strip_suffix(&val, "\n");
                        add_trailer_item(head,
                                         NULL,
                                         strbuf_detach(&val, NULL));
-                       last = NULL;
                }
        }
 
-       return trailer_end;
+       trailer_info_release(&info);
+
+       return info.trailer_end - str;
 }
 
 static void free_all(struct list_head *head)
@@ -951,21 +971,19 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
 {
        LIST_HEAD(head);
        LIST_HEAD(arg_head);
-       struct strbuf **lines;
+       struct strbuf sb = STRBUF_INIT;
        int trailer_end;
        FILE *outfile = stdout;
 
-       /* Default config must be setup first */
-       git_config(git_trailer_default_config, NULL);
-       git_config(git_trailer_config, NULL);
+       ensure_configured();
 
-       lines = read_input_file(file);
+       read_input_file(&sb, file);
 
        if (in_place)
                outfile = create_in_place_tempfile(file);
 
        /* Print the lines before the trailers */
-       trailer_end = process_input_file(outfile, lines, &head);
+       trailer_end = process_input_file(outfile, sb.buf, &head);
 
        process_command_line_args(&arg_head, trailers);
 
@@ -976,11 +994,62 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str
        free_all(&head);
 
        /* Print the lines after the trailers as is */
-       print_lines(outfile, lines, trailer_end, INT_MAX);
+       fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile);
 
        if (in_place)
                if (rename_tempfile(&trailers_tempfile, file))
                        die_errno(_("could not rename temporary file to %s"), file);
 
-       strbuf_list_free(lines);
+       strbuf_release(&sb);
+}
+
+void trailer_info_get(struct trailer_info *info, const char *str)
+{
+       int patch_start, trailer_end, trailer_start;
+       struct strbuf **trailer_lines, **ptr;
+       char **trailer_strings = NULL;
+       size_t nr = 0, alloc = 0;
+       char **last = NULL;
+
+       ensure_configured();
+
+       patch_start = find_patch_start(str);
+       trailer_end = find_trailer_end(str, patch_start);
+       trailer_start = find_trailer_start(str, trailer_end);
+
+       trailer_lines = strbuf_split_buf(str + trailer_start,
+                                        trailer_end - trailer_start,
+                                        '\n',
+                                        0);
+       for (ptr = trailer_lines; *ptr; ptr++) {
+               if (last && isspace((*ptr)->buf[0])) {
+                       struct strbuf sb = STRBUF_INIT;
+                       strbuf_attach(&sb, *last, strlen(*last), strlen(*last));
+                       strbuf_addbuf(&sb, *ptr);
+                       *last = strbuf_detach(&sb, NULL);
+                       continue;
+               }
+               ALLOC_GROW(trailer_strings, nr + 1, alloc);
+               trailer_strings[nr] = strbuf_detach(*ptr, NULL);
+               last = find_separator(trailer_strings[nr], separators) >= 1
+                       ? &trailer_strings[nr]
+                       : NULL;
+               nr++;
+       }
+       strbuf_list_free(trailer_lines);
+
+       info->blank_line_before_trailer = ends_with_blank_line(str,
+                                                              trailer_start);
+       info->trailer_start = str + trailer_start;
+       info->trailer_end = str + trailer_end;
+       info->trailers = trailer_strings;
+       info->trailer_nr = nr;
+}
+
+void trailer_info_release(struct trailer_info *info)
+{
+       int i;
+       for (i = 0; i < info->trailer_nr; i++)
+               free(info->trailers[i]);
+       free(info->trailers);
 }
index 36b40b81761f95742c9c6fe7e94ba89f19aabfde..65cc5d79c6cecfce5cf3302dd7dcb6323f9f85dc 100644 (file)
--- a/trailer.h
+++ b/trailer.h
@@ -1,7 +1,32 @@
 #ifndef TRAILER_H
 #define TRAILER_H
 
+struct trailer_info {
+       /*
+        * True if there is a blank line before the location pointed to by
+        * trailer_start.
+        */
+       int blank_line_before_trailer;
+
+       /*
+        * Pointers to the start and end of the trailer block found. If there
+        * is no trailer block found, these 2 pointers point to the end of the
+        * input string.
+        */
+       const char *trailer_start, *trailer_end;
+
+       /*
+        * Array of trailers found.
+        */
+       char **trailers;
+       size_t trailer_nr;
+};
+
 void process_trailers(const char *file, int in_place, int trim_empty,
                      struct string_list *trailers);
 
+void trailer_info_get(struct trailer_info *info, const char *str);
+
+void trailer_info_release(struct trailer_info *info);
+
 #endif /* TRAILER_H */