##// END OF EJS Templates
Merge with stable
Martin Geisler -
r11189:3ef2572d merge default
parent child Browse files
Show More
@@ -1,551 +1,556 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, error
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 useful for linearizing local changes relative to a master
32 useful for linearizing *local* changes relative to a master
33 33 development tree.
34 34
35 You should not rebase changesets that have already been shared
36 with others. Doing so will force everybody else to perform the
37 same rebase or they will end up with duplicated changesets after
38 pulling in your rebased changesets.
39
35 40 If you don't specify a destination changeset (``-d/--dest``),
36 41 rebase uses the tipmost head of the current named branch as the
37 42 destination. (The destination changeset is not modified by
38 43 rebasing, but new changesets are added as its descendants.)
39 44
40 45 You can specify which changesets to rebase in two ways: as a
41 46 "source" changeset or as a "base" changeset. Both are shorthand
42 47 for a topologically related set of changesets (the "source
43 48 branch"). If you specify source (``-s/--source``), rebase will
44 49 rebase that changeset and all of its descendants onto dest. If you
45 50 specify base (``-b/--base``), rebase will select ancestors of base
46 51 back to but not including the common ancestor with dest. Thus,
47 52 ``-b`` is less precise but more convenient than ``-s``: you can
48 53 specify any changeset in the source branch, and rebase will select
49 54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
50 55 uses the parent of the working directory as the base.
51 56
52 57 By default, rebase recreates the changesets in the source branch
53 58 as descendants of dest and then destroys the originals. Use
54 59 ``--keep`` to preserve the original source changesets. Some
55 60 changesets in the source branch (e.g. merges from the destination
56 61 branch) may be dropped if they no longer contribute any change.
57 62
58 63 One result of the rules for selecting the destination changeset
59 64 and source branch is that, unlike ``merge``, rebase will do
60 65 nothing if you are at the latest (tipmost) head of a named branch
61 66 with two heads. You need to explicitly specify source and/or
62 67 destination (or ``update`` to the other head, if it's the head of
63 68 the intended source branch).
64 69
65 70 If a rebase is interrupted to manually resolve a merge, it can be
66 71 continued with --continue/-c or aborted with --abort/-a.
67 72 """
68 73 originalwd = target = None
69 74 external = nullrev
70 75 state = {}
71 76 skipped = set()
72 77 targetancestors = set()
73 78
74 79 lock = wlock = None
75 80 try:
76 81 lock = repo.lock()
77 82 wlock = repo.wlock()
78 83
79 84 # Validate input and define rebasing points
80 85 destf = opts.get('dest', None)
81 86 srcf = opts.get('source', None)
82 87 basef = opts.get('base', None)
83 88 contf = opts.get('continue')
84 89 abortf = opts.get('abort')
85 90 collapsef = opts.get('collapse', False)
86 91 extrafn = opts.get('extrafn')
87 92 keepf = opts.get('keep', False)
88 93 keepbranchesf = opts.get('keepbranches', False)
89 94 detachf = opts.get('detach', False)
90 95 # keepopen is not meant for use on the command line, but by
91 96 # other extensions
92 97 keepopen = opts.get('keepopen', False)
93 98
94 99 if contf or abortf:
95 100 if contf and abortf:
96 101 raise error.ParseError('rebase',
97 102 _('cannot use both abort and continue'))
98 103 if collapsef:
99 104 raise error.ParseError(
100 105 'rebase', _('cannot use collapse with continue or abort'))
101 106
102 107 if detachf:
103 108 raise error.ParseError(
104 109 'rebase', _('cannot use detach with continue or abort'))
105 110
106 111 if srcf or basef or destf:
107 112 raise error.ParseError('rebase',
108 113 _('abort and continue do not allow specifying revisions'))
109 114
110 115 (originalwd, target, state, collapsef, keepf,
111 116 keepbranchesf, external) = restorestatus(repo)
112 117 if abortf:
113 118 abort(repo, originalwd, target, state)
114 119 return
115 120 else:
116 121 if srcf and basef:
117 122 raise error.ParseError('rebase', _('cannot specify both a '
118 123 'revision and a base'))
119 124 if detachf:
120 125 if not srcf:
121 126 raise error.ParseError(
122 127 'rebase', _('detach requires a revision to be specified'))
123 128 if basef:
124 129 raise error.ParseError(
125 130 'rebase', _('cannot specify a base with detach'))
126 131
127 132 cmdutil.bail_if_changed(repo)
128 133 result = buildstate(repo, destf, srcf, basef, detachf)
129 134 if not result:
130 135 # Empty state built, nothing to rebase
131 136 ui.status(_('nothing to rebase\n'))
132 137 return
133 138 else:
134 139 originalwd, target, state = result
135 140 if collapsef:
136 141 targetancestors = set(repo.changelog.ancestors(target))
137 142 external = checkexternal(repo, state, targetancestors)
138 143
139 144 if keepbranchesf:
140 145 if extrafn:
141 146 raise error.ParseError(
142 147 'rebase', _('cannot use both keepbranches and extrafn'))
143 148 def extrafn(ctx, extra):
144 149 extra['branch'] = ctx.branch()
145 150
146 151 # Rebase
147 152 if not targetancestors:
148 153 targetancestors = set(repo.changelog.ancestors(target))
149 154 targetancestors.add(target)
150 155
151 156 for rev in sorted(state):
152 157 if state[rev] == -1:
153 158 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
154 159 storestatus(repo, originalwd, target, state, collapsef, keepf,
155 160 keepbranchesf, external)
156 161 p1, p2 = defineparents(repo, rev, target, state,
157 162 targetancestors)
158 163 if len(repo.parents()) == 2:
159 164 repo.ui.debug('resuming interrupted rebase\n')
160 165 else:
161 166 stats = rebasenode(repo, rev, p1, p2, state)
162 167 if stats and stats[3] > 0:
163 168 raise util.Abort(_('fix unresolved conflicts with hg '
164 169 'resolve then run hg rebase --continue'))
165 170 updatedirstate(repo, rev, target, p2)
166 171 if not collapsef:
167 172 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
168 173 else:
169 174 # Skip commit if we are collapsing
170 175 repo.dirstate.setparents(repo[p1].node())
171 176 newrev = None
172 177 # Update the state
173 178 if newrev is not None:
174 179 state[rev] = repo[newrev].rev()
175 180 else:
176 181 if not collapsef:
177 182 ui.note(_('no changes, revision %d skipped\n') % rev)
178 183 ui.debug('next revision set to %s\n' % p1)
179 184 skipped.add(rev)
180 185 state[rev] = p1
181 186
182 187 ui.note(_('rebase merging completed\n'))
183 188
184 189 if collapsef and not keepopen:
185 190 p1, p2 = defineparents(repo, min(state), target,
186 191 state, targetancestors)
187 192 commitmsg = 'Collapsed revision'
188 193 for rebased in state:
189 194 if rebased not in skipped and state[rebased] != nullmerge:
190 195 commitmsg += '\n* %s' % repo[rebased].description()
191 196 commitmsg = ui.edit(commitmsg, repo.ui.username())
192 197 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
193 198 extrafn=extrafn)
194 199
195 200 if 'qtip' in repo.tags():
196 201 updatemq(repo, state, skipped, **opts)
197 202
198 203 if not keepf:
199 204 # Remove no more useful revisions
200 205 rebased = [rev for rev in state if state[rev] != nullmerge]
201 206 if rebased:
202 207 if set(repo.changelog.descendants(min(rebased))) - set(state):
203 208 ui.warn(_("warning: new changesets detected "
204 209 "on source branch, not stripping\n"))
205 210 else:
206 211 repair.strip(ui, repo, repo[min(rebased)].node(), "strip")
207 212
208 213 clearstatus(repo)
209 214 ui.status(_("rebase completed\n"))
210 215 if os.path.exists(repo.sjoin('undo')):
211 216 util.unlink(repo.sjoin('undo'))
212 217 if skipped:
213 218 ui.note(_("%d revisions have been skipped\n") % len(skipped))
214 219 finally:
215 220 release(lock, wlock)
216 221
217 222 def rebasemerge(repo, rev, first=False):
218 223 'return the correct ancestor'
219 224 oldancestor = ancestor.ancestor
220 225
221 226 def newancestor(a, b, pfunc):
222 227 if b == rev:
223 228 return repo[rev].parents()[0].rev()
224 229 return oldancestor(a, b, pfunc)
225 230
226 231 if not first:
227 232 ancestor.ancestor = newancestor
228 233 else:
229 234 repo.ui.debug("first revision, do not change ancestor\n")
230 235 try:
231 236 stats = merge.update(repo, rev, True, True, False)
232 237 return stats
233 238 finally:
234 239 ancestor.ancestor = oldancestor
235 240
236 241 def checkexternal(repo, state, targetancestors):
237 242 """Check whether one or more external revisions need to be taken in
238 243 consideration. In the latter case, abort.
239 244 """
240 245 external = nullrev
241 246 source = min(state)
242 247 for rev in state:
243 248 if rev == source:
244 249 continue
245 250 # Check externals and fail if there are more than one
246 251 for p in repo[rev].parents():
247 252 if (p.rev() not in state
248 253 and p.rev() not in targetancestors):
249 254 if external != nullrev:
250 255 raise util.Abort(_('unable to collapse, there is more '
251 256 'than one external parent'))
252 257 external = p.rev()
253 258 return external
254 259
255 260 def updatedirstate(repo, rev, p1, p2):
256 261 """Keep track of renamed files in the revision that is going to be rebased
257 262 """
258 263 # Here we simulate the copies and renames in the source changeset
259 264 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
260 265 m1 = repo[rev].manifest()
261 266 m2 = repo[p1].manifest()
262 267 for k, v in cop.iteritems():
263 268 if k in m1:
264 269 if v in m1 or v in m2:
265 270 repo.dirstate.copy(v, k)
266 271 if v in m2 and v not in m1:
267 272 repo.dirstate.remove(v)
268 273
269 274 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
270 275 'Commit the changes and store useful information in extra'
271 276 try:
272 277 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
273 278 if commitmsg is None:
274 279 commitmsg = repo[rev].description()
275 280 ctx = repo[rev]
276 281 extra = {'rebase_source': ctx.hex()}
277 282 if extrafn:
278 283 extrafn(ctx, extra)
279 284 # Commit might fail if unresolved files exist
280 285 newrev = repo.commit(text=commitmsg, user=ctx.user(),
281 286 date=ctx.date(), extra=extra)
282 287 repo.dirstate.setbranch(repo[newrev].branch())
283 288 return newrev
284 289 except util.Abort:
285 290 # Invalidate the previous setparents
286 291 repo.dirstate.invalidate()
287 292 raise
288 293
289 294 def rebasenode(repo, rev, p1, p2, state):
290 295 'Rebase a single revision'
291 296 # Merge phase
292 297 # Update to target and merge it with local
293 298 if repo['.'].rev() != repo[p1].rev():
294 299 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
295 300 merge.update(repo, p1, False, True, False)
296 301 else:
297 302 repo.ui.debug(" already in target\n")
298 303 repo.dirstate.write()
299 304 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
300 305 first = repo[rev].rev() == repo[min(state)].rev()
301 306 stats = rebasemerge(repo, rev, first)
302 307 return stats
303 308
304 309 def defineparents(repo, rev, target, state, targetancestors):
305 310 'Return the new parent relationship of the revision that will be rebased'
306 311 parents = repo[rev].parents()
307 312 p1 = p2 = nullrev
308 313
309 314 P1n = parents[0].rev()
310 315 if P1n in targetancestors:
311 316 p1 = target
312 317 elif P1n in state:
313 318 if state[P1n] == nullmerge:
314 319 p1 = target
315 320 else:
316 321 p1 = state[P1n]
317 322 else: # P1n external
318 323 p1 = target
319 324 p2 = P1n
320 325
321 326 if len(parents) == 2 and parents[1].rev() not in targetancestors:
322 327 P2n = parents[1].rev()
323 328 # interesting second parent
324 329 if P2n in state:
325 330 if p1 == target: # P1n in targetancestors or external
326 331 p1 = state[P2n]
327 332 else:
328 333 p2 = state[P2n]
329 334 else: # P2n external
330 335 if p2 != nullrev: # P1n external too => rev is a merged revision
331 336 raise util.Abort(_('cannot use revision %d as base, result '
332 337 'would have 3 parents') % rev)
333 338 p2 = P2n
334 339 repo.ui.debug(" future parents are %d and %d\n" %
335 340 (repo[p1].rev(), repo[p2].rev()))
336 341 return p1, p2
337 342
338 343 def isagitpatch(repo, patchname):
339 344 'Return true if the given patch is in git format'
340 345 mqpatch = os.path.join(repo.mq.path, patchname)
341 346 for line in patch.linereader(file(mqpatch, 'rb')):
342 347 if line.startswith('diff --git'):
343 348 return True
344 349 return False
345 350
346 351 def updatemq(repo, state, skipped, **opts):
347 352 'Update rebased mq patches - finalize and then import them'
348 353 mqrebase = {}
349 354 for p in repo.mq.applied:
350 355 if repo[p.node].rev() in state:
351 356 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
352 357 (repo[p.node].rev(), p.name))
353 358 mqrebase[repo[p.node].rev()] = (p.name, isagitpatch(repo, p.name))
354 359
355 360 if mqrebase:
356 361 repo.mq.finish(repo, mqrebase.keys())
357 362
358 363 # We must start import from the newest revision
359 364 for rev in sorted(mqrebase, reverse=True):
360 365 if rev not in skipped:
361 366 repo.ui.debug('import mq patch %d (%s)\n'
362 367 % (state[rev], mqrebase[rev][0]))
363 368 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
364 369 git=mqrebase[rev][1],rev=[str(state[rev])])
365 370 repo.mq.save_dirty()
366 371
367 372 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
368 373 external):
369 374 'Store the current status to allow recovery'
370 375 f = repo.opener("rebasestate", "w")
371 376 f.write(repo[originalwd].hex() + '\n')
372 377 f.write(repo[target].hex() + '\n')
373 378 f.write(repo[external].hex() + '\n')
374 379 f.write('%d\n' % int(collapse))
375 380 f.write('%d\n' % int(keep))
376 381 f.write('%d\n' % int(keepbranches))
377 382 for d, v in state.iteritems():
378 383 oldrev = repo[d].hex()
379 384 newrev = repo[v].hex()
380 385 f.write("%s:%s\n" % (oldrev, newrev))
381 386 f.close()
382 387 repo.ui.debug('rebase status stored\n')
383 388
384 389 def clearstatus(repo):
385 390 'Remove the status files'
386 391 if os.path.exists(repo.join("rebasestate")):
387 392 util.unlink(repo.join("rebasestate"))
388 393
389 394 def restorestatus(repo):
390 395 'Restore a previously stored status'
391 396 try:
392 397 target = None
393 398 collapse = False
394 399 external = nullrev
395 400 state = {}
396 401 f = repo.opener("rebasestate")
397 402 for i, l in enumerate(f.read().splitlines()):
398 403 if i == 0:
399 404 originalwd = repo[l].rev()
400 405 elif i == 1:
401 406 target = repo[l].rev()
402 407 elif i == 2:
403 408 external = repo[l].rev()
404 409 elif i == 3:
405 410 collapse = bool(int(l))
406 411 elif i == 4:
407 412 keep = bool(int(l))
408 413 elif i == 5:
409 414 keepbranches = bool(int(l))
410 415 else:
411 416 oldrev, newrev = l.split(':')
412 417 state[repo[oldrev].rev()] = repo[newrev].rev()
413 418 repo.ui.debug('rebase status resumed\n')
414 419 return originalwd, target, state, collapse, keep, keepbranches, external
415 420 except IOError, err:
416 421 if err.errno != errno.ENOENT:
417 422 raise
418 423 raise util.Abort(_('no rebase in progress'))
419 424
420 425 def abort(repo, originalwd, target, state):
421 426 'Restore the repository to its original state'
422 427 if set(repo.changelog.descendants(target)) - set(state.values()):
423 428 repo.ui.warn(_("warning: new changesets detected on target branch, "
424 429 "not stripping\n"))
425 430 else:
426 431 # Strip from the first rebased revision
427 432 merge.update(repo, repo[originalwd].rev(), False, True, False)
428 433 rebased = filter(lambda x: x > -1, state.values())
429 434 if rebased:
430 435 strippoint = min(rebased)
431 436 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
432 437 clearstatus(repo)
433 438 repo.ui.status(_('rebase aborted\n'))
434 439
435 440 def buildstate(repo, dest, src, base, detach):
436 441 'Define which revisions are going to be rebased and where'
437 442 targetancestors = set()
438 443 detachset = set()
439 444
440 445 if not dest:
441 446 # Destination defaults to the latest revision in the current branch
442 447 branch = repo[None].branch()
443 448 dest = repo[branch].rev()
444 449 else:
445 450 dest = repo[dest].rev()
446 451
447 452 # This check isn't strictly necessary, since mq detects commits over an
448 453 # applied patch. But it prevents messing up the working directory when
449 454 # a partially completed rebase is blocked by mq.
450 455 if 'qtip' in repo.tags() and (repo[dest].node() in
451 456 [s.node for s in repo.mq.applied]):
452 457 raise util.Abort(_('cannot rebase onto an applied mq patch'))
453 458
454 459 if src:
455 460 commonbase = repo[src].ancestor(repo[dest])
456 461 if commonbase == repo[src]:
457 462 raise util.Abort(_('source is ancestor of destination'))
458 463 if commonbase == repo[dest]:
459 464 raise util.Abort(_('source is descendant of destination'))
460 465 source = repo[src].rev()
461 466 if detach:
462 467 # We need to keep track of source's ancestors up to the common base
463 468 srcancestors = set(repo.changelog.ancestors(source))
464 469 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
465 470 detachset = srcancestors - baseancestors
466 471 detachset.remove(commonbase.rev())
467 472 else:
468 473 if base:
469 474 cwd = repo[base].rev()
470 475 else:
471 476 cwd = repo['.'].rev()
472 477
473 478 if cwd == dest:
474 479 repo.ui.debug('source and destination are the same\n')
475 480 return None
476 481
477 482 targetancestors = set(repo.changelog.ancestors(dest))
478 483 if cwd in targetancestors:
479 484 repo.ui.debug('source is ancestor of destination\n')
480 485 return None
481 486
482 487 cwdancestors = set(repo.changelog.ancestors(cwd))
483 488 if dest in cwdancestors:
484 489 repo.ui.debug('source is descendant of destination\n')
485 490 return None
486 491
487 492 cwdancestors.add(cwd)
488 493 rebasingbranch = cwdancestors - targetancestors
489 494 source = min(rebasingbranch)
490 495
491 496 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
492 497 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
493 498 state.update(dict.fromkeys(detachset, nullmerge))
494 499 state[source] = nullrev
495 500 return repo['.'].rev(), repo[dest].rev(), state
496 501
497 502 def pullrebase(orig, ui, repo, *args, **opts):
498 503 'Call rebase after pull if the latter has been invoked with --rebase'
499 504 if opts.get('rebase'):
500 505 if opts.get('update'):
501 506 del opts['update']
502 507 ui.debug('--update and --rebase are not compatible, ignoring '
503 508 'the update flag\n')
504 509
505 510 cmdutil.bail_if_changed(repo)
506 511 revsprepull = len(repo)
507 512 origpostincoming = commands.postincoming
508 513 def _dummy(*args, **kwargs):
509 514 pass
510 515 commands.postincoming = _dummy
511 516 try:
512 517 orig(ui, repo, *args, **opts)
513 518 finally:
514 519 commands.postincoming = origpostincoming
515 520 revspostpull = len(repo)
516 521 if revspostpull > revsprepull:
517 522 rebase(ui, repo, **opts)
518 523 branch = repo[None].branch()
519 524 dest = repo[branch].rev()
520 525 if dest != repo['.'].rev():
521 526 # there was nothing to rebase we force an update
522 527 hg.update(repo, dest)
523 528 else:
524 529 orig(ui, repo, *args, **opts)
525 530
526 531 def uisetup(ui):
527 532 'Replace pull with a decorator to provide --rebase option'
528 533 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
529 534 entry[1].append(('', 'rebase', None,
530 535 _("rebase working directory to branch head"))
531 536 )
532 537
533 538 cmdtable = {
534 539 "rebase":
535 540 (rebase,
536 541 [
537 542 ('s', 'source', '', _('rebase from the specified changeset')),
538 543 ('b', 'base', '', _('rebase from the base of the specified changeset '
539 544 '(up to greatest common ancestor of base and dest)')),
540 545 ('d', 'dest', '', _('rebase onto the specified changeset')),
541 546 ('', 'collapse', False, _('collapse the rebased changesets')),
542 547 ('', 'keep', False, _('keep original changesets')),
543 548 ('', 'keepbranches', False, _('keep original branch names')),
544 549 ('', 'detach', False, _('force detaching of source from its original '
545 550 'branch')),
546 551 ('c', 'continue', False, _('continue an interrupted rebase')),
547 552 ('a', 'abort', False, _('abort an interrupted rebase'))] +
548 553 templateopts,
549 554 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
550 555 'hg rebase {-a|-c}'))
551 556 }
@@ -1,382 +1,382 b''
1 1 # minirst.py - minimal reStructuredText parser
2 2 #
3 3 # Copyright 2009, 2010 Matt Mackall <mpm@selenic.com> and others
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 """simplified reStructuredText parser.
9 9
10 10 This parser knows just enough about reStructuredText to parse the
11 11 Mercurial docstrings.
12 12
13 13 It cheats in a major way: nested blocks are not really nested. They
14 14 are just indented blocks that look like they are nested. This relies
15 15 on the user to keep the right indentation for the blocks.
16 16
17 17 It only supports a small subset of reStructuredText:
18 18
19 19 - sections
20 20
21 21 - paragraphs
22 22
23 23 - literal blocks
24 24
25 25 - definition lists
26 26
27 27 - bullet lists (items must start with '-')
28 28
29 29 - enumerated lists (no autonumbering)
30 30
31 31 - field lists (colons cannot be escaped)
32 32
33 33 - option lists (supports only long options without arguments)
34 34
35 35 - inline literals (no other inline markup is not recognized)
36 36 """
37 37
38 38 import re, sys, textwrap
39 39
40 40
41 41 def findblocks(text):
42 42 """Find continuous blocks of lines in text.
43 43
44 44 Returns a list of dictionaries representing the blocks. Each block
45 45 has an 'indent' field and a 'lines' field.
46 46 """
47 47 blocks = [[]]
48 48 lines = text.splitlines()
49 49 for line in lines:
50 50 if line.strip():
51 51 blocks[-1].append(line)
52 52 elif blocks[-1]:
53 53 blocks.append([])
54 54 if not blocks[-1]:
55 55 del blocks[-1]
56 56
57 57 for i, block in enumerate(blocks):
58 58 indent = min((len(l) - len(l.lstrip())) for l in block)
59 59 blocks[i] = dict(indent=indent, lines=[l[indent:] for l in block])
60 60 return blocks
61 61
62 62
63 63 def findliteralblocks(blocks):
64 64 """Finds literal blocks and adds a 'type' field to the blocks.
65 65
66 66 Literal blocks are given the type 'literal', all other blocks are
67 67 given type the 'paragraph'.
68 68 """
69 69 i = 0
70 70 while i < len(blocks):
71 71 # Searching for a block that looks like this:
72 72 #
73 73 # +------------------------------+
74 74 # | paragraph |
75 75 # | (ends with "::") |
76 76 # +------------------------------+
77 77 # +---------------------------+
78 78 # | indented literal block |
79 79 # +---------------------------+
80 80 blocks[i]['type'] = 'paragraph'
81 81 if blocks[i]['lines'][-1].endswith('::') and i + 1 < len(blocks):
82 82 indent = blocks[i]['indent']
83 83 adjustment = blocks[i + 1]['indent'] - indent
84 84
85 85 if blocks[i]['lines'] == ['::']:
86 86 # Expanded form: remove block
87 87 del blocks[i]
88 88 i -= 1
89 89 elif blocks[i]['lines'][-1].endswith(' ::'):
90 90 # Partially minimized form: remove space and both
91 91 # colons.
92 92 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
93 93 else:
94 94 # Fully minimized form: remove just one colon.
95 95 blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1]
96 96
97 97 # List items are formatted with a hanging indent. We must
98 98 # correct for this here while we still have the original
99 99 # information on the indentation of the subsequent literal
100 100 # blocks available.
101 101 m = _bulletre.match(blocks[i]['lines'][0])
102 102 if m:
103 103 indent += m.end()
104 104 adjustment -= m.end()
105 105
106 106 # Mark the following indented blocks.
107 107 while i + 1 < len(blocks) and blocks[i + 1]['indent'] > indent:
108 108 blocks[i + 1]['type'] = 'literal'
109 109 blocks[i + 1]['indent'] -= adjustment
110 110 i += 1
111 111 i += 1
112 112 return blocks
113 113
114 114 _bulletre = re.compile(r'(-|[0-9A-Za-z]+\.|\(?[0-9A-Za-z]+\)|\|) ')
115 115 _optionre = re.compile(r'^(--[a-z-]+)((?:[ =][a-zA-Z][\w-]*)? +)(.*)$')
116 116 _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)')
117 117 _definitionre = re.compile(r'[^ ]')
118 118
119 119 def splitparagraphs(blocks):
120 120 """Split paragraphs into lists."""
121 121 # Tuples with (list type, item regexp, single line items?). Order
122 122 # matters: definition lists has the least specific regexp and must
123 123 # come last.
124 124 listtypes = [('bullet', _bulletre, True),
125 125 ('option', _optionre, True),
126 126 ('field', _fieldre, True),
127 127 ('definition', _definitionre, False)]
128 128
129 129 def match(lines, i, itemre, singleline):
130 130 """Does itemre match an item at line i?
131 131
132 132 A list item can be followed by an idented line or another list
133 133 item (but only if singleline is True).
134 134 """
135 135 line1 = lines[i]
136 136 line2 = i + 1 < len(lines) and lines[i + 1] or ''
137 137 if not itemre.match(line1):
138 138 return False
139 139 if singleline:
140 140 return line2 == '' or line2[0] == ' ' or itemre.match(line2)
141 141 else:
142 142 return line2.startswith(' ')
143 143
144 144 i = 0
145 145 while i < len(blocks):
146 146 if blocks[i]['type'] == 'paragraph':
147 147 lines = blocks[i]['lines']
148 148 for type, itemre, singleline in listtypes:
149 149 if match(lines, 0, itemre, singleline):
150 150 items = []
151 151 for j, line in enumerate(lines):
152 152 if match(lines, j, itemre, singleline):
153 153 items.append(dict(type=type, lines=[],
154 154 indent=blocks[i]['indent']))
155 155 items[-1]['lines'].append(line)
156 156 blocks[i:i + 1] = items
157 157 break
158 158 i += 1
159 159 return blocks
160 160
161 161
162 162 _fieldwidth = 12
163 163
164 164 def updatefieldlists(blocks):
165 165 """Find key and maximum key width for field lists."""
166 166 i = 0
167 167 while i < len(blocks):
168 168 if blocks[i]['type'] != 'field':
169 169 i += 1
170 170 continue
171 171
172 172 keywidth = 0
173 173 j = i
174 174 while j < len(blocks) and blocks[j]['type'] == 'field':
175 175 m = _fieldre.match(blocks[j]['lines'][0])
176 176 key, rest = m.groups()
177 177 blocks[j]['lines'][0] = rest
178 178 blocks[j]['key'] = key
179 179 keywidth = max(keywidth, len(key))
180 180 j += 1
181 181
182 182 for block in blocks[i:j]:
183 183 block['keywidth'] = keywidth
184 184 i = j + 1
185 185
186 186 return blocks
187 187
188 188
189 189 def prunecontainers(blocks, keep):
190 190 """Prune unwanted containers.
191 191
192 192 The blocks must have a 'type' field, i.e., they should have been
193 193 run through findliteralblocks first.
194 194 """
195 195 pruned = []
196 196 i = 0
197 197 while i + 1 < len(blocks):
198 198 # Searching for a block that looks like this:
199 199 #
200 200 # +-------+---------------------------+
201 201 # | ".. container ::" type |
202 202 # +---+ |
203 203 # | blocks |
204 204 # +-------------------------------+
205 205 if (blocks[i]['type'] == 'paragraph' and
206 206 blocks[i]['lines'][0].startswith('.. container::')):
207 207 indent = blocks[i]['indent']
208 208 adjustment = blocks[i + 1]['indent'] - indent
209 209 containertype = blocks[i]['lines'][0][15:]
210 210 prune = containertype not in keep
211 211 if prune:
212 212 pruned.append(containertype)
213 213
214 214 # Always delete "..container:: type" block
215 215 del blocks[i]
216 216 j = i
217 217 while j < len(blocks) and blocks[j]['indent'] > indent:
218 218 if prune:
219 219 del blocks[j]
220 220 i -= 1 # adjust outer index
221 221 else:
222 222 blocks[j]['indent'] -= adjustment
223 223 j += 1
224 224 i += 1
225 225 return blocks, pruned
226 226
227 227
228 228 _sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""")
229 229
230 230 def findsections(blocks):
231 231 """Finds sections.
232 232
233 233 The blocks must have a 'type' field, i.e., they should have been
234 234 run through findliteralblocks first.
235 235 """
236 236 for block in blocks:
237 237 # Searching for a block that looks like this:
238 238 #
239 239 # +------------------------------+
240 240 # | Section title |
241 241 # | ------------- |
242 242 # +------------------------------+
243 243 if (block['type'] == 'paragraph' and
244 244 len(block['lines']) == 2 and
245 245 len(block['lines'][0]) == len(block['lines'][1]) and
246 246 _sectionre.match(block['lines'][1])):
247 247 block['underline'] = block['lines'][1][0]
248 248 block['type'] = 'section'
249 249 del block['lines'][1]
250 250 return blocks
251 251
252 252
253 253 def inlineliterals(blocks):
254 254 for b in blocks:
255 255 if b['type'] in ('paragraph', 'section'):
256 256 b['lines'] = [l.replace('``', '"') for l in b['lines']]
257 257 return blocks
258 258
259 259
260 260 _hgrolere = re.compile(r':hg:`([^`]+)`')
261 261
262 262 def hgrole(blocks):
263 263 for b in blocks:
264 264 if b['type'] in ('paragraph', 'section'):
265 265 b['lines'] = [_hgrolere.sub(r'"hg \1"', l) for l in b['lines']]
266 266 return blocks
267 267
268 268
269 269 def addmargins(blocks):
270 270 """Adds empty blocks for vertical spacing.
271 271
272 272 This groups bullets, options, and definitions together with no vertical
273 273 space between them, and adds an empty block between all other blocks.
274 274 """
275 275 i = 1
276 276 while i < len(blocks):
277 277 if (blocks[i]['type'] == blocks[i - 1]['type'] and
278 278 blocks[i]['type'] in ('bullet', 'option', 'field')):
279 279 i += 1
280 280 else:
281 281 blocks.insert(i, dict(lines=[''], indent=0, type='margin'))
282 282 i += 2
283 283 return blocks
284 284
285 285
286 286 def formatblock(block, width):
287 287 """Format a block according to width."""
288 288 if width <= 0:
289 289 width = 78
290 290 indent = ' ' * block['indent']
291 291 if block['type'] == 'margin':
292 292 return ''
293 293 if block['type'] == 'literal':
294 294 indent += ' '
295 295 return indent + ('\n' + indent).join(block['lines'])
296 296 if block['type'] == 'section':
297 297 underline = len(block['lines'][0]) * block['underline']
298 298 return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline)
299 299 if block['type'] == 'definition':
300 300 term = indent + block['lines'][0]
301 301 hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip())
302 302 defindent = indent + hang * ' '
303 303 text = ' '.join(map(str.strip, block['lines'][1:]))
304 304 return "%s\n%s" % (term, textwrap.fill(text, width=width,
305 305 initial_indent=defindent,
306 306 subsequent_indent=defindent))
307 307 subindent = indent
308 308 if block['type'] == 'bullet':
309 309 if block['lines'][0].startswith('| '):
310 310 # Remove bullet for line blocks and add no extra
311 311 # indention.
312 312 block['lines'][0] = block['lines'][0][2:]
313 313 else:
314 314 m = _bulletre.match(block['lines'][0])
315 315 subindent = indent + m.end() * ' '
316 316 elif block['type'] == 'field':
317 317 keywidth = block['keywidth']
318 318 key = block['key']
319 319
320 320 subindent = indent + _fieldwidth * ' '
321 321 if len(key) + 2 > _fieldwidth:
322 322 # key too large, use full line width
323 323 key = key.ljust(width)
324 324 elif keywidth + 2 < _fieldwidth:
325 325 # all keys are small, add only two spaces
326 326 key = key.ljust(keywidth + 2)
327 327 subindent = indent + (keywidth + 2) * ' '
328 328 else:
329 329 # mixed sizes, use fieldwidth for this one
330 330 key = key.ljust(_fieldwidth)
331 331 block['lines'][0] = key + block['lines'][0]
332 332 elif block['type'] == 'option':
333 333 m = _optionre.match(block['lines'][0])
334 334 option, arg, rest = m.groups()
335 335 subindent = indent + (len(option) + len(arg)) * ' '
336 336
337 337 text = ' '.join(map(str.strip, block['lines']))
338 338 return textwrap.fill(text, width=width,
339 339 initial_indent=indent,
340 340 subsequent_indent=subindent)
341 341
342 342
343 343 def format(text, width, indent=0, keep=None):
344 344 """Parse and format the text according to width."""
345 345 blocks = findblocks(text)
346 346 for b in blocks:
347 347 b['indent'] += indent
348 348 blocks = findliteralblocks(blocks)
349 349 blocks, pruned = prunecontainers(blocks, keep or [])
350 350 blocks = findsections(blocks)
351 351 blocks = inlineliterals(blocks)
352 352 blocks = hgrole(blocks)
353 353 blocks = splitparagraphs(blocks)
354 354 blocks = updatefieldlists(blocks)
355 355 blocks = addmargins(blocks)
356 356 text = '\n'.join(formatblock(b, width) for b in blocks)
357 357 if keep is None:
358 358 return text
359 359 else:
360 360 return text, pruned
361 361
362 362
363 363 if __name__ == "__main__":
364 364 from pprint import pprint
365 365
366 366 def debug(func, *args):
367 367 blocks = func(*args)
368 368 print "*** after %s:" % func.__name__
369 369 pprint(blocks)
370 370 print
371 371 return blocks
372 372
373 373 text = open(sys.argv[1]).read()
374 374 blocks = debug(findblocks, text)
375 375 blocks = debug(findliteralblocks, blocks)
376 blocks = debug(prunecontainers, blocks, sys.argv[2:])
376 blocks, pruned = debug(prunecontainers, blocks, sys.argv[2:])
377 377 blocks = debug(inlineliterals, blocks)
378 378 blocks = debug(splitparagraphs, blocks)
379 379 blocks = debug(updatefieldlists, blocks)
380 380 blocks = debug(findsections, blocks)
381 381 blocks = debug(addmargins, blocks)
382 382 print '\n'.join(formatblock(b, 30) for b in blocks)
@@ -1,324 +1,344 b''
1 1 % These fail
2 2
3 3 % Use continue and abort
4 4 hg rebase: cannot use both abort and continue
5 5 hg rebase [-s REV | -b REV] [-d REV] [options]
6 6 hg rebase {-a|-c}
7 7
8 8 move changeset (and descendants) to a different branch
9 9
10 10 Rebase uses repeated merging to graft changesets from one part of history
11 11 (the source) onto another (the destination). This can be useful for
12 linearizing local changes relative to a master development tree.
12 linearizing *local* changes relative to a master development tree.
13
14 You should not rebase changesets that have already been shared with
15 others. Doing so will force everybody else to perform the same rebase or
16 they will end up with duplicated changesets after pulling in your rebased
17 changesets.
13 18
14 19 If you don't specify a destination changeset ("-d/--dest"), rebase uses
15 20 the tipmost head of the current named branch as the destination. (The
16 21 destination changeset is not modified by rebasing, but new changesets are
17 22 added as its descendants.)
18 23
19 24 You can specify which changesets to rebase in two ways: as a "source"
20 25 changeset or as a "base" changeset. Both are shorthand for a topologically
21 26 related set of changesets (the "source branch"). If you specify source
22 27 ("-s/--source"), rebase will rebase that changeset and all of its
23 28 descendants onto dest. If you specify base ("-b/--base"), rebase will
24 29 select ancestors of base back to but not including the common ancestor
25 30 with dest. Thus, "-b" is less precise but more convenient than "-s": you
26 31 can specify any changeset in the source branch, and rebase will select the
27 32 whole branch. If you specify neither "-s" nor "-b", rebase uses the parent
28 33 of the working directory as the base.
29 34
30 35 By default, rebase recreates the changesets in the source branch as
31 36 descendants of dest and then destroys the originals. Use "--keep" to
32 37 preserve the original source changesets. Some changesets in the source
33 38 branch (e.g. merges from the destination branch) may be dropped if they no
34 39 longer contribute any change.
35 40
36 41 One result of the rules for selecting the destination changeset and source
37 42 branch is that, unlike "merge", rebase will do nothing if you are at the
38 43 latest (tipmost) head of a named branch with two heads. You need to
39 44 explicitly specify source and/or destination (or "update" to the other
40 45 head, if it's the head of the intended source branch).
41 46
42 47 If a rebase is interrupted to manually resolve a merge, it can be
43 48 continued with --continue/-c or aborted with --abort/-a.
44 49
45 50 options:
46 51
47 52 -s --source rebase from the specified changeset
48 53 -b --base rebase from the base of the specified changeset (up to
49 54 greatest common ancestor of base and dest)
50 55 -d --dest rebase onto the specified changeset
51 56 --collapse collapse the rebased changesets
52 57 --keep keep original changesets
53 58 --keepbranches keep original branch names
54 59 --detach force detaching of source from its original branch
55 60 -c --continue continue an interrupted rebase
56 61 -a --abort abort an interrupted rebase
57 62 --style display using template map file
58 63 --template display with template
59 64
60 65 use "hg -v help rebase" to show global options
61 66
62 67 % Use continue and collapse
63 68 hg rebase: cannot use collapse with continue or abort
64 69 hg rebase [-s REV | -b REV] [-d REV] [options]
65 70 hg rebase {-a|-c}
66 71
67 72 move changeset (and descendants) to a different branch
68 73
69 74 Rebase uses repeated merging to graft changesets from one part of history
70 75 (the source) onto another (the destination). This can be useful for
71 linearizing local changes relative to a master development tree.
76 linearizing *local* changes relative to a master development tree.
77
78 You should not rebase changesets that have already been shared with
79 others. Doing so will force everybody else to perform the same rebase or
80 they will end up with duplicated changesets after pulling in your rebased
81 changesets.
72 82
73 83 If you don't specify a destination changeset ("-d/--dest"), rebase uses
74 84 the tipmost head of the current named branch as the destination. (The
75 85 destination changeset is not modified by rebasing, but new changesets are
76 86 added as its descendants.)
77 87
78 88 You can specify which changesets to rebase in two ways: as a "source"
79 89 changeset or as a "base" changeset. Both are shorthand for a topologically
80 90 related set of changesets (the "source branch"). If you specify source
81 91 ("-s/--source"), rebase will rebase that changeset and all of its
82 92 descendants onto dest. If you specify base ("-b/--base"), rebase will
83 93 select ancestors of base back to but not including the common ancestor
84 94 with dest. Thus, "-b" is less precise but more convenient than "-s": you
85 95 can specify any changeset in the source branch, and rebase will select the
86 96 whole branch. If you specify neither "-s" nor "-b", rebase uses the parent
87 97 of the working directory as the base.
88 98
89 99 By default, rebase recreates the changesets in the source branch as
90 100 descendants of dest and then destroys the originals. Use "--keep" to
91 101 preserve the original source changesets. Some changesets in the source
92 102 branch (e.g. merges from the destination branch) may be dropped if they no
93 103 longer contribute any change.
94 104
95 105 One result of the rules for selecting the destination changeset and source
96 106 branch is that, unlike "merge", rebase will do nothing if you are at the
97 107 latest (tipmost) head of a named branch with two heads. You need to
98 108 explicitly specify source and/or destination (or "update" to the other
99 109 head, if it's the head of the intended source branch).
100 110
101 111 If a rebase is interrupted to manually resolve a merge, it can be
102 112 continued with --continue/-c or aborted with --abort/-a.
103 113
104 114 options:
105 115
106 116 -s --source rebase from the specified changeset
107 117 -b --base rebase from the base of the specified changeset (up to
108 118 greatest common ancestor of base and dest)
109 119 -d --dest rebase onto the specified changeset
110 120 --collapse collapse the rebased changesets
111 121 --keep keep original changesets
112 122 --keepbranches keep original branch names
113 123 --detach force detaching of source from its original branch
114 124 -c --continue continue an interrupted rebase
115 125 -a --abort abort an interrupted rebase
116 126 --style display using template map file
117 127 --template display with template
118 128
119 129 use "hg -v help rebase" to show global options
120 130
121 131 % Use continue/abort and dest/source
122 132 hg rebase: abort and continue do not allow specifying revisions
123 133 hg rebase [-s REV | -b REV] [-d REV] [options]
124 134 hg rebase {-a|-c}
125 135
126 136 move changeset (and descendants) to a different branch
127 137
128 138 Rebase uses repeated merging to graft changesets from one part of history
129 139 (the source) onto another (the destination). This can be useful for
130 linearizing local changes relative to a master development tree.
140 linearizing *local* changes relative to a master development tree.
141
142 You should not rebase changesets that have already been shared with
143 others. Doing so will force everybody else to perform the same rebase or
144 they will end up with duplicated changesets after pulling in your rebased
145 changesets.
131 146
132 147 If you don't specify a destination changeset ("-d/--dest"), rebase uses
133 148 the tipmost head of the current named branch as the destination. (The
134 149 destination changeset is not modified by rebasing, but new changesets are
135 150 added as its descendants.)
136 151
137 152 You can specify which changesets to rebase in two ways: as a "source"
138 153 changeset or as a "base" changeset. Both are shorthand for a topologically
139 154 related set of changesets (the "source branch"). If you specify source
140 155 ("-s/--source"), rebase will rebase that changeset and all of its
141 156 descendants onto dest. If you specify base ("-b/--base"), rebase will
142 157 select ancestors of base back to but not including the common ancestor
143 158 with dest. Thus, "-b" is less precise but more convenient than "-s": you
144 159 can specify any changeset in the source branch, and rebase will select the
145 160 whole branch. If you specify neither "-s" nor "-b", rebase uses the parent
146 161 of the working directory as the base.
147 162
148 163 By default, rebase recreates the changesets in the source branch as
149 164 descendants of dest and then destroys the originals. Use "--keep" to
150 165 preserve the original source changesets. Some changesets in the source
151 166 branch (e.g. merges from the destination branch) may be dropped if they no
152 167 longer contribute any change.
153 168
154 169 One result of the rules for selecting the destination changeset and source
155 170 branch is that, unlike "merge", rebase will do nothing if you are at the
156 171 latest (tipmost) head of a named branch with two heads. You need to
157 172 explicitly specify source and/or destination (or "update" to the other
158 173 head, if it's the head of the intended source branch).
159 174
160 175 If a rebase is interrupted to manually resolve a merge, it can be
161 176 continued with --continue/-c or aborted with --abort/-a.
162 177
163 178 options:
164 179
165 180 -s --source rebase from the specified changeset
166 181 -b --base rebase from the base of the specified changeset (up to
167 182 greatest common ancestor of base and dest)
168 183 -d --dest rebase onto the specified changeset
169 184 --collapse collapse the rebased changesets
170 185 --keep keep original changesets
171 186 --keepbranches keep original branch names
172 187 --detach force detaching of source from its original branch
173 188 -c --continue continue an interrupted rebase
174 189 -a --abort abort an interrupted rebase
175 190 --style display using template map file
176 191 --template display with template
177 192
178 193 use "hg -v help rebase" to show global options
179 194
180 195 % Use source and base
181 196 hg rebase: cannot specify both a revision and a base
182 197 hg rebase [-s REV | -b REV] [-d REV] [options]
183 198 hg rebase {-a|-c}
184 199
185 200 move changeset (and descendants) to a different branch
186 201
187 202 Rebase uses repeated merging to graft changesets from one part of history
188 203 (the source) onto another (the destination). This can be useful for
189 linearizing local changes relative to a master development tree.
204 linearizing *local* changes relative to a master development tree.
205
206 You should not rebase changesets that have already been shared with
207 others. Doing so will force everybody else to perform the same rebase or
208 they will end up with duplicated changesets after pulling in your rebased
209 changesets.
190 210
191 211 If you don't specify a destination changeset ("-d/--dest"), rebase uses
192 212 the tipmost head of the current named branch as the destination. (The
193 213 destination changeset is not modified by rebasing, but new changesets are
194 214 added as its descendants.)
195 215
196 216 You can specify which changesets to rebase in two ways: as a "source"
197 217 changeset or as a "base" changeset. Both are shorthand for a topologically
198 218 related set of changesets (the "source branch"). If you specify source
199 219 ("-s/--source"), rebase will rebase that changeset and all of its
200 220 descendants onto dest. If you specify base ("-b/--base"), rebase will
201 221 select ancestors of base back to but not including the common ancestor
202 222 with dest. Thus, "-b" is less precise but more convenient than "-s": you
203 223 can specify any changeset in the source branch, and rebase will select the
204 224 whole branch. If you specify neither "-s" nor "-b", rebase uses the parent
205 225 of the working directory as the base.
206 226
207 227 By default, rebase recreates the changesets in the source branch as
208 228 descendants of dest and then destroys the originals. Use "--keep" to
209 229 preserve the original source changesets. Some changesets in the source
210 230 branch (e.g. merges from the destination branch) may be dropped if they no
211 231 longer contribute any change.
212 232
213 233 One result of the rules for selecting the destination changeset and source
214 234 branch is that, unlike "merge", rebase will do nothing if you are at the
215 235 latest (tipmost) head of a named branch with two heads. You need to
216 236 explicitly specify source and/or destination (or "update" to the other
217 237 head, if it's the head of the intended source branch).
218 238
219 239 If a rebase is interrupted to manually resolve a merge, it can be
220 240 continued with --continue/-c or aborted with --abort/-a.
221 241
222 242 options:
223 243
224 244 -s --source rebase from the specified changeset
225 245 -b --base rebase from the base of the specified changeset (up to
226 246 greatest common ancestor of base and dest)
227 247 -d --dest rebase onto the specified changeset
228 248 --collapse collapse the rebased changesets
229 249 --keep keep original changesets
230 250 --keepbranches keep original branch names
231 251 --detach force detaching of source from its original branch
232 252 -c --continue continue an interrupted rebase
233 253 -a --abort abort an interrupted rebase
234 254 --style display using template map file
235 255 --template display with template
236 256
237 257 use "hg -v help rebase" to show global options
238 258
239 259 % Rebase with no arguments - from current
240 260 nothing to rebase
241 261
242 262 % Rebase with no arguments - from the current branch
243 263 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
244 264 nothing to rebase
245 265 % ----------
246 266 % These work
247 267
248 268 % Rebase with no arguments (from 3 onto 7)
249 269 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
250 270 saving bundle to
251 271 adding branch
252 272 adding changesets
253 273 adding manifests
254 274 adding file changes
255 275 added 5 changesets with 5 changes to 5 files
256 276 rebase completed
257 277 % Try to rollback after a rebase (fail)
258 278 no rollback information available
259 279
260 280 % Rebase with base == '.' => same as no arguments (from 3 onto 7)
261 281 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
262 282 saving bundle to
263 283 adding branch
264 284 adding changesets
265 285 adding manifests
266 286 adding file changes
267 287 added 5 changesets with 5 changes to 5 files
268 288 rebase completed
269 289
270 290 % Rebase with dest == default => same as no arguments (from 3 onto 7)
271 291 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
272 292 saving bundle to
273 293 adding branch
274 294 adding changesets
275 295 adding manifests
276 296 adding file changes
277 297 added 5 changesets with 5 changes to 5 files
278 298 rebase completed
279 299
280 300 % Specify only source (from 4 onto 7)
281 301 saving bundle to
282 302 adding branch
283 303 adding changesets
284 304 adding manifests
285 305 adding file changes
286 306 added 4 changesets with 4 changes to 4 files (-1 heads)
287 307 rebase completed
288 308
289 309 % Specify only dest (from 3 onto 6)
290 310 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
291 311 saving bundle to
292 312 adding branch
293 313 adding changesets
294 314 adding manifests
295 315 adding file changes
296 316 added 5 changesets with 5 changes to 5 files (+1 heads)
297 317 rebase completed
298 318
299 319 % Specify only base (from 3 onto 7)
300 320 saving bundle to
301 321 adding branch
302 322 adding changesets
303 323 adding manifests
304 324 adding file changes
305 325 added 5 changesets with 5 changes to 5 files
306 326 rebase completed
307 327
308 328 % Specify source and dest (from 4 onto 6)
309 329 saving bundle to
310 330 adding branch
311 331 adding changesets
312 332 adding manifests
313 333 adding file changes
314 334 added 4 changesets with 4 changes to 4 files
315 335 rebase completed
316 336
317 337 % Specify base and dest (from 3 onto 6)
318 338 saving bundle to
319 339 adding branch
320 340 adding changesets
321 341 adding manifests
322 342 adding file changes
323 343 added 5 changesets with 5 changes to 5 files (+1 heads)
324 344 rebase completed
General Comments 0
You need to be logged in to leave comments. Login now