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