##// END OF EJS Templates
record: add checkunfinished support (issue3955)
Matt Mackall -
r19497:e012a200 stable
parent child Browse files
Show More
@@ -1,680 +1,681 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, 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 yield 'other', 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 addother(self, line):
232 232 pass # 'other' lines are ignored
233 233
234 234 def finished(self):
235 235 self.addcontext([])
236 236 return self.headers
237 237
238 238 transitions = {
239 239 'file': {'context': addcontext,
240 240 'file': newfile,
241 241 'hunk': addhunk,
242 242 'range': addrange},
243 243 'context': {'file': newfile,
244 244 'hunk': addhunk,
245 245 'range': addrange,
246 246 'other': addother},
247 247 'hunk': {'context': addcontext,
248 248 'file': newfile,
249 249 'range': addrange},
250 250 'range': {'context': addcontext,
251 251 'hunk': addhunk},
252 252 'other': {'other': addother},
253 253 }
254 254
255 255 p = parser()
256 256
257 257 state = 'context'
258 258 for newstate, data in scanpatch(fp):
259 259 try:
260 260 p.transitions[state][newstate](p, data)
261 261 except KeyError:
262 262 raise patch.PatchError('unhandled transition: %s -> %s' %
263 263 (state, newstate))
264 264 state = newstate
265 265 return p.finished()
266 266
267 267 def filterpatch(ui, headers):
268 268 """Interactively filter patch chunks into applied-only chunks"""
269 269
270 270 def prompt(skipfile, skipall, query, chunk):
271 271 """prompt query, and process base inputs
272 272
273 273 - y/n for the rest of file
274 274 - y/n for the rest
275 275 - ? (help)
276 276 - q (quit)
277 277
278 278 Return True/False and possibly updated skipfile and skipall.
279 279 """
280 280 newpatches = None
281 281 if skipall is not None:
282 282 return skipall, skipfile, skipall, newpatches
283 283 if skipfile is not None:
284 284 return skipfile, skipfile, skipall, newpatches
285 285 while True:
286 286 resps = _('[Ynesfdaq?]'
287 287 '$$ &Yes, record this change'
288 288 '$$ &No, skip this change'
289 289 '$$ &Edit the change manually'
290 290 '$$ &Skip remaining changes to this file'
291 291 '$$ Record remaining changes to this &file'
292 292 '$$ &Done, skip remaining changes and files'
293 293 '$$ Record &all changes to all remaining files'
294 294 '$$ &Quit, recording no changes'
295 295 '$$ &?')
296 296 r = ui.promptchoice("%s %s" % (query, resps))
297 297 ui.write("\n")
298 298 if r == 8: # ?
299 299 doc = gettext(record.__doc__)
300 300 c = doc.find('::') + 2
301 301 for l in doc[c:].splitlines():
302 302 if l.startswith(' '):
303 303 ui.write(l.strip(), '\n')
304 304 continue
305 305 elif r == 0: # yes
306 306 ret = True
307 307 elif r == 1: # no
308 308 ret = False
309 309 elif r == 2: # Edit patch
310 310 if chunk is None:
311 311 ui.write(_('cannot edit patch for whole file'))
312 312 ui.write("\n")
313 313 continue
314 314 if chunk.header.binary():
315 315 ui.write(_('cannot edit patch for binary file'))
316 316 ui.write("\n")
317 317 continue
318 318 # Patch comment based on the Git one (based on comment at end of
319 319 # http://mercurial.selenic.com/wiki/RecordExtension)
320 320 phelp = '---' + _("""
321 321 To remove '-' lines, make them ' ' lines (context).
322 322 To remove '+' lines, delete them.
323 323 Lines starting with # will be removed from the patch.
324 324
325 325 If the patch applies cleanly, the edited hunk will immediately be
326 326 added to the record list. If it does not apply cleanly, a rejects
327 327 file will be generated: you can use that when you try again. If
328 328 all lines of the hunk are removed, then the edit is aborted and
329 329 the hunk is left unchanged.
330 330 """)
331 331 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
332 332 suffix=".diff", text=True)
333 333 ncpatchfp = None
334 334 try:
335 335 # Write the initial patch
336 336 f = os.fdopen(patchfd, "w")
337 337 chunk.header.write(f)
338 338 chunk.write(f)
339 339 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
340 340 f.close()
341 341 # Start the editor and wait for it to complete
342 342 editor = ui.geteditor()
343 343 util.system("%s \"%s\"" % (editor, patchfn),
344 344 environ={'HGUSER': ui.username()},
345 345 onerr=util.Abort, errprefix=_("edit failed"),
346 346 out=ui.fout)
347 347 # Remove comment lines
348 348 patchfp = open(patchfn)
349 349 ncpatchfp = cStringIO.StringIO()
350 350 for line in patchfp:
351 351 if not line.startswith('#'):
352 352 ncpatchfp.write(line)
353 353 patchfp.close()
354 354 ncpatchfp.seek(0)
355 355 newpatches = parsepatch(ncpatchfp)
356 356 finally:
357 357 os.unlink(patchfn)
358 358 del ncpatchfp
359 359 # Signal that the chunk shouldn't be applied as-is, but
360 360 # provide the new patch to be used instead.
361 361 ret = False
362 362 elif r == 3: # Skip
363 363 ret = skipfile = False
364 364 elif r == 4: # file (Record remaining)
365 365 ret = skipfile = True
366 366 elif r == 5: # done, skip remaining
367 367 ret = skipall = False
368 368 elif r == 6: # all
369 369 ret = skipall = True
370 370 elif r == 7: # quit
371 371 raise util.Abort(_('user quit'))
372 372 return ret, skipfile, skipall, newpatches
373 373
374 374 seen = set()
375 375 applied = {} # 'filename' -> [] of chunks
376 376 skipfile, skipall = None, None
377 377 pos, total = 1, sum(len(h.hunks) for h in headers)
378 378 for h in headers:
379 379 pos += len(h.hunks)
380 380 skipfile = None
381 381 fixoffset = 0
382 382 hdr = ''.join(h.header)
383 383 if hdr in seen:
384 384 continue
385 385 seen.add(hdr)
386 386 if skipall is None:
387 387 h.pretty(ui)
388 388 msg = (_('examine changes to %s?') %
389 389 _(' and ').join("'%s'" % f for f in h.files()))
390 390 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
391 391 if not r:
392 392 continue
393 393 applied[h.filename()] = [h]
394 394 if h.allhunks():
395 395 applied[h.filename()] += h.hunks
396 396 continue
397 397 for i, chunk in enumerate(h.hunks):
398 398 if skipfile is None and skipall is None:
399 399 chunk.pretty(ui)
400 400 if total == 1:
401 401 msg = _("record this change to '%s'?") % chunk.filename()
402 402 else:
403 403 idx = pos - len(h.hunks) + i
404 404 msg = _("record change %d/%d to '%s'?") % (idx, total,
405 405 chunk.filename())
406 406 r, skipfile, skipall, newpatches = prompt(skipfile,
407 407 skipall, msg, chunk)
408 408 if r:
409 409 if fixoffset:
410 410 chunk = copy.copy(chunk)
411 411 chunk.toline += fixoffset
412 412 applied[chunk.filename()].append(chunk)
413 413 elif newpatches is not None:
414 414 for newpatch in newpatches:
415 415 for newhunk in newpatch.hunks:
416 416 if fixoffset:
417 417 newhunk.toline += fixoffset
418 418 applied[newhunk.filename()].append(newhunk)
419 419 else:
420 420 fixoffset += chunk.removed - chunk.added
421 421 return sum([h for h in applied.itervalues()
422 422 if h[0].special() or len(h) > 1], [])
423 423
424 424 @command("record",
425 425 # same options as commit + white space diff options
426 426 commands.table['^commit|ci'][1][:] + diffopts,
427 427 _('hg record [OPTION]... [FILE]...'))
428 428 def record(ui, repo, *pats, **opts):
429 429 '''interactively select changes to commit
430 430
431 431 If a list of files is omitted, all changes reported by :hg:`status`
432 432 will be candidates for recording.
433 433
434 434 See :hg:`help dates` for a list of formats valid for -d/--date.
435 435
436 436 You will be prompted for whether to record changes to each
437 437 modified file, and for files with multiple changes, for each
438 438 change to use. For each query, the following responses are
439 439 possible::
440 440
441 441 y - record this change
442 442 n - skip this change
443 443 e - edit this change manually
444 444
445 445 s - skip remaining changes to this file
446 446 f - record remaining changes to this file
447 447
448 448 d - done, skip remaining changes and files
449 449 a - record all changes to all remaining files
450 450 q - quit, recording no changes
451 451
452 452 ? - display help
453 453
454 454 This command is not available when committing a merge.'''
455 455
456 456 dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
457 457
458 458 def qrefresh(origfn, ui, repo, *pats, **opts):
459 459 if not opts['interactive']:
460 460 return origfn(ui, repo, *pats, **opts)
461 461
462 462 mq = extensions.find('mq')
463 463
464 464 def committomq(ui, repo, *pats, **opts):
465 465 # At this point the working copy contains only changes that
466 466 # were accepted. All other changes were reverted.
467 467 # We can't pass *pats here since qrefresh will undo all other
468 468 # changed files in the patch that aren't in pats.
469 469 mq.refresh(ui, repo, **opts)
470 470
471 471 # backup all changed files
472 472 dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
473 473
474 474 def qrecord(ui, repo, patch, *pats, **opts):
475 475 '''interactively record a new patch
476 476
477 477 See :hg:`help qnew` & :hg:`help record` for more information and
478 478 usage.
479 479 '''
480 480
481 481 try:
482 482 mq = extensions.find('mq')
483 483 except KeyError:
484 484 raise util.Abort(_("'mq' extension not loaded"))
485 485
486 486 repo.mq.checkpatchname(patch)
487 487
488 488 def committomq(ui, repo, *pats, **opts):
489 489 opts['checkname'] = False
490 490 mq.new(ui, repo, patch, *pats, **opts)
491 491
492 492 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
493 493
494 494 def qnew(origfn, ui, repo, patch, *args, **opts):
495 495 if opts['interactive']:
496 496 return qrecord(ui, repo, patch, *args, **opts)
497 497 return origfn(ui, repo, patch, *args, **opts)
498 498
499 499 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
500 500 if not ui.interactive():
501 501 raise util.Abort(_('running non-interactively, use %s instead') %
502 502 cmdsuggest)
503 503
504 504 # make sure username is set before going interactive
505 505 ui.username()
506 506
507 507 def recordfunc(ui, repo, message, match, opts):
508 508 """This is generic record driver.
509 509
510 510 Its job is to interactively filter local changes, and
511 511 accordingly prepare working directory into a state in which the
512 512 job can be delegated to a non-interactive commit command such as
513 513 'commit' or 'qrefresh'.
514 514
515 515 After the actual job is done by non-interactive command, the
516 516 working directory is restored to its original state.
517 517
518 518 In the end we'll record interesting changes, and everything else
519 519 will be left in place, so the user can continue working.
520 520 """
521 521
522 cmdutil.checkunfinished(repo, commit=True)
522 523 merge = len(repo[None].parents()) > 1
523 524 if merge:
524 525 raise util.Abort(_('cannot partially commit a merge '
525 526 '(use "hg commit" instead)'))
526 527
527 528 changes = repo.status(match=match)[:3]
528 529 diffopts = patch.diffopts(ui, opts=dict(
529 530 git=True, nodates=True,
530 531 ignorews=opts.get('ignore_all_space'),
531 532 ignorewsamount=opts.get('ignore_space_change'),
532 533 ignoreblanklines=opts.get('ignore_blank_lines')))
533 534 chunks = patch.diff(repo, changes=changes, opts=diffopts)
534 535 fp = cStringIO.StringIO()
535 536 fp.write(''.join(chunks))
536 537 fp.seek(0)
537 538
538 539 # 1. filter patch, so we have intending-to apply subset of it
539 540 try:
540 541 chunks = filterpatch(ui, parsepatch(fp))
541 542 except patch.PatchError, err:
542 543 raise util.Abort(_('error parsing patch: %s') % err)
543 544
544 545 del fp
545 546
546 547 contenders = set()
547 548 for h in chunks:
548 549 try:
549 550 contenders.update(set(h.files()))
550 551 except AttributeError:
551 552 pass
552 553
553 554 changed = changes[0] + changes[1] + changes[2]
554 555 newfiles = [f for f in changed if f in contenders]
555 556 if not newfiles:
556 557 ui.status(_('no changes to record\n'))
557 558 return 0
558 559
559 560 modified = set(changes[0])
560 561
561 562 # 2. backup changed files, so we can restore them in the end
562 563 if backupall:
563 564 tobackup = changed
564 565 else:
565 566 tobackup = [f for f in newfiles if f in modified]
566 567
567 568 backups = {}
568 569 if tobackup:
569 570 backupdir = repo.join('record-backups')
570 571 try:
571 572 os.mkdir(backupdir)
572 573 except OSError, err:
573 574 if err.errno != errno.EEXIST:
574 575 raise
575 576 try:
576 577 # backup continues
577 578 for f in tobackup:
578 579 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
579 580 dir=backupdir)
580 581 os.close(fd)
581 582 ui.debug('backup %r as %r\n' % (f, tmpname))
582 583 util.copyfile(repo.wjoin(f), tmpname)
583 584 shutil.copystat(repo.wjoin(f), tmpname)
584 585 backups[f] = tmpname
585 586
586 587 fp = cStringIO.StringIO()
587 588 for c in chunks:
588 589 if c.filename() in backups:
589 590 c.write(fp)
590 591 dopatch = fp.tell()
591 592 fp.seek(0)
592 593
593 594 # 3a. apply filtered patch to clean repo (clean)
594 595 if backups:
595 596 hg.revert(repo, repo.dirstate.p1(),
596 597 lambda key: key in backups)
597 598
598 599 # 3b. (apply)
599 600 if dopatch:
600 601 try:
601 602 ui.debug('applying patch\n')
602 603 ui.debug(fp.getvalue())
603 604 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
604 605 except patch.PatchError, err:
605 606 raise util.Abort(str(err))
606 607 del fp
607 608
608 609 # 4. We prepared working directory according to filtered
609 610 # patch. Now is the time to delegate the job to
610 611 # commit/qrefresh or the like!
611 612
612 613 # it is important to first chdir to repo root -- we'll call
613 614 # a highlevel command with list of pathnames relative to
614 615 # repo root
615 616 cwd = os.getcwd()
616 617 os.chdir(repo.root)
617 618 try:
618 619 commitfunc(ui, repo, *newfiles, **opts)
619 620 finally:
620 621 os.chdir(cwd)
621 622
622 623 return 0
623 624 finally:
624 625 # 5. finally restore backed-up files
625 626 try:
626 627 for realname, tmpname in backups.iteritems():
627 628 ui.debug('restoring %r to %r\n' % (tmpname, realname))
628 629 util.copyfile(tmpname, repo.wjoin(realname))
629 630 # Our calls to copystat() here and above are a
630 631 # hack to trick any editors that have f open that
631 632 # we haven't modified them.
632 633 #
633 634 # Also note that this racy as an editor could
634 635 # notice the file's mtime before we've finished
635 636 # writing it.
636 637 shutil.copystat(tmpname, repo.wjoin(realname))
637 638 os.unlink(tmpname)
638 639 if tobackup:
639 640 os.rmdir(backupdir)
640 641 except OSError:
641 642 pass
642 643
643 644 # wrap ui.write so diff output can be labeled/colorized
644 645 def wrapwrite(orig, *args, **kw):
645 646 label = kw.pop('label', '')
646 647 for chunk, l in patch.difflabel(lambda: args):
647 648 orig(chunk, label=label + l)
648 649 oldwrite = ui.write
649 650 extensions.wrapfunction(ui, 'write', wrapwrite)
650 651 try:
651 652 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
652 653 finally:
653 654 ui.write = oldwrite
654 655
655 656 cmdtable["qrecord"] = \
656 657 (qrecord, [], # placeholder until mq is available
657 658 _('hg qrecord [OPTION]... PATCH [FILE]...'))
658 659
659 660 def uisetup(ui):
660 661 try:
661 662 mq = extensions.find('mq')
662 663 except KeyError:
663 664 return
664 665
665 666 cmdtable["qrecord"] = \
666 667 (qrecord,
667 668 # same options as qnew, but copy them so we don't get
668 669 # -i/--interactive for qrecord and add white space diff options
669 670 mq.cmdtable['^qnew'][1][:] + diffopts,
670 671 _('hg qrecord [OPTION]... PATCH [FILE]...'))
671 672
672 673 _wrapcmd('qnew', mq.cmdtable, qnew, _("interactively record a new patch"))
673 674 _wrapcmd('qrefresh', mq.cmdtable, qrefresh,
674 675 _("interactively select changes to refresh"))
675 676
676 677 def _wrapcmd(cmd, table, wrapfn, msg):
677 678 entry = extensions.wrapcommand(table, cmd, wrapfn)
678 679 entry[1].append(('i', 'interactive', None, msg))
679 680
680 681 commands.inferrepo += " record qrecord"
General Comments 0
You need to be logged in to leave comments. Login now