259 return p.finished() |
259 return p.finished() |
260 |
260 |
261 def filterpatch(ui, headers): |
261 def filterpatch(ui, headers): |
262 """Interactively filter patch chunks into applied-only chunks""" |
262 """Interactively filter patch chunks into applied-only chunks""" |
263 |
263 |
264 def prompt(skipfile, skipall, query): |
264 def prompt(skipfile, skipall, query, chunk): |
265 """prompt query, and process base inputs |
265 """prompt query, and process base inputs |
266 |
266 |
267 - y/n for the rest of file |
267 - y/n for the rest of file |
268 - y/n for the rest |
268 - y/n for the rest |
269 - ? (help) |
269 - ? (help) |
270 - q (quit) |
270 - q (quit) |
271 |
271 |
272 Return True/False and possibly updated skipfile and skipall. |
272 Return True/False and possibly updated skipfile and skipall. |
273 """ |
273 """ |
|
274 newpatches = None |
274 if skipall is not None: |
275 if skipall is not None: |
275 return skipall, skipfile, skipall |
276 return skipall, skipfile, skipall, newpatches |
276 if skipfile is not None: |
277 if skipfile is not None: |
277 return skipfile, skipfile, skipall |
278 return skipfile, skipfile, skipall, newpatches |
278 while True: |
279 while True: |
279 resps = _('[Ynsfdaq?]') |
280 resps = _('[Ynesfdaq?]') |
280 choices = (_('&Yes, record this change'), |
281 choices = (_('&Yes, record this change'), |
281 _('&No, skip this change'), |
282 _('&No, skip this change'), |
|
283 _('&Edit the change manually'), |
282 _('&Skip remaining changes to this file'), |
284 _('&Skip remaining changes to this file'), |
283 _('Record remaining changes to this &file'), |
285 _('Record remaining changes to this &file'), |
284 _('&Done, skip remaining changes and files'), |
286 _('&Done, skip remaining changes and files'), |
285 _('Record &all changes to all remaining files'), |
287 _('Record &all changes to all remaining files'), |
286 _('&Quit, recording no changes'), |
288 _('&Quit, recording no changes'), |
287 _('&?')) |
289 _('&?')) |
288 r = ui.promptchoice("%s %s" % (query, resps), choices) |
290 r = ui.promptchoice("%s %s" % (query, resps), choices) |
289 ui.write("\n") |
291 ui.write("\n") |
290 if r == 7: # ? |
292 if r == 8: # ? |
291 doc = gettext(record.__doc__) |
293 doc = gettext(record.__doc__) |
292 c = doc.find('::') + 2 |
294 c = doc.find('::') + 2 |
293 for l in doc[c:].splitlines(): |
295 for l in doc[c:].splitlines(): |
294 if l.startswith(' '): |
296 if l.startswith(' '): |
295 ui.write(l.strip(), '\n') |
297 ui.write(l.strip(), '\n') |
296 continue |
298 continue |
297 elif r == 0: # yes |
299 elif r == 0: # yes |
298 ret = True |
300 ret = True |
299 elif r == 1: # no |
301 elif r == 1: # no |
300 ret = False |
302 ret = False |
301 elif r == 2: # Skip |
303 elif r == 2: # Edit patch |
|
304 if chunk is None: |
|
305 ui.write(_('cannot edit patch for whole file')) |
|
306 ui.write("\n") |
|
307 continue |
|
308 if chunk.header.binary(): |
|
309 ui.write(_('cannot edit patch for binary file')) |
|
310 ui.write("\n") |
|
311 continue |
|
312 # Patch comment based on the Git one (based on comment at end of |
|
313 # http://mercurial.selenic.com/wiki/RecordExtension) |
|
314 phelp = '---' + _(""" |
|
315 To remove '-' lines, make them ' ' lines (context). |
|
316 To remove '+' lines, delete them. |
|
317 Lines starting with # will be removed from the patch. |
|
318 |
|
319 If the patch applies cleanly, the edited hunk will immediately be |
|
320 added to the record list. If it does not apply cleanly, a rejects |
|
321 file will be generated: you can use that when you try again. If |
|
322 all lines of the hunk are removed, then the edit is aborted and |
|
323 the hunk is left unchanged. |
|
324 """) |
|
325 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-", |
|
326 suffix=".diff", text=True) |
|
327 try: |
|
328 # Write the initial patch |
|
329 f = os.fdopen(patchfd, "w") |
|
330 chunk.header.write(f) |
|
331 chunk.write(f) |
|
332 f.write('\n'.join(['# ' + i for i in phelp.splitlines()])) |
|
333 f.close() |
|
334 # Start the editor and wait for it to complete |
|
335 editor = ui.geteditor() |
|
336 util.system("%s \"%s\"" % (editor, patchfn), |
|
337 environ={'HGUSER': ui.username()}, |
|
338 onerr=util.Abort, errprefix=_("edit failed"), |
|
339 out=ui.fout) |
|
340 # Remove comment lines |
|
341 patchfp = open(patchfn) |
|
342 ncpatchfp = cStringIO.StringIO() |
|
343 for line in patchfp: |
|
344 if not line.startswith('#'): |
|
345 ncpatchfp.write(line) |
|
346 patchfp.close() |
|
347 ncpatchfp.seek(0) |
|
348 newpatches = parsepatch(ncpatchfp) |
|
349 finally: |
|
350 os.unlink(patchfn) |
|
351 del ncpatchfp |
|
352 # Signal that the chunk shouldn't be applied as-is, but |
|
353 # provide the new patch to be used instead. |
|
354 ret = False |
|
355 elif r == 3: # Skip |
302 ret = skipfile = False |
356 ret = skipfile = False |
303 elif r == 3: # file (Record remaining) |
357 elif r == 4: # file (Record remaining) |
304 ret = skipfile = True |
358 ret = skipfile = True |
305 elif r == 4: # done, skip remaining |
359 elif r == 5: # done, skip remaining |
306 ret = skipall = False |
360 ret = skipall = False |
307 elif r == 5: # all |
361 elif r == 6: # all |
308 ret = skipall = True |
362 ret = skipall = True |
309 elif r == 6: # quit |
363 elif r == 7: # quit |
310 raise util.Abort(_('user quit')) |
364 raise util.Abort(_('user quit')) |
311 return ret, skipfile, skipall |
365 return ret, skipfile, skipall, newpatches |
312 |
366 |
313 seen = set() |
367 seen = set() |
314 applied = {} # 'filename' -> [] of chunks |
368 applied = {} # 'filename' -> [] of chunks |
315 skipfile, skipall = None, None |
369 skipfile, skipall = None, None |
316 pos, total = 1, sum(len(h.hunks) for h in headers) |
370 pos, total = 1, sum(len(h.hunks) for h in headers) |
340 msg = _('record this change to %r?') % chunk.filename() |
394 msg = _('record this change to %r?') % chunk.filename() |
341 else: |
395 else: |
342 idx = pos - len(h.hunks) + i |
396 idx = pos - len(h.hunks) + i |
343 msg = _('record change %d/%d to %r?') % (idx, total, |
397 msg = _('record change %d/%d to %r?') % (idx, total, |
344 chunk.filename()) |
398 chunk.filename()) |
345 r, skipfile, skipall = prompt(skipfile, skipall, msg) |
399 r, skipfile, skipall, newpatches = prompt(skipfile, |
|
400 skipall, msg, chunk) |
346 if r: |
401 if r: |
347 if fixoffset: |
402 if fixoffset: |
348 chunk = copy.copy(chunk) |
403 chunk = copy.copy(chunk) |
349 chunk.toline += fixoffset |
404 chunk.toline += fixoffset |
350 applied[chunk.filename()].append(chunk) |
405 applied[chunk.filename()].append(chunk) |
|
406 elif newpatches is not None: |
|
407 for newpatch in newpatches: |
|
408 for newhunk in newpatch.hunks: |
|
409 if fixoffset: |
|
410 newhunk.toline += fixoffset |
|
411 applied[newhunk.filename()].append(newhunk) |
351 else: |
412 else: |
352 fixoffset += chunk.removed - chunk.added |
413 fixoffset += chunk.removed - chunk.added |
353 return sum([h for h in applied.itervalues() |
414 return sum([h for h in applied.itervalues() |
354 if h[0].special() or len(h) > 1], []) |
415 if h[0].special() or len(h) > 1], []) |
355 |
416 |