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