mirror of
https://github.com/git/git
synced 2024-08-25 02:35:46 +00:00
Merge branch 'jt/use-trailer-api-in-commands'
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
This commit is contained in:
commit
8b0db484e1
|
@ -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"));
|
||||
|
|
22
commit.c
22
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;
|
||||
}
|
||||
|
|
2
commit.h
2
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);
|
||||
|
|
77
sequencer.c
77
sequencer.c
|
@ -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;
|
||||
struct trailer_info info;
|
||||
int i;
|
||||
int found_sob = 0, found_sob_last = 0;
|
||||
|
||||
/* footer must end with newline */
|
||||
if (!len || buf[len - 1] != '\n')
|
||||
trailer_info_get(&info, sb->buf);
|
||||
|
||||
if (info.trailer_start == info.trailer_end)
|
||||
return 0;
|
||||
|
||||
prev = '\0';
|
||||
for (i = len - 1; i > 0; i--) {
|
||||
char ch = buf[i];
|
||||
if (prev == '\n' && ch == '\n') /* paragraph break */
|
||||
break;
|
||||
prev = ch;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/* require at least one blank line */
|
||||
if (prev != '\n' || buf[i] != '\n')
|
||||
return 0;
|
||||
trailer_info_release(&info);
|
||||
|
||||
/* 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++;
|
||||
|
||||
found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
|
||||
if (found_rfc2822 && sob &&
|
||||
!strncmp(buf + i, sob->buf, sob->len))
|
||||
found_sob = k;
|
||||
|
||||
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;
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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
|
||||
'
|
||||
|
|
|
@ -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 &&
|
||||
|
|
305
trailer.c
305
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');
|
||||
|
||||
strbuf_release(&sb);
|
||||
|
||||
return lines;
|
||||
static const char *next_line(const char *str)
|
||||
{
|
||||
const char *nl = strchrnul(str, '\n');
|
||||
return nl + !!*nl;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 last line. If len is 0, return -1.
|
||||
*/
|
||||
static int find_patch_start(struct strbuf **lines, int count)
|
||||
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;
|
||||
|
||||
/* Get the start of the patch part if any */
|
||||
for (i = 0; i < count; i++) {
|
||||
if (starts_with(lines[i]->buf, "---"))
|
||||
return i;
|
||||
for (; i >= 0; i--) {
|
||||
if (buf[i] == '\n')
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 start of the patch or the length of str if there
|
||||
* is no patch in the message.
|
||||
*/
|
||||
static int find_trailer_start(struct strbuf **lines, int count)
|
||||
static int find_patch_start(const char *str)
|
||||
{
|
||||
int start, end_of_title, only_spaces = 1;
|
||||
const char *s;
|
||||
|
||||
for (s = str; *s; s = next_line(s)) {
|
||||
if (starts_with(s, "---"))
|
||||
return s - str;
|
||||
}
|
||||
|
||||
return s - str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the position of the first trailer line or len if there are no
|
||||
* trailers.
|
||||
*/
|
||||
static int find_trailer_start(const char *buf, size_t len)
|
||||
{
|
||||
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;
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Get the index of the end of the trailers */
|
||||
static int find_trailer_end(struct strbuf **lines, int patch_start)
|
||||
/* Return the position of the end of the trailers. */
|
||||
static int find_trailer_end(const char *buf, size_t len)
|
||||
{
|
||||
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 - ignore_non_trailer(buf, len);
|
||||
}
|
||||
|
||||
static int has_blank_line_before(struct strbuf **lines, int start)
|
||||
static int ends_with_blank_line(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;
|
||||
}
|
||||
|
||||
static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end)
|
||||
{
|
||||
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;
|
||||
int i;
|
||||
|
||||
/* Get the line count */
|
||||
while (lines[count])
|
||||
count++;
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
25
trailer.h
25
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 */
|
||||
|
|
Loading…
Reference in a new issue