def pullrebase(orig, ui, repo, *args, **opts): 'Call rebase after pull if the latter has been invoked with --rebase' if opts.get('rebase'): if opts.get('update'): del opts['update'] ui.debug('--update and --rebase are not compatible, ignoring ' 'the update flag\n') cmdutil.bail_if_changed(repo) revsprepull = len(repo) origpostincoming = commands.postincoming def _dummy(*args, **kwargs): pass commands.postincoming = _dummy try: orig(ui, repo, *args, **opts) finally: commands.postincoming = origpostincoming revspostpull = len(repo) if revspostpull > revsprepull: rebase(ui, repo, **opts) branch = repo[None].branch() dest = repo[branch].rev() if dest != repo['.'].rev(): # there was nothing to rebase we force an update hg.update(repo, dest) else: orig(ui, repo, *args, **opts)
def pullrebaseif(orig, ui, repo, *args, **opts): '''Call rebaseif after pull if the latter has been invoked with --rebaseif''' # this function is taken in verbatim from rebase extension, with rebase replaced with rebaseif if opts.get('rebaseif'): if opts.get('update'): del opts['update'] ui.debug(_('--update and --rebaseif are not compatible, ignoring the update flag\n')) try: cmdutil.bailifchanged(repo) # 1.9 except AttributeError: cmdutil.bail_if_changed(repo) # < 1.9 revsprepull = len(repo) origpostincoming = commands.postincoming def _dummy(*args, **kwargs): pass commands.postincoming = _dummy try: orig(ui, repo, *args, **opts) finally: commands.postincoming = origpostincoming revspostpull = len(repo) if revspostpull > revsprepull: rebaseif(ui, repo, **opts) branch = repo[None].branch() dest = repo[branch].rev() if dest != repo['.'].rev(): # there was nothing to rebase we force an update hg.update(repo, dest) else: orig(ui, repo, *args, **opts)
def pullrebase(orig, ui, repo, *args, **opts): 'Call rebase after pull if the latter has been invoked with --rebase' if opts.get('rebase'): if opts.get('update'): del opts['update'] ui.debug('--update and --rebase are not compatible, ignoring ' 'the update flag\n') cmdutil.bail_if_changed(repo) revsprepull = len(repo) orig(ui, repo, *args, **opts) revspostpull = len(repo) if revspostpull > revsprepull: rebase(ui, repo, **opts) branch = repo[None].branch() dest = repo[branch].rev() if dest != repo['.'].rev(): # there was nothing to rebase we force an update merge.update(repo, dest, False, False, False) else: orig(ui, repo, *args, **opts)
def pullrebaseif(orig, ui, repo, *args, **opts): '''Call rebaseif after pull if the latter has been invoked with --rebaseif''' # this function is taken in verbatim from rebase extension, with rebase replaced with rebaseif if opts.get('rebaseif'): if opts.get('update'): del opts['update'] ui.debug( _('--update and --rebaseif are not compatible, ignoring the update flag\n' )) try: cmdutil.bailifchanged(repo) # 1.9 except AttributeError: cmdutil.bail_if_changed(repo) # < 1.9 revsprepull = len(repo) origpostincoming = commands.postincoming def _dummy(*args, **kwargs): pass commands.postincoming = _dummy try: orig(ui, repo, *args, **opts) finally: commands.postincoming = origpostincoming revspostpull = len(repo) if revspostpull > revsprepull: rebaseif(ui, repo, **opts) branch = repo[None].branch() dest = repo[branch].rev() if dest != repo['.'].rev(): # there was nothing to rebase we force an update hg.update(repo, dest) else: orig(ui, repo, *args, **opts)
def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch Rebase uses repeated merging to graft changesets from one part of history onto another. This can be useful for linearizing local changes relative to a master development tree. If a rebase is interrupted to manually resolve a merge, it can be continued with --continue/-c or aborted with --abort/-a. """ originalwd = target = None external = nullrev state = {} skipped = set() lock = wlock = None try: lock = repo.lock() wlock = repo.wlock() # Validate input and define rebasing points destf = opts.get('dest', None) srcf = opts.get('source', None) basef = opts.get('base', None) contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) extrafn = opts.get('extrafn') keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) if contf or abortf: if contf and abortf: raise error.ParseError('rebase', _('cannot use both abort and continue')) if collapsef: raise error.ParseError( 'rebase', _('cannot use collapse with continue or abort')) if srcf or basef or destf: raise error.ParseError('rebase', _('abort and continue do not allow specifying revisions')) (originalwd, target, state, collapsef, keepf, keepbranchesf, external) = restorestatus(repo) if abortf: abort(repo, originalwd, target, state) return else: if srcf and basef: raise error.ParseError('rebase', _('cannot specify both a ' 'revision and a base')) cmdutil.bail_if_changed(repo) result = buildstate(repo, destf, srcf, basef, collapsef) if result: originalwd, target, state, external = result else: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) return if keepbranchesf: if extrafn: raise error.ParseError( 'rebase', _('cannot use both keepbranches and extrafn')) def extrafn(ctx, extra): extra['branch'] = ctx.branch() # Rebase targetancestors = list(repo.changelog.ancestors(target)) targetancestors.append(target) for rev in sorted(state): if state[rev] == -1: storestatus(repo, originalwd, target, state, collapsef, keepf, keepbranchesf, external) rebasenode(repo, rev, target, state, skipped, targetancestors, collapsef, extrafn) ui.note(_('rebase merging completed\n')) if collapsef: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) concludenode(repo, rev, p1, external, state, collapsef, last=True, skipped=skipped, extrafn=extrafn) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) if not keepf: # Remove no more useful revisions if set(repo.changelog.descendants(min(state))) - set(state): ui.warn(_("warning: new changesets detected on source branch, " "not stripping\n")) else: repair.strip(ui, repo, repo[min(state)].node(), "strip") clearstatus(repo) ui.status(_("rebase completed\n")) if os.path.exists(repo.sjoin('undo')): util.unlink(repo.sjoin('undo')) if skipped: ui.note(_("%d revisions have been skipped\n") % len(skipped)) finally: release(lock, wlock)
def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch Rebase uses repeated merging to graft changesets from one part of history (the source) onto another (the destination). This can be useful for linearizing *local* changes relative to a master development tree. You should not rebase changesets that have already been shared with others. Doing so will force everybody else to perform the same rebase or they will end up with duplicated changesets after pulling in your rebased changesets. If you don't specify a destination changeset (``-d/--dest``), rebase uses the tipmost head of the current named branch as the destination. (The destination changeset is not modified by rebasing, but new changesets are added as its descendants.) You can specify which changesets to rebase in two ways: as a "source" changeset or as a "base" changeset. Both are shorthand for a topologically related set of changesets (the "source branch"). If you specify source (``-s/--source``), rebase will rebase that changeset and all of its descendants onto dest. If you specify base (``-b/--base``), rebase will select ancestors of base back to but not including the common ancestor with dest. Thus, ``-b`` is less precise but more convenient than ``-s``: you can specify any changeset in the source branch, and rebase will select the whole branch. If you specify neither ``-s`` nor ``-b``, rebase uses the parent of the working directory as the base. By default, rebase recreates the changesets in the source branch as descendants of dest and then destroys the originals. Use ``--keep`` to preserve the original source changesets. Some changesets in the source branch (e.g. merges from the destination branch) may be dropped if they no longer contribute any change. One result of the rules for selecting the destination changeset and source branch is that, unlike ``merge``, rebase will do nothing if you are at the latest (tipmost) head of a named branch with two heads. You need to explicitly specify source and/or destination (or ``update`` to the other head, if it's the head of the intended source branch). If a rebase is interrupted to manually resolve a merge, it can be continued with --continue/-c or aborted with --abort/-a. Returns 0 on success, 1 if nothing to rebase. """ originalwd = target = None external = nullrev state = {} skipped = set() targetancestors = set() lock = wlock = None try: lock = repo.lock() wlock = repo.wlock() # Validate input and define rebasing points destf = opts.get('dest', None) srcf = opts.get('source', None) basef = opts.get('base', None) contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) extrafn = opts.get('extrafn') keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) detachf = opts.get('detach', False) # keepopen is not meant for use on the command line, but by # other extensions keepopen = opts.get('keepopen', False) if contf or abortf: if contf and abortf: raise util.Abort(_('cannot use both abort and continue')) if collapsef: raise util.Abort( _('cannot use collapse with continue or abort')) if detachf: raise util.Abort(_('cannot use detach with continue or abort')) if srcf or basef or destf: raise util.Abort( _('abort and continue do not allow specifying revisions')) (originalwd, target, state, skipped, collapsef, keepf, keepbranchesf, external) = restorestatus(repo) if abortf: return abort(repo, originalwd, target, state) else: if srcf and basef: raise util.Abort(_('cannot specify both a ' 'revision and a base')) if detachf: if not srcf: raise util.Abort( _('detach requires a revision to be specified')) if basef: raise util.Abort(_('cannot specify a base with detach')) cmdutil.bail_if_changed(repo) result = buildstate(repo, destf, srcf, basef, detachf) if not result: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) return 1 else: originalwd, target, state = result if collapsef: targetancestors = set(repo.changelog.ancestors(target)) external = checkexternal(repo, state, targetancestors) if keepbranchesf: if extrafn: raise util.Abort(_('cannot use both keepbranches and extrafn')) def extrafn(ctx, extra): extra['branch'] = ctx.branch() # Rebase if not targetancestors: targetancestors = set(repo.changelog.ancestors(target)) targetancestors.add(target) sortedstate = sorted(state) total = len(sortedstate) pos = 0 for rev in sortedstate: pos += 1 if state[rev] == -1: ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])), _('changesets'), total) storestatus(repo, originalwd, target, state, collapsef, keepf, keepbranchesf, external) p1, p2 = defineparents(repo, rev, target, state, targetancestors) if len(repo.parents()) == 2: repo.ui.debug('resuming interrupted rebase\n') else: stats = rebasenode(repo, rev, p1, p2, state) if stats and stats[3] > 0: raise util.Abort(_('unresolved conflicts (see hg ' 'resolve, then hg rebase --continue)')) updatedirstate(repo, rev, target, p2) if not collapsef: newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn) else: # Skip commit if we are collapsing repo.dirstate.setparents(repo[p1].node()) newrev = None # Update the state if newrev is not None: state[rev] = repo[newrev].rev() else: if not collapsef: ui.note(_('no changes, revision %d skipped\n') % rev) ui.debug('next revision set to %s\n' % p1) skipped.add(rev) state[rev] = p1 ui.progress(_('rebasing'), None) ui.note(_('rebase merging completed\n')) if collapsef and not keepopen: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) commitmsg = 'Collapsed revision' for rebased in state: if rebased not in skipped and state[rebased] != nullmerge: commitmsg += '\n* %s' % repo[rebased].description() commitmsg = ui.edit(commitmsg, repo.ui.username()) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extrafn=extrafn) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) if not keepf: # Remove no more useful revisions rebased = [rev for rev in state if state[rev] != nullmerge] if rebased: if set(repo.changelog.descendants(min(rebased))) - set(state): ui.warn(_("warning: new changesets detected " "on source branch, not stripping\n")) else: # backup the old csets by default repair.strip(ui, repo, repo[min(rebased)].node(), "all") clearstatus(repo) ui.note(_("rebase completed\n")) if os.path.exists(repo.sjoin('undo')): util.unlink(repo.sjoin('undo')) if skipped: ui.note(_("%d revisions have been skipped\n") % len(skipped)) finally: release(lock, wlock)
def histedit(ui, repo, *parent, **opts): """hg histedit <parent> """ if opts.get('outgoing'): if len(parent) > 1: raise util.Abort('only one repo argument allowed with --outgoing') elif parent: parent = parent[0] dest, revs, checkout = hg.parseurl( ui.expandpath(parent or 'default-push', parent or 'default'), ['tip']) if revs: revs = [repo.lookup(rev) for rev in revs] other = hg.repository(ui, dest) ui.status(_('comparing with %s\n') % url.hidepassword(dest)) parent = repo.findoutgoing(other, force=opts.get('force')) else: if opts.get('force'): raise util.Abort('--force only allowed with --outgoing') if opts.get('continue', False): if len(parent) != 0: raise util.Abort('no arguments allowed with --continue') (parentctxnode, created, replaced, tmpnodes, existing, rules, keep, tip, ) = readstate(repo) currentparent, wantnull = repo.dirstate.parents() parentctx = repo[parentctxnode] # discover any nodes the user has added in the interim newchildren = [c for c in parentctx.children() if c.node() not in existing] action, currentnode = rules.pop(0) while newchildren: if action in ['f', 'fold', ]: tmpnodes.extend([n.node() for n in newchildren]) else: created.extend([n.node() for n in newchildren]) newchildren = filter(lambda x: x.node() not in existing, reduce(lambda x, y: x + y, map(lambda r: r.children(), newchildren))) m, a, r, d = repo.status()[:4] oldctx = repo[currentnode] message = oldctx.description() if action in ('e', 'edit', ): message = ui.edit(message, ui.username()) elif action in ('f', 'fold', ): message = 'fold-temp-revision %s' % currentnode new = None if m or a or r or d: new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(), extra=oldctx.extra()) if action in ('e', 'edit', 'p', 'pick', ): replaced.append(oldctx.node()) if new: created.append(new) parentctx = repo[new] else: # fold if new: tmpnodes.append(new) else: new = newchildren[-1] (parentctx, created_, replaced_, tmpnodes_, ) = finishfold(ui, repo, parentctx, oldctx, new, opts, newchildren) replaced.extend(replaced_) created.extend(created_) tmpnodes.extend(tmpnodes_) elif opts.get('abort', False): if len(parent) != 0: raise util.Abort('no arguments allowed with --abort') (parentctxnode, created, replaced, tmpnodes, existing, rules, keep, tip, ) = readstate(repo) ui.debug('restore wc to old tip %s\n' % node.hex(tip)) hg.clean(repo, tip) ui.debug('should strip created nodes %s\n' % ', '.join([node.hex(n)[:12] for n in created])) ui.debug('should strip temp nodes %s\n' % ', '.join([node.hex(n)[:12] for n in tmpnodes])) for nodes in (created, tmpnodes, ): for n in reversed(nodes): try: repair.strip(ui, repo, n) except error.LookupError: pass os.unlink(os.path.join(repo.path, 'histedit-state')) return else: cmdutil.bail_if_changed(repo) if os.path.exists(os.path.join(repo.path, 'histedit-state')): raise util.Abort('history edit already in progress, try --continue or --abort') tip, empty = repo.dirstate.parents() if len(parent) != 1: raise util.Abort('requires exactly one parent revision') parent = parent[0] revs = between(repo, parent, tip) ctxs = [repo[r] for r in revs] existing = [r.node() for r in ctxs] rules = '\n'.join([('pick %s %s' % (c.hex()[:12], c.description().splitlines()[0]))[:80] for c in ctxs]) rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12], ) rules = ui.edit(rules, ui.username()) parentctx = repo[parent].parents()[0] rules = [l for l in (r.strip() for r in rules.splitlines()) if l and not l[0] == '#'] rules = verifyrules(rules, repo, ctxs) keep = opts.get('keep', False) replaced = [] tmpnodes = [] created = [] while rules: writestate(repo, parentctx.node(), created, replaced, tmpnodes, existing, rules, keep, tip) action, ha = rules.pop(0) (parentctx, created_, replaced_, tmpnodes_, ) = actiontable[action](ui, repo, parentctx, ha, opts) created.extend(created_) replaced.extend(replaced_) tmpnodes.extend(tmpnodes_) hg.update(repo, parentctx.node()) if not keep: ui.debug('should strip replaced nodes %s\n' % ', '.join([node.hex(n)[:12] for n in replaced])) for n in sorted(replaced, lambda x, y: cmp(repo[x].rev(), repo[y].rev())): try: repair.strip(ui, repo, n) except error.LookupError: pass ui.debug('should strip temp nodes %s\n' % ', '.join([node.hex(n)[:12] for n in tmpnodes])) for n in reversed(tmpnodes): try: repair.strip(ui, repo, n) except error.LookupError: pass os.unlink(os.path.join(repo.path, 'histedit-state'))
def push(repo, dest, force, revs): """push revisions starting at a specified head back to Subversion. """ assert not revs, 'designated revisions for push remains unimplemented.' cmdutil.bail_if_changed(repo) ui = repo.ui old_encoding = util.swap_out_encoding() # TODO: implement --rev/#rev support # TODO: do credentials specified in the URL still work? svnurl = repo.ui.expandpath(dest.svnurl) svn = svnrepo.svnremoterepo(repo.ui, svnurl).svn meta = repo.svnmeta(svn.uuid) # Strategy: # 1. Find all outgoing commits from this head if len(repo.parents()) != 1: ui.status('Cowardly refusing to push branch merge\n') return 1 workingrev = repo.parents()[0] ui.status('searching for changes\n') hashes = meta.revmap.hashes() outgoing = util.outgoing_revisions(repo, hashes, workingrev.node()) if not (outgoing and len(outgoing)): ui.status('no changes found\n') return 0 while outgoing: oldest = outgoing.pop(-1) old_ctx = repo[oldest] if len(old_ctx.parents()) != 1: ui.status('Found a branch merge, this needs discussion and ' 'implementation.\n') return 1 base_n = old_ctx.parents()[0].node() old_children = repo[base_n].children() svnbranch = repo[base_n].branch() oldtip = base_n samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch and c.node() in hashes] while samebranchchildren: oldtip = samebranchchildren[0].node() samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch and c.node() in hashes] # 2. Commit oldest revision that needs to be pushed base_revision = hashes[base_n][0] try: pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn) except pushmod.NoFilesException: ui.warn("Could not push revision %s because it had no changes in svn.\n" % old_ctx) return 1 # 3. Fetch revisions from svn # TODO: this probably should pass in the source explicitly - rev too? r = repo.pull(dest, force=force) assert not r or r == 0 # 4. Find the new head of the target branch oldtipctx = repo[oldtip] replacement = [c for c in oldtipctx.children() if c not in old_children and c.branch() == oldtipctx.branch()] assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement replacement = replacement[0] # 5. Rebase all children of the currently-pushing rev to the new branch heads = repo.heads(old_ctx.node()) for needs_transplant in heads: def extrafn(ctx, extra): if ctx.node() == oldest: return extra['branch'] = ctx.branch() # TODO: can we avoid calling our own rebase wrapper here? rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn, svnsourcerev=needs_transplant) repo = hg.repository(ui, meta.path) for child in repo[replacement.node()].children(): rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) if rebasesrc in outgoing: while rebasesrc in outgoing: rebsrcindex = outgoing.index(rebasesrc) outgoing = (outgoing[0:rebsrcindex] + [child.node(), ] + outgoing[rebsrcindex+1:]) children = [c for c in child.children() if c.branch() == child.branch()] if children: child = children[0] rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) # TODO: stop constantly creating the SVNMeta instances. meta = repo.svnmeta(svn.uuid) hashes = meta.revmap.hashes() util.swap_out_encoding(old_encoding) return 0
def collapse(ui, repo, **opts): """collapse multiple revisions into one Collapse combines multiple consecutive changesets into a single changeset, preserving any descendants of the final changeset. The commit messages for the collapsed changesets are concatenated and may be edited before the collapse is completed. """ rng = cmdutil.revrange(repo, opts['rev']) if not rng: raise util.Abort(_('no revisions specified')) first = rng[0] last = rng[-1] revs = inbetween(repo, first, last) if not revs: raise util.Abort(_('revision %s is not an ancestor of revision %s\n') % (first, last)) elif len(revs) == 1: raise util.Abort(_('only one revision specified')) ui.debug(_('Collapsing revisions %s\n') % revs) for r in revs: if repo[r].user() != ui.username() and not opts['force']: raise util.Abort(_('revision %s does not belong to %s\n') % (r, ui.username())) if r != last: children = repo[r].children() if len(children) > 1: for c in children: if not c.rev() in revs: raise util.Abort(_('revision %s has child %s not ' 'being collapsed, please rebase\n') % (r, c.rev())) if r != first: parents = repo[r].parents() if len(parents) > 1: for p in parents: if not p.rev() in revs: raise util.Abort(_('revision %s has parent %s not ' 'being collapsed.') % (r, p.rev())) if len(repo[first].parents()) > 1: raise util.Abort(_('start revision %s has multiple parents, ' 'won\'t collapse.') % first) cmdutil.bail_if_changed(repo) parent = repo[first].parents()[0] tomove = list(repo.changelog.descendants(last)) movemap = dict.fromkeys(tomove, nullrev) ui.debug(_('will move revisions: %s\n') % tomove) origparent = repo['.'].rev() collapsed = None try: branch = repo[last].branch() collapsed = makecollapsed(ui, repo, parent, revs, branch, opts) movemap[max(revs)] = collapsed movedescendants(ui, repo, collapsed, tomove, movemap) except: merge.update(repo, repo[origparent].rev(), False, True, False) if collapsed: repair.strip(ui, repo, collapsed.node(), "strip") raise if not opts['keep']: ui.debug(_('stripping revision %d\n') % first) repair.strip(ui, repo, repo[first].node(), "strip") ui.status(_('collapse completed\n'))
def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch Rebase uses repeated merging to graft changesets from one part of history (the source) onto another (the destination). This can be useful for linearizing *local* changes relative to a master development tree. You should not rebase changesets that have already been shared with others. Doing so will force everybody else to perform the same rebase or they will end up with duplicated changesets after pulling in your rebased changesets. If you don't specify a destination changeset (``-d/--dest``), rebase uses the tipmost head of the current named branch as the destination. (The destination changeset is not modified by rebasing, but new changesets are added as its descendants.) You can specify which changesets to rebase in two ways: as a "source" changeset or as a "base" changeset. Both are shorthand for a topologically related set of changesets (the "source branch"). If you specify source (``-s/--source``), rebase will rebase that changeset and all of its descendants onto dest. If you specify base (``-b/--base``), rebase will select ancestors of base back to but not including the common ancestor with dest. Thus, ``-b`` is less precise but more convenient than ``-s``: you can specify any changeset in the source branch, and rebase will select the whole branch. If you specify neither ``-s`` nor ``-b``, rebase uses the parent of the working directory as the base. By default, rebase recreates the changesets in the source branch as descendants of dest and then destroys the originals. Use ``--keep`` to preserve the original source changesets. Some changesets in the source branch (e.g. merges from the destination branch) may be dropped if they no longer contribute any change. One result of the rules for selecting the destination changeset and source branch is that, unlike ``merge``, rebase will do nothing if you are at the latest (tipmost) head of a named branch with two heads. You need to explicitly specify source and/or destination (or ``update`` to the other head, if it's the head of the intended source branch). If a rebase is interrupted to manually resolve a merge, it can be continued with --continue/-c or aborted with --abort/-a. Returns 0 on success, 1 if nothing to rebase. """ originalwd = target = None external = nullrev state = {} skipped = set() targetancestors = set() lock = wlock = None try: lock = repo.lock() wlock = repo.wlock() # Validate input and define rebasing points destf = opts.get('dest', None) srcf = opts.get('source', None) basef = opts.get('base', None) contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) extrafn = opts.get('extrafn') keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) detachf = opts.get('detach', False) # keepopen is not meant for use on the command line, but by # other extensions keepopen = opts.get('keepopen', False) if contf or abortf: if contf and abortf: raise util.Abort(_('cannot use both abort and continue')) if collapsef: raise util.Abort( _('cannot use collapse with continue or abort')) if detachf: raise util.Abort(_('cannot use detach with continue or abort')) if srcf or basef or destf: raise util.Abort( _('abort and continue do not allow specifying revisions')) (originalwd, target, state, skipped, collapsef, keepf, keepbranchesf, external) = restorestatus(repo) if abortf: return abort(repo, originalwd, target, state) else: if srcf and basef: raise util.Abort( _('cannot specify both a ' 'revision and a base')) if detachf: if not srcf: raise util.Abort( _('detach requires a revision to be specified')) if basef: raise util.Abort(_('cannot specify a base with detach')) cmdutil.bail_if_changed(repo) result = buildstate(repo, destf, srcf, basef, detachf) if not result: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) return 1 else: originalwd, target, state = result if collapsef: targetancestors = set(repo.changelog.ancestors(target)) external = checkexternal(repo, state, targetancestors) if keepbranchesf: if extrafn: raise util.Abort(_('cannot use both keepbranches and extrafn')) def extrafn(ctx, extra): extra['branch'] = ctx.branch() # Rebase if not targetancestors: targetancestors = set(repo.changelog.ancestors(target)) targetancestors.add(target) sortedstate = sorted(state) total = len(sortedstate) pos = 0 for rev in sortedstate: pos += 1 if state[rev] == -1: ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])), _('changesets'), total) storestatus(repo, originalwd, target, state, collapsef, keepf, keepbranchesf, external) p1, p2 = defineparents(repo, rev, target, state, targetancestors) if len(repo.parents()) == 2: repo.ui.debug('resuming interrupted rebase\n') else: stats = rebasenode(repo, rev, p1, p2, state) if stats and stats[3] > 0: raise util.Abort( _('unresolved conflicts (see hg ' 'resolve, then hg rebase --continue)')) updatedirstate(repo, rev, target, p2) if not collapsef: newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn) else: # Skip commit if we are collapsing repo.dirstate.setparents(repo[p1].node()) newrev = None # Update the state if newrev is not None: state[rev] = repo[newrev].rev() else: if not collapsef: ui.note(_('no changes, revision %d skipped\n') % rev) ui.debug('next revision set to %s\n' % p1) skipped.add(rev) state[rev] = p1 ui.progress(_('rebasing'), None) ui.note(_('rebase merging completed\n')) if collapsef and not keepopen: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) commitmsg = 'Collapsed revision' for rebased in state: if rebased not in skipped and state[rebased] != nullmerge: commitmsg += '\n* %s' % repo[rebased].description() commitmsg = ui.edit(commitmsg, repo.ui.username()) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extrafn=extrafn) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) if not keepf: # Remove no more useful revisions rebased = [rev for rev in state if state[rev] != nullmerge] if rebased: if set(repo.changelog.descendants(min(rebased))) - set(state): ui.warn( _("warning: new changesets detected " "on source branch, not stripping\n")) else: # backup the old csets by default repair.strip(ui, repo, repo[min(rebased)].node(), "all") clearstatus(repo) ui.note(_("rebase completed\n")) if os.path.exists(repo.sjoin('undo')): util.unlinkpath(repo.sjoin('undo')) if skipped: ui.note(_("%d revisions have been skipped\n") % len(skipped)) finally: release(lock, wlock)
def push(repo, dest, force, revs): """push revisions starting at a specified head back to Subversion. """ assert not revs, 'designated revisions for push remains unimplemented.' cmdutil.bail_if_changed(repo) checkpush = getattr(repo, 'checkpush', None) if checkpush: checkpush(force, revs) ui = repo.ui old_encoding = util.swap_out_encoding() # TODO: implement --rev/#rev support # TODO: do credentials specified in the URL still work? svnurl = repo.ui.expandpath(dest.svnurl) svn = dest.svn meta = repo.svnmeta(svn.uuid, svn.subdir) # Strategy: # 1. Find all outgoing commits from this head if len(repo.parents()) != 1: ui.status('Cowardly refusing to push branch merge\n') return 0 # results in nonzero exit status, see hg's commands.py workingrev = repo.parents()[0] ui.status('searching for changes\n') hashes = meta.revmap.hashes() outgoing = util.outgoing_revisions(repo, hashes, workingrev.node()) if not (outgoing and len(outgoing)): ui.status('no changes found\n') return 1 # so we get a sane exit status, see hg's commands.push while outgoing: # 2. Commit oldest revision that needs to be pushed oldest = outgoing.pop(-1) old_ctx = repo[oldest] old_pars = old_ctx.parents() if len(old_pars) != 1: ui.status('Found a branch merge, this needs discussion and ' 'implementation.\n') return 0 # results in nonzero exit status, see hg's commands.py # We will commit to svn against this node's parent rev. Any file-level # conflicts here will result in an error reported by svn. base_ctx = old_pars[0] base_revision = hashes[base_ctx.node()][0] svnbranch = base_ctx.branch() # Find most recent svn commit we have on this branch. # This node will become the nearest known ancestor of the pushed rev. oldtipctx = base_ctx old_children = oldtipctx.descendants() seen = set(c.node() for c in old_children) samebranchchildren = [c for c in old_children if c.branch() == svnbranch and c.node() in hashes] if samebranchchildren: # The following relies on descendants being sorted by rev. oldtipctx = samebranchchildren[-1] # All set, so commit now. try: pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn) except pushmod.NoFilesException: ui.warn("Could not push revision %s because it had no changes in svn.\n" % old_ctx) return 1 # 3. Fetch revisions from svn # TODO: this probably should pass in the source explicitly - rev too? r = repo.pull(dest, force=force) assert not r or r == 0 # 4. Find the new head of the target branch # We expect to get our own new commit back, but we might also get other # commits that happened since our last pull, or even right after our own # commit (race). for c in oldtipctx.descendants(): if c.node() not in seen and c.branch() == svnbranch: newtipctx = c # 5. Rebase all children of the currently-pushing rev to the new head heads = repo.heads(old_ctx.node()) for needs_transplant in heads: def extrafn(ctx, extra): if ctx.node() == oldest: return extra['branch'] = ctx.branch() # TODO: can we avoid calling our own rebase wrapper here? rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn, svnsourcerev=needs_transplant) # Reload the repo after the rebase. Do not reuse contexts across this. newtip = newtipctx.node() repo = hg.repository(ui, meta.path) newtipctx = repo[newtip] # Rewrite the node ids in outgoing to their rebased versions. rebasemap = dict() for child in newtipctx.descendants(): rebasesrc = child.extra().get('rebase_source') if rebasesrc: rebasemap[node.bin(rebasesrc)] = child.node() outgoing = [rebasemap.get(n) or n for n in outgoing] # TODO: stop constantly creating the SVNMeta instances. meta = repo.svnmeta(svn.uuid, svn.subdir) hashes = meta.revmap.hashes() util.swap_out_encoding(old_encoding) return 1 # so we get a sane exit status, see hg's commands.push
def collapse(ui, repo, **opts): """collapse multiple revisions into one Collapse combines multiple consecutive changesets into a single changeset, preserving any descendants of the final changeset. The commit messages for the collapsed changesets are concatenated and may be edited before the collapse is completed. """ rng = cmdutil.revrange(repo, opts['rev']) first = rng[0] last = rng[len(rng) - 1] revs = inbetween(repo, first, last) if not revs: raise util.Abort(_('revision %s is not an ancestor of revision %s\n') % (first, last)) elif len(revs) == 1: raise util.Abort(_('only one revision specified')) ui.debug(_('Collapsing revisions %s\n') % revs) for r in revs: if repo[r].user() != ui.username() and not opts['force']: raise util.Abort(_('revision %s does not belong to %s\n') % (r, ui.username())) if r != last: children = repo[r].children() if len(children) > 1: for c in children: if not c.rev() in revs: raise util.Abort(_('revision %s has child %s not ' 'being collapsed, please rebase\n') % (r, c.rev())) if r != first: parents = repo[r].parents() if len(parents) > 1: for p in parents: if not p.rev() in revs: raise util.Abort(_('revision %s has parent %s not ' 'being collapsed.') % (r, p.rev())) if len(repo[first].parents()) > 1: raise util.Abort(_('start revision %s has multiple parents, ' 'won\'t collapse.') % first) cmdutil.bail_if_changed(repo) parent = repo[first].parents()[0] tomove = list(repo.changelog.descendants(last)) movemap = dict.fromkeys(tomove, nullrev) ui.debug(_('will move revisions: %s\n') % tomove) origparent = repo['.'].rev() collapsed = None try: collapsed = makecollapsed(ui, repo, parent, revs) movemap[max(revs)] = collapsed movedescendants(ui, repo, collapsed, tomove, movemap) except: merge.update(repo, repo[origparent].rev(), False, True, False) if collapsed: repair.strip(ui, repo, collapsed.node(), "strip") raise if not opts['keep']: ui.debug(_('stripping revision %d\n') % first) repair.strip(ui, repo, repo[first].node(), "strip") ui.status(_('collapse completed\n'))
def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch Rebase uses repeated merging to graft changesets from one part of history onto another. This can be useful for linearizing local changes relative to a master development tree. If a rebase is interrupted to manually resolve a merge, it can be continued with --continue/-c or aborted with --abort/-a. """ originalwd = target = None external = nullrev state = {} skipped = set() lock = wlock = None try: lock = repo.lock() wlock = repo.wlock() # Validate input and define rebasing points destf = opts.get('dest', None) srcf = opts.get('source', None) basef = opts.get('base', None) contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) extrafn = opts.get('extrafn') keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) if contf or abortf: if contf and abortf: raise error.ParseError('rebase', _('cannot use both abort and continue')) if collapsef: raise error.ParseError( 'rebase', _('cannot use collapse with continue or abort')) if srcf or basef or destf: raise error.ParseError( 'rebase', _('abort and continue do not allow specifying revisions')) (originalwd, target, state, collapsef, keepf, keepbranchesf, external) = restorestatus(repo) if abortf: abort(repo, originalwd, target, state) return else: if srcf and basef: raise error.ParseError( 'rebase', _('cannot specify both a ' 'revision and a base')) cmdutil.bail_if_changed(repo) result = buildstate(repo, destf, srcf, basef, collapsef) if result: originalwd, target, state, external = result else: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) return if keepbranchesf: if extrafn: raise error.ParseError( 'rebase', _('cannot use both keepbranches and extrafn')) def extrafn(ctx, extra): extra['branch'] = ctx.branch() # Rebase targetancestors = list(repo.changelog.ancestors(target)) targetancestors.append(target) for rev in sorted(state): if state[rev] == -1: storestatus(repo, originalwd, target, state, collapsef, keepf, keepbranchesf, external) rebasenode(repo, rev, target, state, skipped, targetancestors, collapsef, extrafn) ui.note(_('rebase merging completed\n')) if collapsef: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) concludenode(repo, rev, p1, external, state, collapsef, last=True, skipped=skipped, extrafn=extrafn) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) if not keepf: # Remove no more useful revisions if set(repo.changelog.descendants(min(state))) - set(state): ui.warn( _("warning: new changesets detected on source branch, " "not stripping\n")) else: repair.strip(ui, repo, repo[min(state)].node(), "strip") clearstatus(repo) ui.status(_("rebase completed\n")) if os.path.exists(repo.sjoin('undo')): util.unlink(repo.sjoin('undo')) if skipped: ui.note(_("%d revisions have been skipped\n") % len(skipped)) finally: release(lock, wlock)
def do_collapse(ui, repo, first, last, revs, movelog, timedelta, opts): ui.debug(_('Collapsing revisions %s\n') % revs) if opts['debugdelay']: debug_delay = float(opts['debugdelay']) else: debug_delay = False for r in revs: if repo[r].user() != ui.username() and not opts['force']: raise util.Abort(_('revision %s does not belong to %s\n') % (r, ui.username())) if r != last: children = repo[r].children() if len(children) > 1: for c in children: if not c.rev() in revs: raise util.Abort(_('revision %s has child %s not ' 'being collapsed, please rebase\n') % (r, c.rev())) if r != first: parents = repo[r].parents() if len(parents) > 1: for p in parents: if not p.rev() in revs: raise util.Abort(_('revision %s has parent %s not ' 'being collapsed.') % (r, p.rev())) if len(repo[first].parents()) > 1: raise util.Abort(_('start revision %s has multiple parents, ' 'won\'t collapse.') % first) try: cmdutil.bailifchanged(repo) except AttributeError: cmdutil.bail_if_changed(repo) parent = repo[first].parents()[0] tomove = list(repo.changelog.descendants(last)) head_hgtags = get_hgtags_from_heads(ui, repo, last) if '.hgtags' in parent: parent_hgtags = parent['.hgtags'].data() else: parent_hgtags = False movemap = dict.fromkeys(tomove, nullrev) ui.debug(_('will move revisions: %s\n') % tomove) tagsmap = dict() if opts['noop']: ui.status(_('noop: not collapsing\n')) else: origparent = repo['.'].rev() collapsed = None try: branch = repo[last].branch() collapsed = makecollapsed(ui, repo, parent, revs, branch, tagsmap, parent_hgtags, movelog, opts) movemap[max(revs)] = collapsed movedescendants(ui, repo, collapsed, tomove, movemap, tagsmap, parent_hgtags, movelog, debug_delay) fix_hgtags(ui, repo, head_hgtags, tagsmap) except: merge.update(repo, repo[origparent].rev(), False, True, False) if collapsed: repair.strip(ui, repo, collapsed.node(), "strip") raise if not opts['keep']: ui.debug(_('stripping revision %d\n') % first) repair.strip(ui, repo, repo[first].node(), "strip") ui.status(_('collapse completed\n'))
def push(repo, dest, force, revs): """push revisions starting at a specified head back to Subversion. """ assert not revs, 'designated revisions for push remains unimplemented.' cmdutil.bail_if_changed(repo) checkpush = getattr(repo, 'checkpush', None) if checkpush: checkpush(force, revs) ui = repo.ui old_encoding = util.swap_out_encoding() # TODO: implement --rev/#rev support # TODO: do credentials specified in the URL still work? svnurl = repo.ui.expandpath(dest.svnurl) svn = dest.svn meta = repo.svnmeta(svn.uuid, svn.subdir) # Strategy: # 1. Find all outgoing commits from this head if len(repo.parents()) != 1: ui.status('Cowardly refusing to push branch merge\n') return 0 # results in nonzero exit status, see hg's commands.py workingrev = repo.parents()[0] ui.status('searching for changes\n') hashes = meta.revmap.hashes() outgoing = util.outgoing_revisions(repo, hashes, workingrev.node()) if not (outgoing and len(outgoing)): ui.status('no changes found\n') return 1 # so we get a sane exit status, see hg's commands.push while outgoing: # 2. Commit oldest revision that needs to be pushed oldest = outgoing.pop(-1) old_ctx = repo[oldest] old_pars = old_ctx.parents() if len(old_pars) != 1: ui.status('Found a branch merge, this needs discussion and ' 'implementation.\n') return 0 # results in nonzero exit status, see hg's commands.py # We will commit to svn against this node's parent rev. Any file-level # conflicts here will result in an error reported by svn. base_ctx = old_pars[0] base_revision = hashes[base_ctx.node()][0] svnbranch = base_ctx.branch() # Find most recent svn commit we have on this branch. # This node will become the nearest known ancestor of the pushed rev. oldtipctx = base_ctx old_children = oldtipctx.descendants() seen = set(c.node() for c in old_children) samebranchchildren = [ c for c in old_children if c.branch() == svnbranch and c.node() in hashes ] if samebranchchildren: # The following relies on descendants being sorted by rev. oldtipctx = samebranchchildren[-1] # All set, so commit now. try: pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn) except pushmod.NoFilesException: ui.warn( "Could not push revision %s because it had no changes in svn.\n" % old_ctx) return 1 # 3. Fetch revisions from svn # TODO: this probably should pass in the source explicitly - rev too? r = repo.pull(dest, force=force) assert not r or r == 0 # 4. Find the new head of the target branch # We expect to get our own new commit back, but we might also get other # commits that happened since our last pull, or even right after our own # commit (race). for c in oldtipctx.descendants(): if c.node() not in seen and c.branch() == svnbranch: newtipctx = c # 5. Rebase all children of the currently-pushing rev to the new head heads = repo.heads(old_ctx.node()) for needs_transplant in heads: def extrafn(ctx, extra): if ctx.node() == oldest: return extra['branch'] = ctx.branch() # TODO: can we avoid calling our own rebase wrapper here? rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn, svnsourcerev=needs_transplant) # Reload the repo after the rebase. Do not reuse contexts across this. newtip = newtipctx.node() repo = hg.repository(ui, meta.path) newtipctx = repo[newtip] # Rewrite the node ids in outgoing to their rebased versions. rebasemap = dict() for child in newtipctx.descendants(): rebasesrc = child.extra().get('rebase_source') if rebasesrc: rebasemap[node.bin(rebasesrc)] = child.node() outgoing = [rebasemap.get(n) or n for n in outgoing] # TODO: stop constantly creating the SVNMeta instances. meta = repo.svnmeta(svn.uuid, svn.subdir) hashes = meta.revmap.hashes() util.swap_out_encoding(old_encoding) return 1 # so we get a sane exit status, see hg's commands.push
def do_collapse(ui, repo, first, last, revs, movelog, timedelta, opts): ui.debug(_('Collapsing revisions %s\n') % revs) if opts['debugdelay']: debug_delay = float(opts['debugdelay']) else: debug_delay = False for r in revs: if repo[r].user() != ui.username() and not opts['force']: raise util.Abort( _('revision %s does not belong to %s\n') % (r, ui.username())) if r != last: children = repo[r].children() if len(children) > 1: for c in children: if not c.rev() in revs: raise util.Abort( _('revision %s has child %s not ' 'being collapsed, please rebase\n') % (r, c.rev())) if r != first: parents = repo[r].parents() if len(parents) > 1: for p in parents: if not p.rev() in revs: raise util.Abort( _('revision %s has parent %s not ' 'being collapsed.') % (r, p.rev())) if len(repo[first].parents()) > 1: raise util.Abort( _('start revision %s has multiple parents, ' 'won\'t collapse.') % first) try: cmdutil.bailifchanged(repo) except AttributeError: cmdutil.bail_if_changed(repo) parent = repo[first].parents()[0] tomove = list(repo.changelog.descendants([last])) head_hgtags = get_hgtags_from_heads(ui, repo, last) if '.hgtags' in parent: parent_hgtags = parent['.hgtags'].data() else: parent_hgtags = False movemap = dict.fromkeys(tomove, nullrev) ui.debug(_('will move revisions: %s\n') % tomove) tagsmap = dict() if opts['noop']: ui.status(_('noop: not collapsing\n')) else: origparent = repo['.'].rev() collapsed = None try: branch = repo[last].branch() collapsed = makecollapsed(ui, repo, parent, revs, branch, tagsmap, parent_hgtags, movelog, opts) movemap[max(revs)] = collapsed movedescendants(ui, repo, collapsed, tomove, movemap, tagsmap, parent_hgtags, movelog, debug_delay) fix_hgtags(ui, repo, head_hgtags, tagsmap) except: merge.update(repo, repo[origparent].rev(), False, True, False) if collapsed: repair.strip(ui, repo, collapsed.node(), "strip") raise if not opts['keep']: ui.debug(_('stripping revision %d\n') % first) repair.strip(ui, repo, repo[first].node(), "strip") ui.status(_('collapse completed\n'))
def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch Rebase uses repeated merging to graft changesets from one part of history onto another. This can be useful for linearizing local changes relative to a master development tree. If a rebase is interrupted to manually resolve a merge, it can be continued with --continue/-c or aborted with --abort/-a. """ originalwd = target = None external = nullrev state = {} skipped = set() targetancestors = set() lock = wlock = None try: lock = repo.lock() wlock = repo.wlock() # Validate input and define rebasing points destf = opts.get('dest', None) srcf = opts.get('source', None) basef = opts.get('base', None) contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) extrafn = opts.get('extrafn') keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) detachf = opts.get('detach', False) if contf or abortf: if contf and abortf: raise error.ParseError('rebase', _('cannot use both abort and continue')) if collapsef: raise error.ParseError( 'rebase', _('cannot use collapse with continue or abort')) if detachf: raise error.ParseError( 'rebase', _('cannot use detach with continue or abort')) if srcf or basef or destf: raise error.ParseError( 'rebase', _('abort and continue do not allow specifying revisions')) (originalwd, target, state, collapsef, keepf, keepbranchesf, external) = restorestatus(repo) if abortf: abort(repo, originalwd, target, state) return else: if srcf and basef: raise error.ParseError( 'rebase', _('cannot specify both a ' 'revision and a base')) if detachf: if not srcf: raise error.ParseError( 'rebase', _('detach requires a revision to be specified')) if basef: raise error.ParseError( 'rebase', _('cannot specify a base with detach')) cmdutil.bail_if_changed(repo) result = buildstate(repo, destf, srcf, basef, detachf) if not result: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) return else: originalwd, target, state = result if collapsef: targetancestors = set(repo.changelog.ancestors(target)) external = checkexternal(repo, state, targetancestors) if keepbranchesf: if extrafn: raise error.ParseError( 'rebase', _('cannot use both keepbranches and extrafn')) def extrafn(ctx, extra): extra['branch'] = ctx.branch() # Rebase if not targetancestors: targetancestors = set(repo.changelog.ancestors(target)) targetancestors.add(target) for rev in sorted(state): if state[rev] == -1: ui.debug("rebasing %d:%s\n" % (rev, repo[rev])) storestatus(repo, originalwd, target, state, collapsef, keepf, keepbranchesf, external) p1, p2 = defineparents(repo, rev, target, state, targetancestors) if len(repo.parents()) == 2: repo.ui.debug('resuming interrupted rebase\n') else: stats = rebasenode(repo, rev, p1, p2, state) if stats and stats[3] > 0: raise util.Abort( _('fix unresolved conflicts with hg ' 'resolve then run hg rebase --continue')) updatedirstate(repo, rev, target, p2) if not collapsef: extra = {'rebase_source': repo[rev].hex()} if extrafn: extrafn(repo[rev], extra) newrev = concludenode(repo, rev, p1, p2, extra=extra) else: # Skip commit if we are collapsing repo.dirstate.setparents(repo[p1].node()) newrev = None # Update the state if newrev is not None: state[rev] = repo[newrev].rev() else: if not collapsef: ui.note(_('no changes, revision %d skipped\n') % rev) ui.debug('next revision set to %s\n' % p1) skipped.add(rev) state[rev] = p1 ui.note(_('rebase merging completed\n')) if collapsef: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) commitmsg = 'Collapsed revision' for rebased in state: if rebased not in skipped and state[rebased] != nullmerge: commitmsg += '\n* %s' % repo[rebased].description() commitmsg = ui.edit(commitmsg, repo.ui.username()) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extra=extrafn) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) if not keepf: # Remove no more useful revisions rebased = [rev for rev in state if state[rev] != nullmerge] if rebased: if set(repo.changelog.descendants(min(rebased))) - set(state): ui.warn( _("warning: new changesets detected " "on source branch, not stripping\n")) else: repair.strip(ui, repo, repo[min(rebased)].node(), "strip") clearstatus(repo) ui.status(_("rebase completed\n")) if os.path.exists(repo.sjoin('undo')): util.unlink(repo.sjoin('undo')) if skipped: ui.note(_("%d revisions have been skipped\n") % len(skipped)) finally: release(lock, wlock)