sequencer.c: always separate "(cherry picked from" from commit body

Start treating the "(cherry picked from" line added by cherry-pick -x the same way that the s-o-b lines are treated. Namely, separate them from the main commit message body with an empty line. Introduce tests to test this functionality. Signed-off-by: Brandon Casey <bcasey@nvidia.com> Reviewed-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 b971e04f54e73a5a2af54ad62540273bb90ee7bb
2 files changed +118 -63
sequencer.c
+65 -63
@@ -20,6 +20,69 @@
20 const char sign_off_header[] = "Signed-off-by: ";
21 static const char cherry_picked_prefix[] = "(cherry picked from commit ";
22
23 +static int is_rfc2822_line(const char *buf, int len)
24 +{
25 + int i;
26 +
27 + for (i = 0; i < len; i++) {
28 + int ch = buf[i];
29 + if (ch == ':')
30 + return 1;
31 + if (!isalnum(ch) && ch != '-')
32 + break;
33 + }
34 +
35 + return 0;
36 +}
37 +
38 +static int is_cherry_picked_from_line(const char *buf, int len)
39 +{
40 + /*
41 + * We only care that it looks roughly like (cherry picked from ...)
42 + */
43 + return len > strlen(cherry_picked_prefix) + 1 &&
44 + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')';
45 +}
46 +
47 +static int has_conforming_footer(struct strbuf *sb, int ignore_footer)
48 +{
49 + char prev;
50 + int i, k;
51 + int len = sb->len - ignore_footer;
52 + const char *buf = sb->buf;
53 +
54 + /* footer must end with newline */
55 + if (!len || buf[len - 1] != '\n')
56 + return 0;
57 +
58 + prev = '\0';
59 + for (i = len - 1; i > 0; i--) {
60 + char ch = buf[i];
61 + if (prev == '\n' && ch == '\n') /* paragraph break */
62 + break;
63 + prev = ch;
64 + }
65 +
66 + /* require at least one blank line */
67 + if (prev != '\n' || buf[i] != '\n')
68 + return 0;
69 +
70 + /* advance to start of last paragraph */
71 + while (i < len - 1 && buf[i] == '\n')
72 + i++;
73 +
74 + for (; i < len; i = k) {
75 + for (k = i; k < len && buf[k] != '\n'; k++)
76 + ; /* do nothing */
77 + k++;
78 +
79 + if (!is_rfc2822_line(buf + i, k - i - 1) &&
80 + !is_cherry_picked_from_line(buf + i, k - i - 1))
81 + return 0;
82 + }
83 + return 1;
84 +}
85 +
86 static void remove_sequencer_state(void)
87 {
88 struct strbuf seq_dir = STRBUF_INIT;
@@ -493,6 +556,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
556 }
557
558 if (opts->record_origin) {
559 + if (!has_conforming_footer(&msgbuf, 0))
560 + strbuf_addch(&msgbuf, '\n');
561 strbuf_addstr(&msgbuf, cherry_picked_prefix);
562 strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
563 strbuf_addstr(&msgbuf, ")\n");
@@ -1018,69 +1083,6 @@ int sequencer_pick_revisions(struct replay_opts *opts)
1083 return pick_commits(todo_list, opts);
1084 }
1085
1021 -static int is_rfc2822_line(const char *buf, int len)
1022 -{
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--) {
1058 - char ch = buf[i];
1059 - if (prev == '\n' && ch == '\n') /* paragraph break */
1060 - break;
1061 - prev = ch;
1062 - }
1063 -
1064 - /* require at least one blank line */
1065 - if (prev != '\n' || buf[i] != '\n')
1066 - return 0;
1067 -
1068 - /* advance to start of last paragraph */
1069 - while (i < len - 1 && buf[i] == '\n')
1070 - i++;
1071 -
1072 - for (; i < len; i = k) {
1073 - for (k = i; k < len && buf[k] != '\n'; k++)
1074 - ; /* do nothing */
1075 - k++;
1076 -
1077 - if (!is_rfc2822_line(buf + i, k - i - 1) &&
1078 - !is_cherry_picked_from_line(buf + i, k - i - 1))
1079 - return 0;
1080 - }
1081 - return 1;
1082 -}
1083 -
1086 void append_signoff(struct strbuf *msgbuf, int ignore_footer)
1087 {
1088 struct strbuf sob = STRBUF_INIT;
t/t3511-cherry-pick-x.sh
+53
@@ -57,6 +57,19 @@ test_expect_success setup '
57 test_commit conflicting unrelated
58 '
59
60 +test_expect_success 'cherry-pick -x inserts blank line after one line subject' '
61 + pristine_detach initial &&
62 + sha1=`git rev-parse mesg-one-line^0` &&
63 + git cherry-pick -x mesg-one-line &&
64 + cat <<-EOF >expect &&
65 + $mesg_one_line
66 +
67 + (cherry picked from commit $sha1)
68 + EOF
69 + git log -1 --pretty=format:%B >actual &&
70 + test_cmp expect actual
71 +'
72 +
73 test_expect_success 'cherry-pick -s inserts blank line after one line subject' '
74 pristine_detach initial &&
75 git cherry-pick -s mesg-one-line &&
@@ -81,6 +94,19 @@ test_expect_failure 'cherry-pick -s inserts blank line after non-conforming foot
94 test_cmp expect actual
95 '
96
97 +test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' '
98 + pristine_detach initial &&
99 + sha1=`git rev-parse mesg-no-footer^0` &&
100 + git cherry-pick -x mesg-no-footer &&
101 + cat <<-EOF >expect &&
102 + $mesg_no_footer
103 +
104 + (cherry picked from commit $sha1)
105 + EOF
106 + git log -1 --pretty=format:%B >actual &&
107 + test_cmp expect actual
108 +'
109 +
110 test_expect_success 'cherry-pick -s inserts blank line when conforming footer not found' '
111 pristine_detach initial &&
112 git cherry-pick -s mesg-no-footer &&
@@ -93,6 +119,20 @@ test_expect_success 'cherry-pick -s inserts blank line when conforming footer no
119 test_cmp expect actual
120 '
121
122 +test_expect_success 'cherry-pick -x -s inserts blank line when conforming footer not found' '
123 + pristine_detach initial &&
124 + sha1=`git rev-parse mesg-no-footer^0` &&
125 + git cherry-pick -x -s mesg-no-footer &&
126 + cat <<-EOF >expect &&
127 + $mesg_no_footer
128 +
129 + (cherry picked from commit $sha1)
130 + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
131 + EOF
132 + git log -1 --pretty=format:%B >actual &&
133 + test_cmp expect actual
134 +'
135 +
136 test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committer' '
137 pristine_detach initial &&
138 git cherry-pick -s mesg-with-footer &&
@@ -163,4 +203,17 @@ test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part
203 test_cmp expect actual
204 '
205
206 +test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as part of footer' '
207 + pristine_detach initial &&
208 + sha1=`git rev-parse mesg-with-cherry-footer^0` &&
209 + git cherry-pick -x -s mesg-with-cherry-footer &&
210 + cat <<-EOF >expect &&
211 + $mesg_with_cherry_footer
212 + (cherry picked from commit $sha1)
213 + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
214 + EOF
215 + git log -1 --pretty=format:%B >actual &&
216 + test_cmp expect actual
217 +'
218 +
219 test_done