##// END OF EJS Templates
record: access status fields by name rather than index...
Martin von Zweigbergk -
r22921:75e7d4a0 default
parent child Browse files
Show More
@@ -1,662 +1,662 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 # This command registration is replaced during uisetup().
463 463 @command('qrecord',
464 464 [],
465 465 _('hg qrecord [OPTION]... PATCH [FILE]...'),
466 466 inferrepo=True)
467 467 def qrecord(ui, repo, patch, *pats, **opts):
468 468 '''interactively record a new patch
469 469
470 470 See :hg:`help qnew` & :hg:`help record` for more information and
471 471 usage.
472 472 '''
473 473
474 474 try:
475 475 mq = extensions.find('mq')
476 476 except KeyError:
477 477 raise util.Abort(_("'mq' extension not loaded"))
478 478
479 479 repo.mq.checkpatchname(patch)
480 480
481 481 def committomq(ui, repo, *pats, **opts):
482 482 opts['checkname'] = False
483 483 mq.new(ui, repo, patch, *pats, **opts)
484 484
485 485 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
486 486
487 487 def qnew(origfn, ui, repo, patch, *args, **opts):
488 488 if opts['interactive']:
489 489 return qrecord(ui, repo, patch, *args, **opts)
490 490 return origfn(ui, repo, patch, *args, **opts)
491 491
492 492 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
493 493 if not ui.interactive():
494 494 raise util.Abort(_('running non-interactively, use %s instead') %
495 495 cmdsuggest)
496 496
497 497 # make sure username is set before going interactive
498 498 if not opts.get('user'):
499 499 ui.username() # raise exception, username not provided
500 500
501 501 def recordfunc(ui, repo, message, match, opts):
502 502 """This is generic record driver.
503 503
504 504 Its job is to interactively filter local changes, and
505 505 accordingly prepare working directory into a state in which the
506 506 job can be delegated to a non-interactive commit command such as
507 507 'commit' or 'qrefresh'.
508 508
509 509 After the actual job is done by non-interactive command, the
510 510 working directory is restored to its original state.
511 511
512 512 In the end we'll record interesting changes, and everything else
513 513 will be left in place, so the user can continue working.
514 514 """
515 515
516 516 cmdutil.checkunfinished(repo, commit=True)
517 517 merge = len(repo[None].parents()) > 1
518 518 if merge:
519 519 raise util.Abort(_('cannot partially commit a merge '
520 520 '(use "hg commit" instead)'))
521 521
522 changes = repo.status(match=match)[:3]
522 status = repo.status(match=match)
523 523 diffopts = opts.copy()
524 524 diffopts['nodates'] = True
525 525 diffopts['git'] = True
526 526 diffopts = patch.diffopts(ui, opts=diffopts)
527 chunks = patch.diff(repo, changes=changes, opts=diffopts)
527 chunks = patch.diff(repo, changes=status, opts=diffopts)
528 528 fp = cStringIO.StringIO()
529 529 fp.write(''.join(chunks))
530 530 fp.seek(0)
531 531
532 532 # 1. filter patch, so we have intending-to apply subset of it
533 533 try:
534 534 chunks = filterpatch(ui, parsepatch(fp))
535 535 except patch.PatchError, err:
536 536 raise util.Abort(_('error parsing patch: %s') % err)
537 537
538 538 del fp
539 539
540 540 contenders = set()
541 541 for h in chunks:
542 542 try:
543 543 contenders.update(set(h.files()))
544 544 except AttributeError:
545 545 pass
546 546
547 changed = changes[0] + changes[1] + changes[2]
547 changed = status.modified + status.added + status.removed
548 548 newfiles = [f for f in changed if f in contenders]
549 549 if not newfiles:
550 550 ui.status(_('no changes to record\n'))
551 551 return 0
552 552
553 modified = set(changes[0])
553 modified = set(status.modified)
554 554
555 555 # 2. backup changed files, so we can restore them in the end
556 556 if backupall:
557 557 tobackup = changed
558 558 else:
559 559 tobackup = [f for f in newfiles if f in modified]
560 560
561 561 backups = {}
562 562 if tobackup:
563 563 backupdir = repo.join('record-backups')
564 564 try:
565 565 os.mkdir(backupdir)
566 566 except OSError, err:
567 567 if err.errno != errno.EEXIST:
568 568 raise
569 569 try:
570 570 # backup continues
571 571 for f in tobackup:
572 572 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
573 573 dir=backupdir)
574 574 os.close(fd)
575 575 ui.debug('backup %r as %r\n' % (f, tmpname))
576 576 util.copyfile(repo.wjoin(f), tmpname)
577 577 shutil.copystat(repo.wjoin(f), tmpname)
578 578 backups[f] = tmpname
579 579
580 580 fp = cStringIO.StringIO()
581 581 for c in chunks:
582 582 if c.filename() in backups:
583 583 c.write(fp)
584 584 dopatch = fp.tell()
585 585 fp.seek(0)
586 586
587 587 # 3a. apply filtered patch to clean repo (clean)
588 588 if backups:
589 589 hg.revert(repo, repo.dirstate.p1(),
590 590 lambda key: key in backups)
591 591
592 592 # 3b. (apply)
593 593 if dopatch:
594 594 try:
595 595 ui.debug('applying patch\n')
596 596 ui.debug(fp.getvalue())
597 597 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
598 598 except patch.PatchError, err:
599 599 raise util.Abort(str(err))
600 600 del fp
601 601
602 602 # 4. We prepared working directory according to filtered
603 603 # patch. Now is the time to delegate the job to
604 604 # commit/qrefresh or the like!
605 605
606 606 # Make all of the pathnames absolute.
607 607 newfiles = [repo.wjoin(nf) for nf in newfiles]
608 608 commitfunc(ui, repo, *newfiles, **opts)
609 609
610 610 return 0
611 611 finally:
612 612 # 5. finally restore backed-up files
613 613 try:
614 614 for realname, tmpname in backups.iteritems():
615 615 ui.debug('restoring %r to %r\n' % (tmpname, realname))
616 616 util.copyfile(tmpname, repo.wjoin(realname))
617 617 # Our calls to copystat() here and above are a
618 618 # hack to trick any editors that have f open that
619 619 # we haven't modified them.
620 620 #
621 621 # Also note that this racy as an editor could
622 622 # notice the file's mtime before we've finished
623 623 # writing it.
624 624 shutil.copystat(tmpname, repo.wjoin(realname))
625 625 os.unlink(tmpname)
626 626 if tobackup:
627 627 os.rmdir(backupdir)
628 628 except OSError:
629 629 pass
630 630
631 631 # wrap ui.write so diff output can be labeled/colorized
632 632 def wrapwrite(orig, *args, **kw):
633 633 label = kw.pop('label', '')
634 634 for chunk, l in patch.difflabel(lambda: args):
635 635 orig(chunk, label=label + l)
636 636 oldwrite = ui.write
637 637 extensions.wrapfunction(ui, 'write', wrapwrite)
638 638 try:
639 639 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
640 640 finally:
641 641 ui.write = oldwrite
642 642
643 643 def uisetup(ui):
644 644 try:
645 645 mq = extensions.find('mq')
646 646 except KeyError:
647 647 return
648 648
649 649 cmdtable["qrecord"] = \
650 650 (qrecord,
651 651 # same options as qnew, but copy them so we don't get
652 652 # -i/--interactive for qrecord and add white space diff options
653 653 mq.cmdtable['^qnew'][1][:] + commands.diffwsopts,
654 654 _('hg qrecord [OPTION]... PATCH [FILE]...'))
655 655
656 656 _wrapcmd('qnew', mq.cmdtable, qnew, _("interactively record a new patch"))
657 657 _wrapcmd('qrefresh', mq.cmdtable, qrefresh,
658 658 _("interactively select changes to refresh"))
659 659
660 660 def _wrapcmd(cmd, table, wrapfn, msg):
661 661 entry = extensions.wrapcommand(table, cmd, wrapfn)
662 662 entry[1].append(('i', 'interactive', None, msg))
General Comments 0
You need to be logged in to leave comments. Login now