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