[PATCH v2] cherry-pick: don't forget -s on failure
From: Miklos Vajna <hidden>
Date: 2016-06-15 22:54:44
Subsystem:
the rest · Maintainer:
Linus Torvalds
Possibly related (same subject, not in this thread)
- 2016-06-15 · Re: [PATCH v2] cherry-pick: don't forget -s on failure · Junio C Hamano <hidden>
- 2016-06-15 · Re: [PATCH v2] cherry-pick: don't forget -s on failure · Junio C Hamano <hidden>
In case 'git cherry-pick -s <commit>' failed, the user had to use 'git commit -s' (i.e. state the -s option again), which is easy to forget about. Instead, write the signed-off-by line early, so plain 'git commit' will have the same result. Also update 'git commit -s', so that in case there is already a relevant Signed-off-by line before the Conflicts: line, it won't add one more at the end of the message. Signed-off-by: Miklos Vajna <redacted> --- On Wed, Sep 12, 2012 at 03:45:10PM -0700, Junio C Hamano [off-list ref] wrote:
- The additional S-o-b should come immediately after the existing block of footers.
This was trivial to fix.
- And the last entry in the existing footer block is already mine, so there shouldn't have been a new and duplicated one added. I am not sure how reusable the moved function is without enhancements for your purpose. The logic to identify the footer needs to be enhanced so that an "end" pointer to point at the byte before the caller added "Conflicts: " can be given, and pretend as if it is the end of the buffer, unlike in the fresh commit case where it can consider the real end of the buffer as such.
Below is what I came up with. It simply ignores anything after the Conclicts: line, when checking for the last Signed-off-by line. An other thing: I forgot to run 'make test' for the initial patch, it seems t3510-cherry-pick-sequence.sh has 3 tests that basically ensures the opposite of what my patch does. Given that there are already testcases for the new behaviour, can they be just removed? For now, I just disabled them. builtin/commit.c | 79 +++++++++++--------------------------- sequencer.c | 65 ++++++++++++++++++++++++++++++++ sequencer.h | 4 ++ t/t3507-cherry-pick-conflict.sh | 14 +++++++ t/t3510-cherry-pick-sequence.sh | 6 +- t/test-lib-functions.sh | 8 +++- 6 files changed, 116 insertions(+), 60 deletions(-)
diff --git a/builtin/commit.c b/builtin/commit.c
index 778cf16..4d50484 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c@@ -28,6 +28,7 @@ #include "submodule.h" #include "gpg-interface.h" #include "column.h" +#include "sequencer.h" static const char * const builtin_commit_usage[] = { N_("git commit [options] [--] <filepattern>..."),
@@ -466,8 +467,6 @@ static int is_a_merge(const struct commit *current_head) return !!(current_head->parents && current_head->parents->next); } -static const char sign_off_header[] = "Signed-off-by: "; - static void export_one(const char *var, const char *s, const char *e, int hack) { struct strbuf buf = STRBUF_INIT;
@@ -552,47 +551,6 @@ static void determine_author_info(struct strbuf *author_ident) } } -static int ends_rfc2822_footer(struct strbuf *sb) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; -} - static char *cut_ident_timestamp_part(char *string) { char *ket = strrchr(string, '>');
@@ -717,21 +675,30 @@ static int prepare_to_commit(const char *index_file, const char *prefix, stripspace(&sb, 0); if (signoff) { - struct strbuf sob = STRBUF_INIT; - int i; + /* + * See if we have a Conflicts: block at the end. If yes, count + * its size, so we can ignore it. + */ + int ignore_footer = 0; + int i, eol, previous = 0; + const char *nl; - strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); - strbuf_addch(&sob, '\n'); - for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(sb.buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(&sb)) - strbuf_addch(&sb, '\n'); - strbuf_addbuf(&sb, &sob); + for (i = 0; i < sb.len; i++) { + nl = memchr(sb.buf + i, '\n', sb.len - i); + if (nl) + eol = nl - sb.buf; + else + eol = sb.len; + if (!prefixcmp(sb.buf + previous, "\nConflicts:\n")) { + ignore_footer = sb.len - previous; + break; + } + while (i < eol) + i++; + previous = eol; } - strbuf_release(&sob); + + append_signoff(&sb, ignore_footer); } if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
diff --git a/sequencer.c b/sequencer.c
index f86f116..e691b08 100644
--- a/sequencer.c
+++ b/sequencer.c@@ -17,6 +17,8 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +const char sign_off_header[] = "Signed-off-by: "; + void remove_sequencer_state(void) { struct strbuf seq_dir = STRBUF_INIT;
@@ -233,6 +235,9 @@ static int do_recursive_merge(struct commit *base, struct commit *next, die(_("%s: Unable to write new index file"), action_name(opts)); rollback_lock_file(&index_lock); + if (opts->signoff) + append_signoff(msgbuf, 0); + if (!clean) { int i; strbuf_addstr(msgbuf, "\nConflicts:\n");
@@ -1011,3 +1016,63 @@ int sequencer_pick_revisions(struct replay_opts *opts) save_opts(opts); return pick_commits(todo_list, opts); } + +static int ends_rfc2822_footer(struct strbuf *sb) +{ + int ch; + int hit = 0; + int i, j, k; + int len = sb->len; + int first = 1; + const char *buf = sb->buf; + + for (i = len - 1; i > 0; i--) { + if (hit && buf[i] == '\n') + break; + hit = (buf[i] == '\n'); + } + + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + if ((buf[k] == ' ' || buf[k] == '\t') && !first) + continue; + + first = 0; + + for (j = 0; i + j < len; j++) { + ch = buf[i + j]; + if (ch == ':') + break; + if (isalnum(ch) || + (ch == '-')) + continue; + return 0; + } + } + return 1; +} + +void append_signoff(struct strbuf *msgbuf, int ignore_footer) +{ + struct strbuf sob = STRBUF_INIT; + int i; + + strbuf_addstr(&sob, sign_off_header); + strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); + strbuf_addch(&sob, '\n'); + for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) + ; /* do nothing */ + if (prefixcmp(msgbuf->buf + i, sob.buf)) { + if (!i || !ends_rfc2822_footer(msgbuf)) + strbuf_addch(msgbuf, '\n'); + strbuf_addbuf(msgbuf, &sob); + } + strbuf_release(&sob); +}
diff --git a/sequencer.h b/sequencer.h
index d849420..60287b8 100644
--- a/sequencer.h
+++ b/sequencer.h@@ -49,4 +49,8 @@ extern void remove_sequencer_state(void); int sequencer_pick_revisions(struct replay_opts *opts); +extern const char sign_off_header[]; + +void append_signoff(struct strbuf *msgbuf, int ignore_footer); + #endif
diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh
index 0c81b3c..b67756f 100755
--- a/t/t3507-cherry-pick-conflict.sh
+++ b/t/t3507-cherry-pick-conflict.sh@@ -30,6 +30,7 @@ test_expect_success setup ' test_commit initial foo a && test_commit base foo b && test_commit picked foo c && + test_commit --signoff picked-signed foo d && git config advice.detachedhead false '
@@ -340,4 +341,17 @@ test_expect_success 'revert conflict, diff3 -m style' ' test_cmp expected actual ' +test_expect_success 'failed cherry-pick does not forget -s' ' + pristine_detach initial && + test_must_fail git cherry-pick -s picked && + test_i18ngrep -e "Signed-off-by" .git/MERGE_MSG +' + +test_expect_success 'commit after failed cherry-pick does not add duplicated -s' ' + pristine_detach initial && + test_must_fail git cherry-pick -s picked-signed && + git commit -a -s && + test $(git show -s |grep -c "Signed-off-by") = 1 +' + test_done
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index f4e6450..b5fb527 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh@@ -410,7 +410,7 @@ test_expect_success '--continue respects -x in first commit in multi-pick' ' grep "cherry picked from.*$picked" msg ' -test_expect_success '--signoff is not automatically propagated to resolved conflict' ' +test_expect_failure '--signoff is automatically propagated to resolved conflict' ' pristine_detach initial && test_expect_code 1 git cherry-pick --signoff base..anotherpick && echo "c" >foo &&
@@ -428,7 +428,7 @@ test_expect_success '--signoff is not automatically propagated to resolved confl grep "Signed-off-by:" anotherpick_msg ' -test_expect_success '--signoff dropped for implicit commit of resolution, multi-pick case' ' +test_expect_failure '--signoff dropped for implicit commit of resolution, multi-pick case' ' pristine_detach initial && test_must_fail git cherry-pick -s picked anotherpick && echo c >foo &&
@@ -441,7 +441,7 @@ test_expect_success '--signoff dropped for implicit commit of resolution, multi- ! grep Signed-off-by: msg ' -test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' ' +test_expect_failure 'sign-off needs to be reaffirmed after conflict resolution, single-pick case' ' pristine_detach initial && test_must_fail git cherry-pick -s picked && echo c >foo &&
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 9bc57d2..bbb7b7d 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh@@ -149,6 +149,12 @@ test_commit () { notick=yes shift fi && + signoff= && + if test "z$1" = "z--signoff" + then + signoff="$1" + shift + fi && file=${2:-"$1.t"} && echo "${3-$1}" > "$file" && git add "$file" &&
@@ -156,7 +162,7 @@ test_commit () { then test_tick fi && - git commit -m "$1" && + git commit $signoff -m "$1" && git tag "$1" }
--
1.7.7