git p4: notice Jobs lines in git commit messages

P4 has a feature called "jobs" that allows linking changes to a bug tracking system or other tasks. When submitting code, a job name can be specified to mark that this change is associated with a particular job. Teach git-p4 to find an optional "Jobs:" line in git commit messages and use them to make a Jobs section in the p4 change specifitation. Signed-off-by: Pete Wyckoff <pw@padd.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Pete Wyckoff committed Jul 4, 2012 at 09:34 UTC f19cb0a0e80e094970cfa854dc3da60c927830eb
2 files changed +195 -6
git-p4.py
+40 -6
@@ -854,9 +854,34 @@ class P4Submit(Command, P4UserMap):
854 if len(p4CmdList("opened ...")) > 0:
855 die("You have files opened with perforce! Close them before starting the sync.")
856
857 - # replaces everything between 'Description:' and the next P4 submit template field with the
858 - # commit message
859 - def prepareLogMessage(self, template, message):
857 + def separate_jobs_from_description(self, message):
858 + """Extract and return a possible Jobs field in the commit
859 + message. It goes into a separate section in the p4 change
860 + specification.
861 +
862 + A jobs line starts with "Jobs:" and looks like a new field
863 + in a form. Values are white-space separated on the same
864 + line or on following lines that start with a tab.
865 +
866 + This does not parse and extract the full git commit message
867 + like a p4 form. It just sees the Jobs: line as a marker
868 + to pass everything from then on directly into the p4 form,
869 + but outside the description section.
870 +
871 + Return a tuple (stripped log message, jobs string)."""
872 +
873 + m = re.search(r'^Jobs:', message, re.MULTILINE)
874 + if m is None:
875 + return (message, None)
876 +
877 + jobtext = message[m.start():]
878 + stripped_message = message[:m.start()].rstrip()
879 + return (stripped_message, jobtext)
880 +
881 + def prepareLogMessage(self, template, message, jobs):
882 + """Edits the template returned from "p4 change -o" to insert
883 + the message in the Description field, and the jobs text in
884 + the Jobs field."""
885 result = ""
886
887 inDescriptionSection = False
@@ -869,6 +894,9 @@ class P4Submit(Command, P4UserMap):
894 if inDescriptionSection:
895 if line.startswith("Files:") or line.startswith("Jobs:"):
896 inDescriptionSection = False
897 + # insert Jobs section
898 + if jobs:
899 + result += jobs + "\n"
900 else:
901 continue
902 else:
@@ -980,7 +1008,13 @@ class P4Submit(Command, P4UserMap):
1008 return 0
1009
1010 def prepareSubmitTemplate(self):
983 - # remove lines in the Files section that show changes to files outside the depot path we're committing into
1011 + """Run "p4 change -o" to grab a change specification template.
1012 + This does not use "p4 -G", as it is nice to keep the submission
1013 + template in original order, since a human might edit it.
1014 +
1015 + Remove lines in the Files section that show changes to files
1016 + outside the depot path we're committing into."""
1017 +
1018 template = ""
1019 inFilesSection = False
1020 for line in p4_read_pipe_lines(['change', '-o']):
@@ -1205,10 +1239,10 @@ class P4Submit(Command, P4UserMap):
1239
1240 logMessage = extractLogMessageFromGitCommit(id)
1241 logMessage = logMessage.strip()
1242 + (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
1243
1244 template = self.prepareSubmitTemplate()
1210 -
1211 - submitTemplate = self.prepareLogMessage(template, logMessage)
1245 + submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
1246
1247 if self.preserveUser:
1248 submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
t/t9807-git-p4-submit.sh
+155
@@ -182,6 +182,161 @@ test_expect_success 'submit rename' '
182 )
183 '
184
185 +#
186 +# Converting git commit message to p4 change description, including
187 +# parsing out the optional Jobs: line.
188 +#
189 +test_expect_success 'simple one-line description' '
190 + test_when_finished cleanup_git &&
191 + git p4 clone --dest="$git" //depot &&
192 + (
193 + cd "$git" &&
194 + echo desc2 >desc2 &&
195 + git add desc2 &&
196 + cat >msg <<-EOF &&
197 + One-line description line for desc2.
198 + EOF
199 + git commit -F - <msg &&
200 + git config git-p4.skipSubmitEdit true &&
201 + git p4 submit &&
202 + change=$(p4 -G changes -m 1 //depot/... | \
203 + marshal_dump change) &&
204 + # marshal_dump always adds a newline
205 + p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
206 + test_cmp msg pmsg
207 + )
208 +'
209 +
210 +test_expect_success 'description with odd formatting' '
211 + test_when_finished cleanup_git &&
212 + git p4 clone --dest="$git" //depot &&
213 + (
214 + cd "$git" &&
215 + echo desc3 >desc3 &&
216 + git add desc3 &&
217 + (
218 + printf "subject line\n\n\tExtra tab\nline.\n\n" &&
219 + printf "Description:\n\tBogus description marker\n\n" &&
220 + # git commit eats trailing newlines; only use one
221 + printf "Files:\n\tBogus descs marker\n"
222 + ) >msg &&
223 + git commit -F - <msg &&
224 + git config git-p4.skipSubmitEdit true &&
225 + git p4 submit &&
226 + change=$(p4 -G changes -m 1 //depot/... | \
227 + marshal_dump change) &&
228 + # marshal_dump always adds a newline
229 + p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
230 + test_cmp msg pmsg
231 + )
232 +'
233 +
234 +make_job() {
235 + name="$1" &&
236 + tab="$(printf \\t)" &&
237 + p4 job -o | \
238 + sed -e "/^Job:/s/.*/Job: $name/" \
239 + -e "/^Description/{ n; s/.*/$tab job text/; }" | \
240 + p4 job -i
241 +}
242 +
243 +test_expect_success 'description with Jobs section at end' '
244 + test_when_finished cleanup_git &&
245 + git p4 clone --dest="$git" //depot &&
246 + (
247 + cd "$git" &&
248 + echo desc4 >desc4 &&
249 + git add desc4 &&
250 + echo 6060842 >jobname &&
251 + (
252 + printf "subject line\n\n\tExtra tab\nline.\n\n" &&
253 + printf "Files:\n\tBogus files marker\n" &&
254 + printf "Junk: 3164175\n" &&
255 + printf "Jobs: $(cat jobname)\n"
256 + ) >msg &&
257 + git commit -F - <msg &&
258 + git config git-p4.skipSubmitEdit true &&
259 + # build a job
260 + make_job $(cat jobname) &&
261 + git p4 submit &&
262 + change=$(p4 -G changes -m 1 //depot/... | \
263 + marshal_dump change) &&
264 + # marshal_dump always adds a newline
265 + p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
266 + # make sure Jobs line and all following is gone
267 + sed "/^Jobs:/,\$d" msg >jmsg &&
268 + test_cmp jmsg pmsg &&
269 + # make sure p4 knows about job
270 + p4 -G describe $change | marshal_dump job0 >job0 &&
271 + test_cmp jobname job0
272 + )
273 +'
274 +
275 +test_expect_success 'description with Jobs and values on separate lines' '
276 + test_when_finished cleanup_git &&
277 + git p4 clone --dest="$git" //depot &&
278 + (
279 + cd "$git" &&
280 + echo desc5 >desc5 &&
281 + git add desc5 &&
282 + echo PROJ-6060842 >jobname1 &&
283 + echo PROJ-6060847 >jobname2 &&
284 + (
285 + printf "subject line\n\n\tExtra tab\nline.\n\n" &&
286 + printf "Files:\n\tBogus files marker\n" &&
287 + printf "Junk: 3164175\n" &&
288 + printf "Jobs:\n" &&
289 + printf "\t$(cat jobname1)\n" &&
290 + printf "\t$(cat jobname2)\n"
291 + ) >msg &&
292 + git commit -F - <msg &&
293 + git config git-p4.skipSubmitEdit true &&
294 + # build two jobs
295 + make_job $(cat jobname1) &&
296 + make_job $(cat jobname2) &&
297 + git p4 submit &&
298 + change=$(p4 -G changes -m 1 //depot/... | \
299 + marshal_dump change) &&
300 + # marshal_dump always adds a newline
301 + p4 -G describe $change | marshal_dump desc | sed \$d >pmsg &&
302 + # make sure Jobs line and all following is gone
303 + sed "/^Jobs:/,\$d" msg >jmsg &&
304 + test_cmp jmsg pmsg &&
305 + # make sure p4 knows about the two jobs
306 + p4 -G describe $change >change &&
307 + (
308 + marshal_dump job0 <change &&
309 + marshal_dump job1 <change
310 + ) | sort >jobs &&
311 + cat jobname1 jobname2 | sort >expected &&
312 + test_cmp expected jobs
313 + )
314 +'
315 +
316 +test_expect_success 'description with Jobs section and bogus following text' '
317 + test_when_finished cleanup_git &&
318 + git p4 clone --dest="$git" //depot &&
319 + (
320 + cd "$git" &&
321 + echo desc6 >desc6 &&
322 + git add desc6 &&
323 + echo 6060843 >jobname &&
324 + (
325 + printf "subject line\n\n\tExtra tab\nline.\n\n" &&
326 + printf "Files:\n\tBogus files marker\n" &&
327 + printf "Junk: 3164175\n" &&
328 + printf "Jobs: $(cat jobname)\n" &&
329 + printf "MoreJunk: 3711\n"
330 + ) >msg &&
331 + git commit -F - <msg &&
332 + git config git-p4.skipSubmitEdit true &&
333 + # build a job
334 + make_job $(cat jobname) &&
335 + test_must_fail git p4 submit 2>err &&
336 + test_i18ngrep "Unknown field name" err
337 + )
338 +'
339 +
340 test_expect_success 'kill p4d' '
341 kill_p4d
342 '