##// END OF EJS Templates
rebase: drop unused p2 arg from rebasenode
Matt Mackall -
r13880:255009c7 default
parent child Browse files
Show More
@@ -1,589 +1,589 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 http://mercurial.selenic.com/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands
18 18 from mercurial import extensions, ancestor, copies, patch
19 19 from mercurial.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 nullmerge = -2
26 26
27 27 def rebase(ui, repo, **opts):
28 28 """move changeset (and descendants) to a different branch
29 29
30 30 Rebase uses repeated merging to graft changesets from one part of
31 31 history (the source) onto another (the destination). This can be
32 32 useful for linearizing *local* changes relative to a master
33 33 development tree.
34 34
35 35 You should not rebase changesets that have already been shared
36 36 with others. Doing so will force everybody else to perform the
37 37 same rebase or they will end up with duplicated changesets after
38 38 pulling in your rebased changesets.
39 39
40 40 If you don't specify a destination changeset (``-d/--dest``),
41 41 rebase uses the tipmost head of the current named branch as the
42 42 destination. (The destination changeset is not modified by
43 43 rebasing, but new changesets are added as its descendants.)
44 44
45 45 You can specify which changesets to rebase in two ways: as a
46 46 "source" changeset or as a "base" changeset. Both are shorthand
47 47 for a topologically related set of changesets (the "source
48 48 branch"). If you specify source (``-s/--source``), rebase will
49 49 rebase that changeset and all of its descendants onto dest. If you
50 50 specify base (``-b/--base``), rebase will select ancestors of base
51 51 back to but not including the common ancestor with dest. Thus,
52 52 ``-b`` is less precise but more convenient than ``-s``: you can
53 53 specify any changeset in the source branch, and rebase will select
54 54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
55 55 uses the parent of the working directory as the base.
56 56
57 57 By default, rebase recreates the changesets in the source branch
58 58 as descendants of dest and then destroys the originals. Use
59 59 ``--keep`` to preserve the original source changesets. Some
60 60 changesets in the source branch (e.g. merges from the destination
61 61 branch) may be dropped if they no longer contribute any change.
62 62
63 63 One result of the rules for selecting the destination changeset
64 64 and source branch is that, unlike ``merge``, rebase will do
65 65 nothing if you are at the latest (tipmost) head of a named branch
66 66 with two heads. You need to explicitly specify source and/or
67 67 destination (or ``update`` to the other head, if it's the head of
68 68 the intended source branch).
69 69
70 70 If a rebase is interrupted to manually resolve a merge, it can be
71 71 continued with --continue/-c or aborted with --abort/-a.
72 72
73 73 Returns 0 on success, 1 if nothing to rebase.
74 74 """
75 75 originalwd = target = None
76 76 external = nullrev
77 77 state = {}
78 78 skipped = set()
79 79 targetancestors = set()
80 80
81 81 lock = wlock = None
82 82 try:
83 83 lock = repo.lock()
84 84 wlock = repo.wlock()
85 85
86 86 # Validate input and define rebasing points
87 87 destf = opts.get('dest', None)
88 88 srcf = opts.get('source', None)
89 89 basef = opts.get('base', None)
90 90 contf = opts.get('continue')
91 91 abortf = opts.get('abort')
92 92 collapsef = opts.get('collapse', False)
93 93 collapsemsg = cmdutil.logmessage(opts)
94 94 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
95 95 keepf = opts.get('keep', False)
96 96 keepbranchesf = opts.get('keepbranches', False)
97 97 detachf = opts.get('detach', False)
98 98 # keepopen is not meant for use on the command line, but by
99 99 # other extensions
100 100 keepopen = opts.get('keepopen', False)
101 101
102 102 if collapsemsg and not collapsef:
103 103 raise util.Abort(
104 104 _('message can only be specified with collapse'))
105 105
106 106 if contf or abortf:
107 107 if contf and abortf:
108 108 raise util.Abort(_('cannot use both abort and continue'))
109 109 if collapsef:
110 110 raise util.Abort(
111 111 _('cannot use collapse with continue or abort'))
112 112 if detachf:
113 113 raise util.Abort(_('cannot use detach with continue or abort'))
114 114 if srcf or basef or destf:
115 115 raise util.Abort(
116 116 _('abort and continue do not allow specifying revisions'))
117 117 if opts.get('tool', False):
118 118 ui.warn(_('tool option will be ignored\n'))
119 119
120 120 (originalwd, target, state, skipped, collapsef, keepf,
121 121 keepbranchesf, external) = restorestatus(repo)
122 122 if abortf:
123 123 return abort(repo, originalwd, target, state)
124 124 else:
125 125 if srcf and basef:
126 126 raise util.Abort(_('cannot specify both a '
127 127 'revision and a base'))
128 128 if detachf:
129 129 if not srcf:
130 130 raise util.Abort(
131 131 _('detach requires a revision to be specified'))
132 132 if basef:
133 133 raise util.Abort(_('cannot specify a base with detach'))
134 134
135 135 cmdutil.bail_if_changed(repo)
136 136 result = buildstate(repo, destf, srcf, basef, detachf)
137 137 if not result:
138 138 # Empty state built, nothing to rebase
139 139 ui.status(_('nothing to rebase\n'))
140 140 return 1
141 141 else:
142 142 originalwd, target, state = result
143 143 if collapsef:
144 144 targetancestors = set(repo.changelog.ancestors(target))
145 145 external = checkexternal(repo, state, targetancestors)
146 146
147 147 if keepbranchesf:
148 148 assert not extrafn, 'cannot use both keepbranches and extrafn'
149 149 def extrafn(ctx, extra):
150 150 extra['branch'] = ctx.branch()
151 151
152 152 # Rebase
153 153 if not targetancestors:
154 154 targetancestors = set(repo.changelog.ancestors(target))
155 155 targetancestors.add(target)
156 156
157 157 sortedstate = sorted(state)
158 158 total = len(sortedstate)
159 159 pos = 0
160 160 for rev in sortedstate:
161 161 pos += 1
162 162 if state[rev] == -1:
163 163 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
164 164 _('changesets'), total)
165 165 storestatus(repo, originalwd, target, state, collapsef, keepf,
166 166 keepbranchesf, external)
167 167 p1, p2 = defineparents(repo, rev, target, state,
168 168 targetancestors)
169 169 if len(repo.parents()) == 2:
170 170 repo.ui.debug('resuming interrupted rebase\n')
171 171 else:
172 172 try:
173 173 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
174 stats = rebasenode(repo, rev, p1, p2, state)
174 stats = rebasenode(repo, rev, p1, state)
175 175 if stats and stats[3] > 0:
176 176 raise util.Abort(_('unresolved conflicts (see hg '
177 177 'resolve, then hg rebase --continue)'))
178 178 finally:
179 179 ui.setconfig('ui', 'forcemerge', '')
180 180 updatedirstate(repo, rev, target, p2)
181 181 if not collapsef:
182 182 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
183 183 else:
184 184 # Skip commit if we are collapsing
185 185 repo.dirstate.setparents(repo[p1].node())
186 186 newrev = None
187 187 # Update the state
188 188 if newrev is not None:
189 189 state[rev] = repo[newrev].rev()
190 190 else:
191 191 if not collapsef:
192 192 ui.note(_('no changes, revision %d skipped\n') % rev)
193 193 ui.debug('next revision set to %s\n' % p1)
194 194 skipped.add(rev)
195 195 state[rev] = p1
196 196
197 197 ui.progress(_('rebasing'), None)
198 198 ui.note(_('rebase merging completed\n'))
199 199
200 200 if collapsef and not keepopen:
201 201 p1, p2 = defineparents(repo, min(state), target,
202 202 state, targetancestors)
203 203 if collapsemsg:
204 204 commitmsg = collapsemsg
205 205 else:
206 206 commitmsg = 'Collapsed revision'
207 207 for rebased in state:
208 208 if rebased not in skipped and state[rebased] != nullmerge:
209 209 commitmsg += '\n* %s' % repo[rebased].description()
210 210 commitmsg = ui.edit(commitmsg, repo.ui.username())
211 211 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
212 212 extrafn=extrafn)
213 213
214 214 if 'qtip' in repo.tags():
215 215 updatemq(repo, state, skipped, **opts)
216 216
217 217 if not keepf:
218 218 # Remove no more useful revisions
219 219 rebased = [rev for rev in state if state[rev] != nullmerge]
220 220 if rebased:
221 221 if set(repo.changelog.descendants(min(rebased))) - set(state):
222 222 ui.warn(_("warning: new changesets detected "
223 223 "on source branch, not stripping\n"))
224 224 else:
225 225 # backup the old csets by default
226 226 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
227 227
228 228 clearstatus(repo)
229 229 ui.note(_("rebase completed\n"))
230 230 if os.path.exists(repo.sjoin('undo')):
231 231 util.unlinkpath(repo.sjoin('undo'))
232 232 if skipped:
233 233 ui.note(_("%d revisions have been skipped\n") % len(skipped))
234 234 finally:
235 235 release(lock, wlock)
236 236
237 237 def checkexternal(repo, state, targetancestors):
238 238 """Check whether one or more external revisions need to be taken in
239 239 consideration. In the latter case, abort.
240 240 """
241 241 external = nullrev
242 242 source = min(state)
243 243 for rev in state:
244 244 if rev == source:
245 245 continue
246 246 # Check externals and fail if there are more than one
247 247 for p in repo[rev].parents():
248 248 if (p.rev() not in state
249 249 and p.rev() not in targetancestors):
250 250 if external != nullrev:
251 251 raise util.Abort(_('unable to collapse, there is more '
252 252 'than one external parent'))
253 253 external = p.rev()
254 254 return external
255 255
256 256 def updatedirstate(repo, rev, p1, p2):
257 257 """Keep track of renamed files in the revision that is going to be rebased
258 258 """
259 259 # Here we simulate the copies and renames in the source changeset
260 260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
261 261 m1 = repo[rev].manifest()
262 262 m2 = repo[p1].manifest()
263 263 for k, v in cop.iteritems():
264 264 if k in m1:
265 265 if v in m1 or v in m2:
266 266 repo.dirstate.copy(v, k)
267 267 if v in m2 and v not in m1 and k in m2:
268 268 repo.dirstate.remove(v)
269 269
270 270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
271 271 'Commit the changes and store useful information in extra'
272 272 try:
273 273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
274 274 ctx = repo[rev]
275 275 if commitmsg is None:
276 276 commitmsg = ctx.description()
277 277 extra = {'rebase_source': ctx.hex()}
278 278 if extrafn:
279 279 extrafn(ctx, extra)
280 280 # Commit might fail if unresolved files exist
281 281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
282 282 date=ctx.date(), extra=extra)
283 283 repo.dirstate.setbranch(repo[newrev].branch())
284 284 return newrev
285 285 except util.Abort:
286 286 # Invalidate the previous setparents
287 287 repo.dirstate.invalidate()
288 288 raise
289 289
290 def rebasenode(repo, rev, p1, p2, state):
290 def rebasenode(repo, rev, p1, state):
291 291 'Rebase a single revision'
292 292 # Merge phase
293 293 # Update to target and merge it with local
294 294 if repo['.'].rev() != repo[p1].rev():
295 295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
296 296 merge.update(repo, p1, False, True, False)
297 297 else:
298 298 repo.ui.debug(" already in target\n")
299 299 repo.dirstate.write()
300 300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
301 301 base = None
302 302 if repo[rev].rev() != repo[min(state)].rev():
303 303 base = repo[rev].p1().node()
304 304 return merge.update(repo, rev, True, True, False, base)
305 305
306 306 def defineparents(repo, rev, target, state, targetancestors):
307 307 'Return the new parent relationship of the revision that will be rebased'
308 308 parents = repo[rev].parents()
309 309 p1 = p2 = nullrev
310 310
311 311 P1n = parents[0].rev()
312 312 if P1n in targetancestors:
313 313 p1 = target
314 314 elif P1n in state:
315 315 if state[P1n] == nullmerge:
316 316 p1 = target
317 317 else:
318 318 p1 = state[P1n]
319 319 else: # P1n external
320 320 p1 = target
321 321 p2 = P1n
322 322
323 323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
324 324 P2n = parents[1].rev()
325 325 # interesting second parent
326 326 if P2n in state:
327 327 if p1 == target: # P1n in targetancestors or external
328 328 p1 = state[P2n]
329 329 else:
330 330 p2 = state[P2n]
331 331 else: # P2n external
332 332 if p2 != nullrev: # P1n external too => rev is a merged revision
333 333 raise util.Abort(_('cannot use revision %d as base, result '
334 334 'would have 3 parents') % rev)
335 335 p2 = P2n
336 336 repo.ui.debug(" future parents are %d and %d\n" %
337 337 (repo[p1].rev(), repo[p2].rev()))
338 338 return p1, p2
339 339
340 340 def isagitpatch(repo, patchname):
341 341 'Return true if the given patch is in git format'
342 342 mqpatch = os.path.join(repo.mq.path, patchname)
343 343 for line in patch.linereader(file(mqpatch, 'rb')):
344 344 if line.startswith('diff --git'):
345 345 return True
346 346 return False
347 347
348 348 def updatemq(repo, state, skipped, **opts):
349 349 'Update rebased mq patches - finalize and then import them'
350 350 mqrebase = {}
351 351 mq = repo.mq
352 352 original_series = mq.full_series[:]
353 353
354 354 for p in mq.applied:
355 355 rev = repo[p.node].rev()
356 356 if rev in state:
357 357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
358 358 (rev, p.name))
359 359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
360 360
361 361 if mqrebase:
362 362 mq.finish(repo, mqrebase.keys())
363 363
364 364 # We must start import from the newest revision
365 365 for rev in sorted(mqrebase, reverse=True):
366 366 if rev not in skipped:
367 367 name, isgit = mqrebase[rev]
368 368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
369 369 mq.qimport(repo, (), patchname=name, git=isgit,
370 370 rev=[str(state[rev])])
371 371
372 372 # Restore missing guards
373 373 for s in original_series:
374 374 pname = mq.guard_re.split(s, 1)[0]
375 375 if pname in mq.full_series:
376 376 repo.ui.debug('restoring guard for patch %s' % (pname))
377 377 mq.full_series.remove(pname)
378 378 mq.full_series.append(s)
379 379 mq.series_dirty = True
380 380 mq.save_dirty()
381 381
382 382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
383 383 external):
384 384 'Store the current status to allow recovery'
385 385 f = repo.opener("rebasestate", "w")
386 386 f.write(repo[originalwd].hex() + '\n')
387 387 f.write(repo[target].hex() + '\n')
388 388 f.write(repo[external].hex() + '\n')
389 389 f.write('%d\n' % int(collapse))
390 390 f.write('%d\n' % int(keep))
391 391 f.write('%d\n' % int(keepbranches))
392 392 for d, v in state.iteritems():
393 393 oldrev = repo[d].hex()
394 394 newrev = repo[v].hex()
395 395 f.write("%s:%s\n" % (oldrev, newrev))
396 396 f.close()
397 397 repo.ui.debug('rebase status stored\n')
398 398
399 399 def clearstatus(repo):
400 400 'Remove the status files'
401 401 if os.path.exists(repo.join("rebasestate")):
402 402 util.unlinkpath(repo.join("rebasestate"))
403 403
404 404 def restorestatus(repo):
405 405 'Restore a previously stored status'
406 406 try:
407 407 target = None
408 408 collapse = False
409 409 external = nullrev
410 410 state = {}
411 411 f = repo.opener("rebasestate")
412 412 for i, l in enumerate(f.read().splitlines()):
413 413 if i == 0:
414 414 originalwd = repo[l].rev()
415 415 elif i == 1:
416 416 target = repo[l].rev()
417 417 elif i == 2:
418 418 external = repo[l].rev()
419 419 elif i == 3:
420 420 collapse = bool(int(l))
421 421 elif i == 4:
422 422 keep = bool(int(l))
423 423 elif i == 5:
424 424 keepbranches = bool(int(l))
425 425 else:
426 426 oldrev, newrev = l.split(':')
427 427 state[repo[oldrev].rev()] = repo[newrev].rev()
428 428 skipped = set()
429 429 # recompute the set of skipped revs
430 430 if not collapse:
431 431 seen = set([target])
432 432 for old, new in sorted(state.items()):
433 433 if new != nullrev and new in seen:
434 434 skipped.add(old)
435 435 seen.add(new)
436 436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
437 437 repo.ui.debug('rebase status resumed\n')
438 438 return (originalwd, target, state, skipped,
439 439 collapse, keep, keepbranches, external)
440 440 except IOError, err:
441 441 if err.errno != errno.ENOENT:
442 442 raise
443 443 raise util.Abort(_('no rebase in progress'))
444 444
445 445 def abort(repo, originalwd, target, state):
446 446 'Restore the repository to its original state'
447 447 if set(repo.changelog.descendants(target)) - set(state.values()):
448 448 repo.ui.warn(_("warning: new changesets detected on target branch, "
449 449 "can't abort\n"))
450 450 return -1
451 451 else:
452 452 # Strip from the first rebased revision
453 453 merge.update(repo, repo[originalwd].rev(), False, True, False)
454 454 rebased = filter(lambda x: x > -1 and x != target, state.values())
455 455 if rebased:
456 456 strippoint = min(rebased)
457 457 # no backup of rebased cset versions needed
458 458 repair.strip(repo.ui, repo, repo[strippoint].node())
459 459 clearstatus(repo)
460 460 repo.ui.warn(_('rebase aborted\n'))
461 461 return 0
462 462
463 463 def buildstate(repo, dest, src, base, detach):
464 464 'Define which revisions are going to be rebased and where'
465 465 targetancestors = set()
466 466 detachset = set()
467 467
468 468 if not dest:
469 469 # Destination defaults to the latest revision in the current branch
470 470 branch = repo[None].branch()
471 471 dest = repo[branch].rev()
472 472 else:
473 473 dest = repo[dest].rev()
474 474
475 475 # This check isn't strictly necessary, since mq detects commits over an
476 476 # applied patch. But it prevents messing up the working directory when
477 477 # a partially completed rebase is blocked by mq.
478 478 if 'qtip' in repo.tags() and (repo[dest].node() in
479 479 [s.node for s in repo.mq.applied]):
480 480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
481 481
482 482 if src:
483 483 commonbase = repo[src].ancestor(repo[dest])
484 484 samebranch = repo[src].branch() == repo[dest].branch()
485 485 if commonbase == repo[src]:
486 486 raise util.Abort(_('source is ancestor of destination'))
487 487 if samebranch and commonbase == repo[dest]:
488 488 raise util.Abort(_('source is descendant of destination'))
489 489 source = repo[src].rev()
490 490 if detach:
491 491 # We need to keep track of source's ancestors up to the common base
492 492 srcancestors = set(repo.changelog.ancestors(source))
493 493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
494 494 detachset = srcancestors - baseancestors
495 495 detachset.discard(commonbase.rev())
496 496 else:
497 497 if base:
498 498 cwd = repo[base].rev()
499 499 else:
500 500 cwd = repo['.'].rev()
501 501
502 502 if cwd == dest:
503 503 repo.ui.debug('source and destination are the same\n')
504 504 return None
505 505
506 506 targetancestors = set(repo.changelog.ancestors(dest))
507 507 if cwd in targetancestors:
508 508 repo.ui.debug('source is ancestor of destination\n')
509 509 return None
510 510
511 511 cwdancestors = set(repo.changelog.ancestors(cwd))
512 512 if dest in cwdancestors:
513 513 repo.ui.debug('source is descendant of destination\n')
514 514 return None
515 515
516 516 cwdancestors.add(cwd)
517 517 rebasingbranch = cwdancestors - targetancestors
518 518 source = min(rebasingbranch)
519 519
520 520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
521 521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
522 522 state.update(dict.fromkeys(detachset, nullmerge))
523 523 state[source] = nullrev
524 524 return repo['.'].rev(), repo[dest].rev(), state
525 525
526 526 def pullrebase(orig, ui, repo, *args, **opts):
527 527 'Call rebase after pull if the latter has been invoked with --rebase'
528 528 if opts.get('rebase'):
529 529 if opts.get('update'):
530 530 del opts['update']
531 531 ui.debug('--update and --rebase are not compatible, ignoring '
532 532 'the update flag\n')
533 533
534 534 cmdutil.bail_if_changed(repo)
535 535 revsprepull = len(repo)
536 536 origpostincoming = commands.postincoming
537 537 def _dummy(*args, **kwargs):
538 538 pass
539 539 commands.postincoming = _dummy
540 540 try:
541 541 orig(ui, repo, *args, **opts)
542 542 finally:
543 543 commands.postincoming = origpostincoming
544 544 revspostpull = len(repo)
545 545 if revspostpull > revsprepull:
546 546 rebase(ui, repo, **opts)
547 547 branch = repo[None].branch()
548 548 dest = repo[branch].rev()
549 549 if dest != repo['.'].rev():
550 550 # there was nothing to rebase we force an update
551 551 hg.update(repo, dest)
552 552 else:
553 553 orig(ui, repo, *args, **opts)
554 554
555 555 def uisetup(ui):
556 556 'Replace pull with a decorator to provide --rebase option'
557 557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
558 558 entry[1].append(('', 'rebase', None,
559 559 _("rebase working directory to branch head"))
560 560 )
561 561
562 562 cmdtable = {
563 563 "rebase":
564 564 (rebase,
565 565 [
566 566 ('s', 'source', '',
567 567 _('rebase from the specified changeset'), _('REV')),
568 568 ('b', 'base', '',
569 569 _('rebase from the base of the specified changeset '
570 570 '(up to greatest common ancestor of base and dest)'),
571 571 _('REV')),
572 572 ('d', 'dest', '',
573 573 _('rebase onto the specified changeset'), _('REV')),
574 574 ('', 'collapse', False, _('collapse the rebased changesets')),
575 575 ('m', 'message', '',
576 576 _('use text as collapse commit message'), _('TEXT')),
577 577 ('l', 'logfile', '',
578 578 _('read collapse commit message from file'), _('FILE')),
579 579 ('', 'keep', False, _('keep original changesets')),
580 580 ('', 'keepbranches', False, _('keep original branch names')),
581 581 ('', 'detach', False, _('force detaching of source from its original '
582 582 'branch')),
583 583 ('t', 'tool', '', _('specify merge tool')),
584 584 ('c', 'continue', False, _('continue an interrupted rebase')),
585 585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
586 586 templateopts,
587 587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
588 588 'hg rebase {-a|-c}'))
589 589 }
General Comments 0
You need to be logged in to leave comments. Login now