##// END OF EJS Templates
record: use absolute path instead of os.chdir...
Pierre-Yves David -
r20334:205599e3 stable
parent child Browse files
Show More
@@ -1,669 +1,665 b''
1 1 # record.py
2 2 #
3 3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''commands to interactively select changes for commit/qrefresh'''
9 9
10 10 from mercurial.i18n import _
11 11 from mercurial import cmdutil, commands, extensions, hg, patch
12 12 from mercurial import util
13 13 import copy, cStringIO, errno, os, re, shutil, tempfile
14 14
15 15 cmdtable = {}
16 16 command = cmdutil.command(cmdtable)
17 17 testedwith = 'internal'
18 18
19 19 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
20 20
21 21 def scanpatch(fp):
22 22 """like patch.iterhunks, but yield different events
23 23
24 24 - ('file', [header_lines + fromfile + tofile])
25 25 - ('context', [context_lines])
26 26 - ('hunk', [hunk_lines])
27 27 - ('range', (-start,len, +start,len, proc))
28 28 """
29 29 lr = patch.linereader(fp)
30 30
31 31 def scanwhile(first, p):
32 32 """scan lr while predicate holds"""
33 33 lines = [first]
34 34 while True:
35 35 line = lr.readline()
36 36 if not line:
37 37 break
38 38 if p(line):
39 39 lines.append(line)
40 40 else:
41 41 lr.push(line)
42 42 break
43 43 return lines
44 44
45 45 while True:
46 46 line = lr.readline()
47 47 if not line:
48 48 break
49 49 if line.startswith('diff --git a/') or line.startswith('diff -r '):
50 50 def notheader(line):
51 51 s = line.split(None, 1)
52 52 return not s or s[0] not in ('---', 'diff')
53 53 header = scanwhile(line, notheader)
54 54 fromfile = lr.readline()
55 55 if fromfile.startswith('---'):
56 56 tofile = lr.readline()
57 57 header += [fromfile, tofile]
58 58 else:
59 59 lr.push(fromfile)
60 60 yield 'file', header
61 61 elif line[0] == ' ':
62 62 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
63 63 elif line[0] in '-+':
64 64 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
65 65 else:
66 66 m = lines_re.match(line)
67 67 if m:
68 68 yield 'range', m.groups()
69 69 else:
70 70 yield 'other', line
71 71
72 72 class header(object):
73 73 """patch header
74 74
75 75 XXX shouldn't we move this to mercurial/patch.py ?
76 76 """
77 77 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
78 78 diff_re = re.compile('diff -r .* (.*)$')
79 79 allhunks_re = re.compile('(?:index|new file|deleted file) ')
80 80 pretty_re = re.compile('(?:new file|deleted file) ')
81 81 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
82 82
83 83 def __init__(self, header):
84 84 self.header = header
85 85 self.hunks = []
86 86
87 87 def binary(self):
88 88 return util.any(h.startswith('index ') for h in self.header)
89 89
90 90 def pretty(self, fp):
91 91 for h in self.header:
92 92 if h.startswith('index '):
93 93 fp.write(_('this modifies a binary file (all or nothing)\n'))
94 94 break
95 95 if self.pretty_re.match(h):
96 96 fp.write(h)
97 97 if self.binary():
98 98 fp.write(_('this is a binary file\n'))
99 99 break
100 100 if h.startswith('---'):
101 101 fp.write(_('%d hunks, %d lines changed\n') %
102 102 (len(self.hunks),
103 103 sum([max(h.added, h.removed) for h in self.hunks])))
104 104 break
105 105 fp.write(h)
106 106
107 107 def write(self, fp):
108 108 fp.write(''.join(self.header))
109 109
110 110 def allhunks(self):
111 111 return util.any(self.allhunks_re.match(h) for h in self.header)
112 112
113 113 def files(self):
114 114 match = self.diffgit_re.match(self.header[0])
115 115 if match:
116 116 fromfile, tofile = match.groups()
117 117 if fromfile == tofile:
118 118 return [fromfile]
119 119 return [fromfile, tofile]
120 120 else:
121 121 return self.diff_re.match(self.header[0]).groups()
122 122
123 123 def filename(self):
124 124 return self.files()[-1]
125 125
126 126 def __repr__(self):
127 127 return '<header %s>' % (' '.join(map(repr, self.files())))
128 128
129 129 def special(self):
130 130 return util.any(self.special_re.match(h) for h in self.header)
131 131
132 132 def countchanges(hunk):
133 133 """hunk -> (n+,n-)"""
134 134 add = len([h for h in hunk if h[0] == '+'])
135 135 rem = len([h for h in hunk if h[0] == '-'])
136 136 return add, rem
137 137
138 138 class hunk(object):
139 139 """patch hunk
140 140
141 141 XXX shouldn't we merge this with patch.hunk ?
142 142 """
143 143 maxcontext = 3
144 144
145 145 def __init__(self, header, fromline, toline, proc, before, hunk, after):
146 146 def trimcontext(number, lines):
147 147 delta = len(lines) - self.maxcontext
148 148 if False and delta > 0:
149 149 return number + delta, lines[:self.maxcontext]
150 150 return number, lines
151 151
152 152 self.header = header
153 153 self.fromline, self.before = trimcontext(fromline, before)
154 154 self.toline, self.after = trimcontext(toline, after)
155 155 self.proc = proc
156 156 self.hunk = hunk
157 157 self.added, self.removed = countchanges(self.hunk)
158 158
159 159 def write(self, fp):
160 160 delta = len(self.before) + len(self.after)
161 161 if self.after and self.after[-1] == '\\ No newline at end of file\n':
162 162 delta -= 1
163 163 fromlen = delta + self.removed
164 164 tolen = delta + self.added
165 165 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
166 166 (self.fromline, fromlen, self.toline, tolen,
167 167 self.proc and (' ' + self.proc)))
168 168 fp.write(''.join(self.before + self.hunk + self.after))
169 169
170 170 pretty = write
171 171
172 172 def filename(self):
173 173 return self.header.filename()
174 174
175 175 def __repr__(self):
176 176 return '<hunk %r@%d>' % (self.filename(), self.fromline)
177 177
178 178 def parsepatch(fp):
179 179 """patch -> [] of headers -> [] of hunks """
180 180 class parser(object):
181 181 """patch parsing state machine"""
182 182 def __init__(self):
183 183 self.fromline = 0
184 184 self.toline = 0
185 185 self.proc = ''
186 186 self.header = None
187 187 self.context = []
188 188 self.before = []
189 189 self.hunk = []
190 190 self.headers = []
191 191
192 192 def addrange(self, limits):
193 193 fromstart, fromend, tostart, toend, proc = limits
194 194 self.fromline = int(fromstart)
195 195 self.toline = int(tostart)
196 196 self.proc = proc
197 197
198 198 def addcontext(self, context):
199 199 if self.hunk:
200 200 h = hunk(self.header, self.fromline, self.toline, self.proc,
201 201 self.before, self.hunk, context)
202 202 self.header.hunks.append(h)
203 203 self.fromline += len(self.before) + h.removed
204 204 self.toline += len(self.before) + h.added
205 205 self.before = []
206 206 self.hunk = []
207 207 self.proc = ''
208 208 self.context = context
209 209
210 210 def addhunk(self, hunk):
211 211 if self.context:
212 212 self.before = self.context
213 213 self.context = []
214 214 self.hunk = hunk
215 215
216 216 def newfile(self, hdr):
217 217 self.addcontext([])
218 218 h = header(hdr)
219 219 self.headers.append(h)
220 220 self.header = h
221 221
222 222 def addother(self, line):
223 223 pass # 'other' lines are ignored
224 224
225 225 def finished(self):
226 226 self.addcontext([])
227 227 return self.headers
228 228
229 229 transitions = {
230 230 'file': {'context': addcontext,
231 231 'file': newfile,
232 232 'hunk': addhunk,
233 233 'range': addrange},
234 234 'context': {'file': newfile,
235 235 'hunk': addhunk,
236 236 'range': addrange,
237 237 'other': addother},
238 238 'hunk': {'context': addcontext,
239 239 'file': newfile,
240 240 'range': addrange},
241 241 'range': {'context': addcontext,
242 242 'hunk': addhunk},
243 243 'other': {'other': addother},
244 244 }
245 245
246 246 p = parser()
247 247
248 248 state = 'context'
249 249 for newstate, data in scanpatch(fp):
250 250 try:
251 251 p.transitions[state][newstate](p, data)
252 252 except KeyError:
253 253 raise patch.PatchError('unhandled transition: %s -> %s' %
254 254 (state, newstate))
255 255 state = newstate
256 256 return p.finished()
257 257
258 258 def filterpatch(ui, headers):
259 259 """Interactively filter patch chunks into applied-only chunks"""
260 260
261 261 def prompt(skipfile, skipall, query, chunk):
262 262 """prompt query, and process base inputs
263 263
264 264 - y/n for the rest of file
265 265 - y/n for the rest
266 266 - ? (help)
267 267 - q (quit)
268 268
269 269 Return True/False and possibly updated skipfile and skipall.
270 270 """
271 271 newpatches = None
272 272 if skipall is not None:
273 273 return skipall, skipfile, skipall, newpatches
274 274 if skipfile is not None:
275 275 return skipfile, skipfile, skipall, newpatches
276 276 while True:
277 277 resps = _('[Ynesfdaq?]'
278 278 '$$ &Yes, record this change'
279 279 '$$ &No, skip this change'
280 280 '$$ &Edit this change manually'
281 281 '$$ &Skip remaining changes to this file'
282 282 '$$ Record remaining changes to this &file'
283 283 '$$ &Done, skip remaining changes and files'
284 284 '$$ Record &all changes to all remaining files'
285 285 '$$ &Quit, recording no changes'
286 286 '$$ &? (display help)')
287 287 r = ui.promptchoice("%s %s" % (query, resps))
288 288 ui.write("\n")
289 289 if r == 8: # ?
290 290 for c, t in ui.extractchoices(resps)[1]:
291 291 ui.write('%s - %s\n' % (c, t.lower()))
292 292 continue
293 293 elif r == 0: # yes
294 294 ret = True
295 295 elif r == 1: # no
296 296 ret = False
297 297 elif r == 2: # Edit patch
298 298 if chunk is None:
299 299 ui.write(_('cannot edit patch for whole file'))
300 300 ui.write("\n")
301 301 continue
302 302 if chunk.header.binary():
303 303 ui.write(_('cannot edit patch for binary file'))
304 304 ui.write("\n")
305 305 continue
306 306 # Patch comment based on the Git one (based on comment at end of
307 307 # http://mercurial.selenic.com/wiki/RecordExtension)
308 308 phelp = '---' + _("""
309 309 To remove '-' lines, make them ' ' lines (context).
310 310 To remove '+' lines, delete them.
311 311 Lines starting with # will be removed from the patch.
312 312
313 313 If the patch applies cleanly, the edited hunk will immediately be
314 314 added to the record list. If it does not apply cleanly, a rejects
315 315 file will be generated: you can use that when you try again. If
316 316 all lines of the hunk are removed, then the edit is aborted and
317 317 the hunk is left unchanged.
318 318 """)
319 319 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
320 320 suffix=".diff", text=True)
321 321 ncpatchfp = None
322 322 try:
323 323 # Write the initial patch
324 324 f = os.fdopen(patchfd, "w")
325 325 chunk.header.write(f)
326 326 chunk.write(f)
327 327 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
328 328 f.close()
329 329 # Start the editor and wait for it to complete
330 330 editor = ui.geteditor()
331 331 util.system("%s \"%s\"" % (editor, patchfn),
332 332 environ={'HGUSER': ui.username()},
333 333 onerr=util.Abort, errprefix=_("edit failed"),
334 334 out=ui.fout)
335 335 # Remove comment lines
336 336 patchfp = open(patchfn)
337 337 ncpatchfp = cStringIO.StringIO()
338 338 for line in patchfp:
339 339 if not line.startswith('#'):
340 340 ncpatchfp.write(line)
341 341 patchfp.close()
342 342 ncpatchfp.seek(0)
343 343 newpatches = parsepatch(ncpatchfp)
344 344 finally:
345 345 os.unlink(patchfn)
346 346 del ncpatchfp
347 347 # Signal that the chunk shouldn't be applied as-is, but
348 348 # provide the new patch to be used instead.
349 349 ret = False
350 350 elif r == 3: # Skip
351 351 ret = skipfile = False
352 352 elif r == 4: # file (Record remaining)
353 353 ret = skipfile = True
354 354 elif r == 5: # done, skip remaining
355 355 ret = skipall = False
356 356 elif r == 6: # all
357 357 ret = skipall = True
358 358 elif r == 7: # quit
359 359 raise util.Abort(_('user quit'))
360 360 return ret, skipfile, skipall, newpatches
361 361
362 362 seen = set()
363 363 applied = {} # 'filename' -> [] of chunks
364 364 skipfile, skipall = None, None
365 365 pos, total = 1, sum(len(h.hunks) for h in headers)
366 366 for h in headers:
367 367 pos += len(h.hunks)
368 368 skipfile = None
369 369 fixoffset = 0
370 370 hdr = ''.join(h.header)
371 371 if hdr in seen:
372 372 continue
373 373 seen.add(hdr)
374 374 if skipall is None:
375 375 h.pretty(ui)
376 376 msg = (_('examine changes to %s?') %
377 377 _(' and ').join("'%s'" % f for f in h.files()))
378 378 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
379 379 if not r:
380 380 continue
381 381 applied[h.filename()] = [h]
382 382 if h.allhunks():
383 383 applied[h.filename()] += h.hunks
384 384 continue
385 385 for i, chunk in enumerate(h.hunks):
386 386 if skipfile is None and skipall is None:
387 387 chunk.pretty(ui)
388 388 if total == 1:
389 389 msg = _("record this change to '%s'?") % chunk.filename()
390 390 else:
391 391 idx = pos - len(h.hunks) + i
392 392 msg = _("record change %d/%d to '%s'?") % (idx, total,
393 393 chunk.filename())
394 394 r, skipfile, skipall, newpatches = prompt(skipfile,
395 395 skipall, msg, chunk)
396 396 if r:
397 397 if fixoffset:
398 398 chunk = copy.copy(chunk)
399 399 chunk.toline += fixoffset
400 400 applied[chunk.filename()].append(chunk)
401 401 elif newpatches is not None:
402 402 for newpatch in newpatches:
403 403 for newhunk in newpatch.hunks:
404 404 if fixoffset:
405 405 newhunk.toline += fixoffset
406 406 applied[newhunk.filename()].append(newhunk)
407 407 else:
408 408 fixoffset += chunk.removed - chunk.added
409 409 return sum([h for h in applied.itervalues()
410 410 if h[0].special() or len(h) > 1], [])
411 411
412 412 @command("record",
413 413 # same options as commit + white space diff options
414 414 commands.table['^commit|ci'][1][:] + commands.diffwsopts,
415 415 _('hg record [OPTION]... [FILE]...'))
416 416 def record(ui, repo, *pats, **opts):
417 417 '''interactively select changes to commit
418 418
419 419 If a list of files is omitted, all changes reported by :hg:`status`
420 420 will be candidates for recording.
421 421
422 422 See :hg:`help dates` for a list of formats valid for -d/--date.
423 423
424 424 You will be prompted for whether to record changes to each
425 425 modified file, and for files with multiple changes, for each
426 426 change to use. For each query, the following responses are
427 427 possible::
428 428
429 429 y - record this change
430 430 n - skip this change
431 431 e - edit this change manually
432 432
433 433 s - skip remaining changes to this file
434 434 f - record remaining changes to this file
435 435
436 436 d - done, skip remaining changes and files
437 437 a - record all changes to all remaining files
438 438 q - quit, recording no changes
439 439
440 440 ? - display help
441 441
442 442 This command is not available when committing a merge.'''
443 443
444 444 dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
445 445
446 446 def qrefresh(origfn, ui, repo, *pats, **opts):
447 447 if not opts['interactive']:
448 448 return origfn(ui, repo, *pats, **opts)
449 449
450 450 mq = extensions.find('mq')
451 451
452 452 def committomq(ui, repo, *pats, **opts):
453 453 # At this point the working copy contains only changes that
454 454 # were accepted. All other changes were reverted.
455 455 # We can't pass *pats here since qrefresh will undo all other
456 456 # changed files in the patch that aren't in pats.
457 457 mq.refresh(ui, repo, **opts)
458 458
459 459 # backup all changed files
460 460 dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
461 461
462 462 def qrecord(ui, repo, patch, *pats, **opts):
463 463 '''interactively record a new patch
464 464
465 465 See :hg:`help qnew` & :hg:`help record` for more information and
466 466 usage.
467 467 '''
468 468
469 469 try:
470 470 mq = extensions.find('mq')
471 471 except KeyError:
472 472 raise util.Abort(_("'mq' extension not loaded"))
473 473
474 474 repo.mq.checkpatchname(patch)
475 475
476 476 def committomq(ui, repo, *pats, **opts):
477 477 opts['checkname'] = False
478 478 mq.new(ui, repo, patch, *pats, **opts)
479 479
480 480 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
481 481
482 482 def qnew(origfn, ui, repo, patch, *args, **opts):
483 483 if opts['interactive']:
484 484 return qrecord(ui, repo, patch, *args, **opts)
485 485 return origfn(ui, repo, patch, *args, **opts)
486 486
487 487 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
488 488 if not ui.interactive():
489 489 raise util.Abort(_('running non-interactively, use %s instead') %
490 490 cmdsuggest)
491 491
492 492 # make sure username is set before going interactive
493 493 if not opts.get('user'):
494 494 ui.username() # raise exception, username not provided
495 495
496 496 def recordfunc(ui, repo, message, match, opts):
497 497 """This is generic record driver.
498 498
499 499 Its job is to interactively filter local changes, and
500 500 accordingly prepare working directory into a state in which the
501 501 job can be delegated to a non-interactive commit command such as
502 502 'commit' or 'qrefresh'.
503 503
504 504 After the actual job is done by non-interactive command, the
505 505 working directory is restored to its original state.
506 506
507 507 In the end we'll record interesting changes, and everything else
508 508 will be left in place, so the user can continue working.
509 509 """
510 510
511 511 cmdutil.checkunfinished(repo, commit=True)
512 512 merge = len(repo[None].parents()) > 1
513 513 if merge:
514 514 raise util.Abort(_('cannot partially commit a merge '
515 515 '(use "hg commit" instead)'))
516 516
517 517 changes = repo.status(match=match)[:3]
518 518 diffopts = opts.copy()
519 519 diffopts['nodates'] = True
520 520 diffopts['git'] = True
521 521 diffopts = patch.diffopts(ui, opts=diffopts)
522 522 chunks = patch.diff(repo, changes=changes, opts=diffopts)
523 523 fp = cStringIO.StringIO()
524 524 fp.write(''.join(chunks))
525 525 fp.seek(0)
526 526
527 527 # 1. filter patch, so we have intending-to apply subset of it
528 528 try:
529 529 chunks = filterpatch(ui, parsepatch(fp))
530 530 except patch.PatchError, err:
531 531 raise util.Abort(_('error parsing patch: %s') % err)
532 532
533 533 del fp
534 534
535 535 contenders = set()
536 536 for h in chunks:
537 537 try:
538 538 contenders.update(set(h.files()))
539 539 except AttributeError:
540 540 pass
541 541
542 542 changed = changes[0] + changes[1] + changes[2]
543 543 newfiles = [f for f in changed if f in contenders]
544 544 if not newfiles:
545 545 ui.status(_('no changes to record\n'))
546 546 return 0
547 547
548 548 modified = set(changes[0])
549 549
550 550 # 2. backup changed files, so we can restore them in the end
551 551 if backupall:
552 552 tobackup = changed
553 553 else:
554 554 tobackup = [f for f in newfiles if f in modified]
555 555
556 556 backups = {}
557 557 if tobackup:
558 558 backupdir = repo.join('record-backups')
559 559 try:
560 560 os.mkdir(backupdir)
561 561 except OSError, err:
562 562 if err.errno != errno.EEXIST:
563 563 raise
564 564 try:
565 565 # backup continues
566 566 for f in tobackup:
567 567 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
568 568 dir=backupdir)
569 569 os.close(fd)
570 570 ui.debug('backup %r as %r\n' % (f, tmpname))
571 571 util.copyfile(repo.wjoin(f), tmpname)
572 572 shutil.copystat(repo.wjoin(f), tmpname)
573 573 backups[f] = tmpname
574 574
575 575 fp = cStringIO.StringIO()
576 576 for c in chunks:
577 577 if c.filename() in backups:
578 578 c.write(fp)
579 579 dopatch = fp.tell()
580 580 fp.seek(0)
581 581
582 582 # 3a. apply filtered patch to clean repo (clean)
583 583 if backups:
584 584 hg.revert(repo, repo.dirstate.p1(),
585 585 lambda key: key in backups)
586 586
587 587 # 3b. (apply)
588 588 if dopatch:
589 589 try:
590 590 ui.debug('applying patch\n')
591 591 ui.debug(fp.getvalue())
592 592 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
593 593 except patch.PatchError, err:
594 594 raise util.Abort(str(err))
595 595 del fp
596 596
597 597 # 4. We prepared working directory according to filtered
598 598 # patch. Now is the time to delegate the job to
599 599 # commit/qrefresh or the like!
600 600
601 601 # it is important to first chdir to repo root -- we'll call
602 602 # a highlevel command with list of pathnames relative to
603 603 # repo root
604 cwd = os.getcwd()
605 os.chdir(repo.root)
606 try:
607 commitfunc(ui, repo, *newfiles, **opts)
608 finally:
609 os.chdir(cwd)
604 newfiles = [repo.wjoin(nf) for nf in newfiles]
605 commitfunc(ui, repo, *newfiles, **opts)
610 606
611 607 return 0
612 608 finally:
613 609 # 5. finally restore backed-up files
614 610 try:
615 611 for realname, tmpname in backups.iteritems():
616 612 ui.debug('restoring %r to %r\n' % (tmpname, realname))
617 613 util.copyfile(tmpname, repo.wjoin(realname))
618 614 # Our calls to copystat() here and above are a
619 615 # hack to trick any editors that have f open that
620 616 # we haven't modified them.
621 617 #
622 618 # Also note that this racy as an editor could
623 619 # notice the file's mtime before we've finished
624 620 # writing it.
625 621 shutil.copystat(tmpname, repo.wjoin(realname))
626 622 os.unlink(tmpname)
627 623 if tobackup:
628 624 os.rmdir(backupdir)
629 625 except OSError:
630 626 pass
631 627
632 628 # wrap ui.write so diff output can be labeled/colorized
633 629 def wrapwrite(orig, *args, **kw):
634 630 label = kw.pop('label', '')
635 631 for chunk, l in patch.difflabel(lambda: args):
636 632 orig(chunk, label=label + l)
637 633 oldwrite = ui.write
638 634 extensions.wrapfunction(ui, 'write', wrapwrite)
639 635 try:
640 636 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
641 637 finally:
642 638 ui.write = oldwrite
643 639
644 640 cmdtable["qrecord"] = \
645 641 (qrecord, [], # placeholder until mq is available
646 642 _('hg qrecord [OPTION]... PATCH [FILE]...'))
647 643
648 644 def uisetup(ui):
649 645 try:
650 646 mq = extensions.find('mq')
651 647 except KeyError:
652 648 return
653 649
654 650 cmdtable["qrecord"] = \
655 651 (qrecord,
656 652 # same options as qnew, but copy them so we don't get
657 653 # -i/--interactive for qrecord and add white space diff options
658 654 mq.cmdtable['^qnew'][1][:] + commands.diffwsopts,
659 655 _('hg qrecord [OPTION]... PATCH [FILE]...'))
660 656
661 657 _wrapcmd('qnew', mq.cmdtable, qnew, _("interactively record a new patch"))
662 658 _wrapcmd('qrefresh', mq.cmdtable, qrefresh,
663 659 _("interactively select changes to refresh"))
664 660
665 661 def _wrapcmd(cmd, table, wrapfn, msg):
666 662 entry = extensions.wrapcommand(table, cmd, wrapfn)
667 663 entry[1].append(('i', 'interactive', None, msg))
668 664
669 665 commands.inferrepo += " record qrecord"
General Comments 0
You need to be logged in to leave comments. Login now