Mercurial > evolve
view hgext/drophack.py @ 1106:6b0cf1b73693 stable
evolve: replace each obsolete sha1 in the description with its latest successor
Obsolete csets are hidden by default and don't get pushed to the parent repo.
In order to avoid broken references in commit messages, it makes sense to evolve
those references to the latest and greatest successor, as each cset containing
them is evolved. Of course, stale references can still occur if a commit in
branch 'A' references something in branch 'B', and that something in 'B' is
evolved but 'A' isn't subsequently evolved.
This alleviates the user that is evolving a series of commits from having to
1) recognize that there is a hash that needs updating in any one of the series
2) look up the latest successor manually
3) hg amend -e
The regular expression for matching and the logic for replacing are borrowed
from the convert extension [1].
It might be nice for the output to state the reason that the reference couldn't
be updated (it was pruned, split or diverged), but that may be excessive for
something only displayed in verbose mode. (Maybe it should be a ui.status()
instead?)
[1] http://selenic.com/hg/rev/45562379ce4e
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Sat, 09 Aug 2014 19:12:16 -0400 |
parents | f7f4a1fac6c0 |
children | 1a39b1b8e092 |
line wrap: on
line source
# This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. '''This extension add a hacky command to drop changeset during review This extension is intended as a temporary hack to allow Matt Mackall to use evolve in the Mercurial review it self. You should probably not use it if your name is not Matt Mackall. ''' import os import time import contextlib from mercurial.i18n import _ from mercurial import cmdutil from mercurial import repair from mercurial import scmutil from mercurial import lock as lockmod from mercurial import util from mercurial import commands cmdtable = {} command = cmdutil.command(cmdtable) @contextlib.contextmanager def timed(ui, caption): ostart = os.times() cstart = time.time() yield cstop = time.time() ostop = os.times() wall = cstop - cstart user = ostop[0] - ostart[0] sys = ostop[1] - ostart[1] comb = user + sys ui.write("%s: wall %f comb %f user %f sys %f\n" % (caption, wall, comb, user, sys)) def obsmarkerchainfrom(obsstore, nodes): """return all marker chain starting from node Starting from mean "use as successors".""" # XXX need something smarter for descendant of bumped changeset seennodes = set(nodes) seenmarkers = set() pendingnodes = set(nodes) precursorsmarkers = obsstore.precursors while pendingnodes: current = pendingnodes.pop() new = set() for precmark in precursorsmarkers.get(current, ()): if precmark in seenmarkers: continue seenmarkers.add(precmark) new.add(precmark[0]) yield precmark new -= seennodes pendingnodes |= new def stripmarker(ui, repo, markers): """remove <markers> from the repo obsstore The old obsstore content is saved in a `obsstore.prestrip` file """ repo = repo.unfiltered() repo.destroying() oldmarkers = list(repo.obsstore._all) util.rename(repo.sjoin('obsstore'), repo.join('obsstore.prestrip')) del repo.obsstore # drop the cache newstore = repo.obsstore assert not newstore # should be empty after rename newmarkers = [m for m in oldmarkers if m not in markers] tr = repo.transaction('drophack') try: newstore.add(tr, newmarkers) tr.close() finally: tr.release() repo.destroyed() @command('drop', [('r', 'rev', [], 'revision to update')], _('[-r] revs')) def cmddrop(ui, repo, *revs, **opts): """I'm hacky do not use me! This command strip a changeset, its precursors and all obsolescence marker associated to its chain. There is no way to limit the extend of the purge yet. You may have to repull from other source to get some changeset and obsolescence marker back. This intended for Matt Mackall usage only. do not use me. """ revs = list(revs) revs.extend(opts['rev']) if not revs: revs = ['.'] # get the changeset revs = scmutil.revrange(repo, revs) if not revs: ui.write_err('no revision to drop\n') return 1 # lock from the beginning to prevent race wlock = lock = None try: wlock = repo.wlock() lock = repo.lock() # check they have no children if repo.revs('%ld and public()', revs): ui.write_err('cannot drop public revision') return 1 if repo.revs('children(%ld) - %ld', revs, revs): ui.write_err('cannot drop revision with children') return 1 if repo.revs('. and %ld', revs): newrevs = repo.revs('max(::. - %ld)', revs) if newrevs: assert len(newrevs) == 1 newrev = newrevs[0] else: newrev = -1 commands.update(ui, repo, newrev) ui.status(_('working directory now at %s\n') % repo[newrev]) # get all markers and successors up to root nodes = [repo[r].node() for r in revs] with timed(ui, 'search obsmarker'): markers = set(obsmarkerchainfrom(repo.obsstore, nodes)) ui.write('%i obsmarkers found\n' % len(markers)) cl = repo.unfiltered().changelog with timed(ui, 'search nodes'): allnodes = set(nodes) allnodes.update(m[0] for m in markers if cl.hasnode(m[0])) ui.write('%i nodes found\n' % len(allnodes)) cl = repo.changelog visiblenodes = set(n for n in allnodes if cl.hasnode(n)) # check constraint again if repo.revs('%ln and public()', visiblenodes): ui.write_err('cannot drop public revision') return 1 if repo.revs('children(%ln) - %ln', visiblenodes, visiblenodes): ui.write_err('cannot drop revision with children') return 1 if markers: # strip them with timed(ui, 'strip obsmarker'): stripmarker(ui, repo, markers) # strip the changeset with timed(ui, 'strip nodes'): repair.strip(ui, repo, allnodes, backup="all", topic='drophack') finally: lockmod.release(lock, wlock) # rewrite the whole file. # print data. # - time to compute the chain # - time to strip the changeset # - time to strip the obs marker.