sequencer.c: recognize "(cherry picked from ..." as part of s-o-b footer

When 'cherry-pick -s' is used to append a signed-off-by line to a cherry picked commit, it does not currently detect the "(cherry picked from..." that may have been appended by a previous 'cherry-pick -x' as part of the s-o-b footer and it will insert a blank line before appending a new s-o-b. Let's detect "(cherry picked from...)" as part of the footer so that we will produce this: Signed-off-by: A U Thor <author@example.com> (cherry picked from da39a3ee5e6b4b0d3255bfef95601890afd80709) Signed-off-by: C O Mmitter <committer@example.com> instead of this: Signed-off-by: A U Thor <author@example.com> (cherry picked from da39a3ee5e6b4b0d3255bfef95601890afd80709) Signed-off-by: C O Mmitter <committer@example.com> [with improvements from Jonathan Nieder] Signed-off-by: Brandon Casey <bcasey@nvidia.com> Acked-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Casey committed Feb 12, 2013 at 02:17 UTC cd650a4eee148dadb0943f415e88c210f18e70df
2 files changed +92 -14
sequencer.c
+37 -14
@@ -18,6 +18,7 @@
18 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
19
20 const char sign_off_header[] = "Signed-off-by: ";
21 +static const char cherry_picked_prefix[] = "(cherry picked from commit ";
22
23 static void remove_sequencer_state(void)
24 {
@@ -492,7 +493,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
493 }
494
495 if (opts->record_origin) {
495 - strbuf_addstr(&msgbuf, "(cherry picked from commit ");
496 + strbuf_addstr(&msgbuf, cherry_picked_prefix);
497 strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
498 strbuf_addstr(&msgbuf, ")\n");
499 }
@@ -1017,16 +1018,44 @@ int sequencer_pick_revisions(struct replay_opts *opts)
1018 return pick_commits(todo_list, opts);
1019 }
1020
1020 -static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
1021 +static int is_rfc2822_line(const char *buf, int len)
1022 {
1022 - char ch, prev;
1023 - int i, j, k;
1023 + int i;
1024 +
1025 + for (i = 0; i < len; i++) {
1026 + int ch = buf[i];
1027 + if (ch == ':')
1028 + return 1;
1029 + if (!isalnum(ch) && ch != '-')
1030 + break;
1031 + }
1032 +
1033 + return 0;
1034 +}
1035 +
1036 +static int is_cherry_picked_from_line(const char *buf, int len)
1037 +{
1038 + /*
1039 + * We only care that it looks roughly like (cherry picked from ...)
1040 + */
1041 + return len > strlen(cherry_picked_prefix) + 1 &&
1042 + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')';
1043 +}
1044 +
1045 +static int has_conforming_footer(struct strbuf *sb, int ignore_footer)
1046 +{
1047 + char prev;
1048 + int i, k;
1049 int len = sb->len - ignore_footer;
1050 const char *buf = sb->buf;
1051
1052 + /* footer must end with newline */
1053 + if (!len || buf[len - 1] != '\n')
1054 + return 0;
1055 +
1056 prev = '\0';
1057 for (i = len - 1; i > 0; i--) {
1029 - ch = buf[i];
1058 + char ch = buf[i];
1059 if (prev == '\n' && ch == '\n') /* paragraph break */
1060 break;
1061 prev = ch;
@@ -1041,15 +1070,9 @@ static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
1070 ; /* do nothing */
1071 k++;
1072
1044 - for (j = 0; i + j < len; j++) {
1045 - ch = buf[i + j];
1046 - if (ch == ':')
1047 - break;
1048 - if (isalnum(ch) ||
1049 - (ch == '-'))
1050 - continue;
1073 + if (!is_rfc2822_line(buf + i, k - i - 1) &&
1074 + !is_cherry_picked_from_line(buf + i, k - i - 1))
1075 return 0;
1052 - }
1076 }
1077 return 1;
1078 }
@@ -1066,7 +1089,7 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer)
1089 for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--)
1090 ; /* do nothing */
1091 if (prefixcmp(msgbuf->buf + i, sob.buf)) {
1069 - if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer))
1092 + if (!i || !has_conforming_footer(msgbuf, ignore_footer))
1093 strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1);
1094 strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len);
1095 }
t/t3511-cherry-pick-x.sh
+55
@@ -32,6 +32,10 @@ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
32 mesg_with_footer_sob="$mesg_with_footer
33 Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
34
35 +mesg_with_cherry_footer="$mesg_with_footer_sob
36 +(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
37 +Tested-by: C.U. Thor <cuthor@example.com>"
38 +
39
40 test_expect_success setup '
41 git config advice.detachedhead false &&
@@ -47,6 +51,8 @@ test_expect_success setup '
51 test_commit "$mesg_with_footer" foo b mesg-with-footer &&
52 git reset --hard initial &&
53 test_commit "$mesg_with_footer_sob" foo b mesg-with-footer-sob &&
54 + git reset --hard initial &&
55 + test_commit "$mesg_with_cherry_footer" foo b mesg-with-cherry-footer &&
56 pristine_detach initial &&
57 test_commit conflicting unrelated
58 '
@@ -98,6 +104,19 @@ test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committe
104 test_cmp expect actual
105 '
106
107 +test_expect_success 'cherry-pick -x -s adds sob when last sob doesnt match committer' '
108 + pristine_detach initial &&
109 + sha1=`git rev-parse mesg-with-footer^0` &&
110 + git cherry-pick -x -s mesg-with-footer &&
111 + cat <<-EOF >expect &&
112 + $mesg_with_footer
113 + (cherry picked from commit $sha1)
114 + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
115 + EOF
116 + git log -1 --pretty=format:%B >actual &&
117 + test_cmp expect actual
118 +'
119 +
120 test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob' '
121 pristine_detach initial &&
122 git cherry-pick -s mesg-with-footer-sob &&
@@ -108,4 +127,40 @@ test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob'
127 test_cmp expect actual
128 '
129
130 +test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists for committer' '
131 + pristine_detach initial &&
132 + sha1=`git rev-parse mesg-with-footer-sob^0` &&
133 + git cherry-pick -x -s mesg-with-footer-sob &&
134 + cat <<-EOF >expect &&
135 + $mesg_with_footer_sob
136 + (cherry picked from commit $sha1)
137 + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
138 + EOF
139 + git log -1 --pretty=format:%B >actual &&
140 + test_cmp expect actual
141 +'
142 +
143 +test_expect_success 'cherry-pick -x treats "(cherry picked from..." line as part of footer' '
144 + pristine_detach initial &&
145 + sha1=`git rev-parse mesg-with-cherry-footer^0` &&
146 + git cherry-pick -x mesg-with-cherry-footer &&
147 + cat <<-EOF >expect &&
148 + $mesg_with_cherry_footer
149 + (cherry picked from commit $sha1)
150 + EOF
151 + git log -1 --pretty=format:%B >actual &&
152 + test_cmp expect actual
153 +'
154 +
155 +test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part of footer' '
156 + pristine_detach initial &&
157 + git cherry-pick -s mesg-with-cherry-footer &&
158 + cat <<-EOF >expect &&
159 + $mesg_with_cherry_footer
160 + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
161 + EOF
162 + git log -1 --pretty=format:%B >actual &&
163 + test_cmp expect actual
164 +'
165 +
166 test_done