##// END OF EJS Templates
rebase: use merge's ancestor parameter
Matt Mackall -
r13875:ff3c683e default
parent child Browse files
Show More
@@ -1,607 +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 174 stats = rebasenode(repo, rev, p1, p2, 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 def rebasemerge(repo, rev, first=False):
238 'return the correct ancestor'
239 oldancestor = ancestor.ancestor
240
241 def newancestor(a, b, pfunc):
242 if b == rev:
243 return repo[rev].parents()[0].rev()
244 return oldancestor(a, b, pfunc)
245
246 if not first:
247 ancestor.ancestor = newancestor
248 else:
249 repo.ui.debug("first revision, do not change ancestor\n")
250 try:
251 stats = merge.update(repo, rev, True, True, False)
252 return stats
253 finally:
254 ancestor.ancestor = oldancestor
255
256 237 def checkexternal(repo, state, targetancestors):
257 238 """Check whether one or more external revisions need to be taken in
258 239 consideration. In the latter case, abort.
259 240 """
260 241 external = nullrev
261 242 source = min(state)
262 243 for rev in state:
263 244 if rev == source:
264 245 continue
265 246 # Check externals and fail if there are more than one
266 247 for p in repo[rev].parents():
267 248 if (p.rev() not in state
268 249 and p.rev() not in targetancestors):
269 250 if external != nullrev:
270 251 raise util.Abort(_('unable to collapse, there is more '
271 252 'than one external parent'))
272 253 external = p.rev()
273 254 return external
274 255
275 256 def updatedirstate(repo, rev, p1, p2):
276 257 """Keep track of renamed files in the revision that is going to be rebased
277 258 """
278 259 # Here we simulate the copies and renames in the source changeset
279 260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
280 261 m1 = repo[rev].manifest()
281 262 m2 = repo[p1].manifest()
282 263 for k, v in cop.iteritems():
283 264 if k in m1:
284 265 if v in m1 or v in m2:
285 266 repo.dirstate.copy(v, k)
286 267 if v in m2 and v not in m1 and k in m2:
287 268 repo.dirstate.remove(v)
288 269
289 270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
290 271 'Commit the changes and store useful information in extra'
291 272 try:
292 273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
293 274 ctx = repo[rev]
294 275 if commitmsg is None:
295 276 commitmsg = ctx.description()
296 277 extra = {'rebase_source': ctx.hex()}
297 278 if extrafn:
298 279 extrafn(ctx, extra)
299 280 # Commit might fail if unresolved files exist
300 281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
301 282 date=ctx.date(), extra=extra)
302 283 repo.dirstate.setbranch(repo[newrev].branch())
303 284 return newrev
304 285 except util.Abort:
305 286 # Invalidate the previous setparents
306 287 repo.dirstate.invalidate()
307 288 raise
308 289
309 290 def rebasenode(repo, rev, p1, p2, state):
310 291 'Rebase a single revision'
311 292 # Merge phase
312 293 # Update to target and merge it with local
313 294 if repo['.'].rev() != repo[p1].rev():
314 295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
315 296 merge.update(repo, p1, False, True, False)
316 297 else:
317 298 repo.ui.debug(" already in target\n")
318 299 repo.dirstate.write()
319 300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
320 first = repo[rev].rev() == repo[min(state)].rev()
321 stats = rebasemerge(repo, rev, first)
322 return stats
301 base = None
302 if repo[rev].rev() != repo[min(state)].rev():
303 base = repo[rev].parents()[0].node()
304 return merge.update(repo, rev, True, True, False, base)
323 305
324 306 def defineparents(repo, rev, target, state, targetancestors):
325 307 'Return the new parent relationship of the revision that will be rebased'
326 308 parents = repo[rev].parents()
327 309 p1 = p2 = nullrev
328 310
329 311 P1n = parents[0].rev()
330 312 if P1n in targetancestors:
331 313 p1 = target
332 314 elif P1n in state:
333 315 if state[P1n] == nullmerge:
334 316 p1 = target
335 317 else:
336 318 p1 = state[P1n]
337 319 else: # P1n external
338 320 p1 = target
339 321 p2 = P1n
340 322
341 323 if len(parents) == 2 and parents[1].rev() not in targetancestors:
342 324 P2n = parents[1].rev()
343 325 # interesting second parent
344 326 if P2n in state:
345 327 if p1 == target: # P1n in targetancestors or external
346 328 p1 = state[P2n]
347 329 else:
348 330 p2 = state[P2n]
349 331 else: # P2n external
350 332 if p2 != nullrev: # P1n external too => rev is a merged revision
351 333 raise util.Abort(_('cannot use revision %d as base, result '
352 334 'would have 3 parents') % rev)
353 335 p2 = P2n
354 336 repo.ui.debug(" future parents are %d and %d\n" %
355 337 (repo[p1].rev(), repo[p2].rev()))
356 338 return p1, p2
357 339
358 340 def isagitpatch(repo, patchname):
359 341 'Return true if the given patch is in git format'
360 342 mqpatch = os.path.join(repo.mq.path, patchname)
361 343 for line in patch.linereader(file(mqpatch, 'rb')):
362 344 if line.startswith('diff --git'):
363 345 return True
364 346 return False
365 347
366 348 def updatemq(repo, state, skipped, **opts):
367 349 'Update rebased mq patches - finalize and then import them'
368 350 mqrebase = {}
369 351 mq = repo.mq
370 352 original_series = mq.full_series[:]
371 353
372 354 for p in mq.applied:
373 355 rev = repo[p.node].rev()
374 356 if rev in state:
375 357 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
376 358 (rev, p.name))
377 359 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
378 360
379 361 if mqrebase:
380 362 mq.finish(repo, mqrebase.keys())
381 363
382 364 # We must start import from the newest revision
383 365 for rev in sorted(mqrebase, reverse=True):
384 366 if rev not in skipped:
385 367 name, isgit = mqrebase[rev]
386 368 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
387 369 mq.qimport(repo, (), patchname=name, git=isgit,
388 370 rev=[str(state[rev])])
389 371
390 372 # Restore missing guards
391 373 for s in original_series:
392 374 pname = mq.guard_re.split(s, 1)[0]
393 375 if pname in mq.full_series:
394 376 repo.ui.debug('restoring guard for patch %s' % (pname))
395 377 mq.full_series.remove(pname)
396 378 mq.full_series.append(s)
397 379 mq.series_dirty = True
398 380 mq.save_dirty()
399 381
400 382 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
401 383 external):
402 384 'Store the current status to allow recovery'
403 385 f = repo.opener("rebasestate", "w")
404 386 f.write(repo[originalwd].hex() + '\n')
405 387 f.write(repo[target].hex() + '\n')
406 388 f.write(repo[external].hex() + '\n')
407 389 f.write('%d\n' % int(collapse))
408 390 f.write('%d\n' % int(keep))
409 391 f.write('%d\n' % int(keepbranches))
410 392 for d, v in state.iteritems():
411 393 oldrev = repo[d].hex()
412 394 newrev = repo[v].hex()
413 395 f.write("%s:%s\n" % (oldrev, newrev))
414 396 f.close()
415 397 repo.ui.debug('rebase status stored\n')
416 398
417 399 def clearstatus(repo):
418 400 'Remove the status files'
419 401 if os.path.exists(repo.join("rebasestate")):
420 402 util.unlinkpath(repo.join("rebasestate"))
421 403
422 404 def restorestatus(repo):
423 405 'Restore a previously stored status'
424 406 try:
425 407 target = None
426 408 collapse = False
427 409 external = nullrev
428 410 state = {}
429 411 f = repo.opener("rebasestate")
430 412 for i, l in enumerate(f.read().splitlines()):
431 413 if i == 0:
432 414 originalwd = repo[l].rev()
433 415 elif i == 1:
434 416 target = repo[l].rev()
435 417 elif i == 2:
436 418 external = repo[l].rev()
437 419 elif i == 3:
438 420 collapse = bool(int(l))
439 421 elif i == 4:
440 422 keep = bool(int(l))
441 423 elif i == 5:
442 424 keepbranches = bool(int(l))
443 425 else:
444 426 oldrev, newrev = l.split(':')
445 427 state[repo[oldrev].rev()] = repo[newrev].rev()
446 428 skipped = set()
447 429 # recompute the set of skipped revs
448 430 if not collapse:
449 431 seen = set([target])
450 432 for old, new in sorted(state.items()):
451 433 if new != nullrev and new in seen:
452 434 skipped.add(old)
453 435 seen.add(new)
454 436 repo.ui.debug('computed skipped revs: %s\n' % skipped)
455 437 repo.ui.debug('rebase status resumed\n')
456 438 return (originalwd, target, state, skipped,
457 439 collapse, keep, keepbranches, external)
458 440 except IOError, err:
459 441 if err.errno != errno.ENOENT:
460 442 raise
461 443 raise util.Abort(_('no rebase in progress'))
462 444
463 445 def abort(repo, originalwd, target, state):
464 446 'Restore the repository to its original state'
465 447 if set(repo.changelog.descendants(target)) - set(state.values()):
466 448 repo.ui.warn(_("warning: new changesets detected on target branch, "
467 449 "can't abort\n"))
468 450 return -1
469 451 else:
470 452 # Strip from the first rebased revision
471 453 merge.update(repo, repo[originalwd].rev(), False, True, False)
472 454 rebased = filter(lambda x: x > -1 and x != target, state.values())
473 455 if rebased:
474 456 strippoint = min(rebased)
475 457 # no backup of rebased cset versions needed
476 458 repair.strip(repo.ui, repo, repo[strippoint].node())
477 459 clearstatus(repo)
478 460 repo.ui.warn(_('rebase aborted\n'))
479 461 return 0
480 462
481 463 def buildstate(repo, dest, src, base, detach):
482 464 'Define which revisions are going to be rebased and where'
483 465 targetancestors = set()
484 466 detachset = set()
485 467
486 468 if not dest:
487 469 # Destination defaults to the latest revision in the current branch
488 470 branch = repo[None].branch()
489 471 dest = repo[branch].rev()
490 472 else:
491 473 dest = repo[dest].rev()
492 474
493 475 # This check isn't strictly necessary, since mq detects commits over an
494 476 # applied patch. But it prevents messing up the working directory when
495 477 # a partially completed rebase is blocked by mq.
496 478 if 'qtip' in repo.tags() and (repo[dest].node() in
497 479 [s.node for s in repo.mq.applied]):
498 480 raise util.Abort(_('cannot rebase onto an applied mq patch'))
499 481
500 482 if src:
501 483 commonbase = repo[src].ancestor(repo[dest])
502 484 samebranch = repo[src].branch() == repo[dest].branch()
503 485 if commonbase == repo[src]:
504 486 raise util.Abort(_('source is ancestor of destination'))
505 487 if samebranch and commonbase == repo[dest]:
506 488 raise util.Abort(_('source is descendant of destination'))
507 489 source = repo[src].rev()
508 490 if detach:
509 491 # We need to keep track of source's ancestors up to the common base
510 492 srcancestors = set(repo.changelog.ancestors(source))
511 493 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
512 494 detachset = srcancestors - baseancestors
513 495 detachset.discard(commonbase.rev())
514 496 else:
515 497 if base:
516 498 cwd = repo[base].rev()
517 499 else:
518 500 cwd = repo['.'].rev()
519 501
520 502 if cwd == dest:
521 503 repo.ui.debug('source and destination are the same\n')
522 504 return None
523 505
524 506 targetancestors = set(repo.changelog.ancestors(dest))
525 507 if cwd in targetancestors:
526 508 repo.ui.debug('source is ancestor of destination\n')
527 509 return None
528 510
529 511 cwdancestors = set(repo.changelog.ancestors(cwd))
530 512 if dest in cwdancestors:
531 513 repo.ui.debug('source is descendant of destination\n')
532 514 return None
533 515
534 516 cwdancestors.add(cwd)
535 517 rebasingbranch = cwdancestors - targetancestors
536 518 source = min(rebasingbranch)
537 519
538 520 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
539 521 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
540 522 state.update(dict.fromkeys(detachset, nullmerge))
541 523 state[source] = nullrev
542 524 return repo['.'].rev(), repo[dest].rev(), state
543 525
544 526 def pullrebase(orig, ui, repo, *args, **opts):
545 527 'Call rebase after pull if the latter has been invoked with --rebase'
546 528 if opts.get('rebase'):
547 529 if opts.get('update'):
548 530 del opts['update']
549 531 ui.debug('--update and --rebase are not compatible, ignoring '
550 532 'the update flag\n')
551 533
552 534 cmdutil.bail_if_changed(repo)
553 535 revsprepull = len(repo)
554 536 origpostincoming = commands.postincoming
555 537 def _dummy(*args, **kwargs):
556 538 pass
557 539 commands.postincoming = _dummy
558 540 try:
559 541 orig(ui, repo, *args, **opts)
560 542 finally:
561 543 commands.postincoming = origpostincoming
562 544 revspostpull = len(repo)
563 545 if revspostpull > revsprepull:
564 546 rebase(ui, repo, **opts)
565 547 branch = repo[None].branch()
566 548 dest = repo[branch].rev()
567 549 if dest != repo['.'].rev():
568 550 # there was nothing to rebase we force an update
569 551 hg.update(repo, dest)
570 552 else:
571 553 orig(ui, repo, *args, **opts)
572 554
573 555 def uisetup(ui):
574 556 'Replace pull with a decorator to provide --rebase option'
575 557 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
576 558 entry[1].append(('', 'rebase', None,
577 559 _("rebase working directory to branch head"))
578 560 )
579 561
580 562 cmdtable = {
581 563 "rebase":
582 564 (rebase,
583 565 [
584 566 ('s', 'source', '',
585 567 _('rebase from the specified changeset'), _('REV')),
586 568 ('b', 'base', '',
587 569 _('rebase from the base of the specified changeset '
588 570 '(up to greatest common ancestor of base and dest)'),
589 571 _('REV')),
590 572 ('d', 'dest', '',
591 573 _('rebase onto the specified changeset'), _('REV')),
592 574 ('', 'collapse', False, _('collapse the rebased changesets')),
593 575 ('m', 'message', '',
594 576 _('use text as collapse commit message'), _('TEXT')),
595 577 ('l', 'logfile', '',
596 578 _('read collapse commit message from file'), _('FILE')),
597 579 ('', 'keep', False, _('keep original changesets')),
598 580 ('', 'keepbranches', False, _('keep original branch names')),
599 581 ('', 'detach', False, _('force detaching of source from its original '
600 582 'branch')),
601 583 ('t', 'tool', '', _('specify merge tool')),
602 584 ('c', 'continue', False, _('continue an interrupted rebase')),
603 585 ('a', 'abort', False, _('abort an interrupted rebase'))] +
604 586 templateopts,
605 587 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
606 588 'hg rebase {-a|-c}'))
607 589 }
General Comments 0
You need to be logged in to leave comments. Login now