hgext/drophack.py
changeset 790 5af309865040
child 812 60dd0c401034
equal deleted inserted replaced
789:0d2bb0282e78 790:5af309865040
       
     1 # This software may be used and distributed according to the terms of the
       
     2 # GNU General Public License version 2 or any later version.
       
     3 '''This extension add a hacky command to drop changeset during review
       
     4 
       
     5 This extension is intended as a temporary hack to allow Matt Mackall to use
       
     6 evolve in the Mercurial review it self. You should probably not use it if your
       
     7 name is not Matt Mackall.
       
     8 '''
       
     9 
       
    10 import os
       
    11 import time
       
    12 import contextlib
       
    13 
       
    14 from mercurial.i18n import _
       
    15 from mercurial import cmdutil
       
    16 from mercurial import repair
       
    17 from mercurial import scmutil
       
    18 from mercurial import lock as lockmod
       
    19 from mercurial import util
       
    20 from mercurial import commands
       
    21 
       
    22 cmdtable = {}
       
    23 command = cmdutil.command(cmdtable)
       
    24 
       
    25 
       
    26 @contextlib.contextmanager
       
    27 def timed(ui, caption):
       
    28     ostart = os.times()
       
    29     cstart = time.time()
       
    30     yield
       
    31     cstop = time.time()
       
    32     ostop = os.times()
       
    33     wall = cstop - cstart
       
    34     user = ostop[0] - ostart[0]
       
    35     sys  = ostop[1] - ostart[1]
       
    36     comb = user + sys
       
    37     ui.write("%s: wall %f comb %f user %f sys %f\n"
       
    38              % (caption, wall, comb, user, sys))
       
    39 
       
    40 def obsmarkerchainfrom(obsstore, nodes):
       
    41     """return all marker chain starting from node
       
    42 
       
    43     Starting from mean "use as successors"."""
       
    44     # XXX need something smarter for descendant of bumped changeset
       
    45     seennodes = set(nodes)
       
    46     seenmarkers = set()
       
    47     pendingnodes = set(nodes)
       
    48     precursorsmarkers = obsstore.precursors
       
    49     while pendingnodes:
       
    50         current = pendingnodes.pop()
       
    51         new = set()
       
    52         for precmark in precursorsmarkers.get(current, ()):
       
    53             if precmark in seenmarkers:
       
    54                 continue
       
    55             seenmarkers.add(precmark)
       
    56             new.add(precmark[0])
       
    57             yield precmark
       
    58         new -= seennodes
       
    59         pendingnodes |= new
       
    60 
       
    61 def stripmarker(ui, repo, markers):
       
    62     """remove <markers> from the repo obsstore
       
    63 
       
    64     The old obsstore content is saved in a `obsstore.prestrip` file
       
    65     """
       
    66     repo = repo.unfiltered()
       
    67     repo.destroying()
       
    68     oldmarkers = list(repo.obsstore._all)
       
    69     util.rename(repo.sjoin('obsstore'),
       
    70                 repo.join('obsstore.prestrip'))
       
    71     del repo.obsstore # drop the cache
       
    72     newstore = repo.obsstore
       
    73     assert not newstore # should be empty after rename
       
    74     tr = repo.transaction('drophack')
       
    75     try:
       
    76         for m in oldmarkers:
       
    77             if m not in markers:
       
    78                 newstore.add(tr, [m])
       
    79         tr.close()
       
    80     finally:
       
    81         tr.release()
       
    82     repo.destroyed()
       
    83 
       
    84 
       
    85 @command('drop', [('r', 'rev', [], 'revision to update')], _('[-r] revs'))
       
    86 def cmddrop(ui, repo, *revs, **opts):
       
    87     """I'm hacky do not use me!
       
    88 
       
    89     This command strip a changeset, its precursors and all obsolescence marker
       
    90     associated to its chain.
       
    91 
       
    92     There is no way to limit the extend of the purge yet. You may have to
       
    93     repull from other source to get some changeset and obsolescence marker
       
    94     back.
       
    95 
       
    96     This intended for Matt Mackall usage only. do not use me.
       
    97     """
       
    98     revs = list(revs)
       
    99     revs.extend(opts['rev'])
       
   100     if not revs:
       
   101         revs = ['.']
       
   102     # get the changeset
       
   103     revs = scmutil.revrange(repo, revs)
       
   104     if not revs:
       
   105         ui.write_err('no revision to drop\n')
       
   106         return 1
       
   107     # lock from the beginning to prevent race
       
   108     wlock = lock = None
       
   109     try:
       
   110         lock = repo.wlock()
       
   111         lock = repo.lock()
       
   112         # check they have no children
       
   113         if repo.revs('%ld and public()', revs):
       
   114             ui.write_err('cannot drop public revision')
       
   115             return 1
       
   116         if repo.revs('children(%ld) - %ld', revs, revs):
       
   117             ui.write_err('cannot drop revision with children')
       
   118             return 1
       
   119         if repo.revs('. and %ld', revs):
       
   120             newrevs = repo.revs('max(::. - %ld)', revs)
       
   121             if newrevs:
       
   122                 assert len(newrevs) == 1
       
   123                 newrev = newrevs[0]
       
   124             else:
       
   125                 newrev = -1
       
   126             commands.update(ui, repo, newrev)
       
   127             ui.status(_('working directory now at %s\n') % repo[newrev])
       
   128         # get all markers and successors up to root
       
   129         nodes = [repo[r].node() for r in revs]
       
   130         with timed(ui, 'search obsmarker'):
       
   131             markers = set(obsmarkerchainfrom(repo.obsstore, nodes))
       
   132         ui.write('%i obsmarkers found\n' % len(markers))
       
   133         cl = repo.unfiltered().changelog
       
   134         with timed(ui, 'search nodes'):
       
   135             allnodes = set(nodes)
       
   136             allnodes.update(m[0] for m in markers if cl.hasnode(m[0]))
       
   137         ui.write('%i nodes found\n' % len(allnodes))
       
   138         cl = repo.changelog
       
   139         visiblenodes = set(n for n in allnodes if cl.hasnode(n))
       
   140         # check constraint again
       
   141         if repo.revs('%ln and public()', visiblenodes):
       
   142             ui.write_err('cannot drop public revision')
       
   143             return 1
       
   144         if repo.revs('children(%ln) - %ln', visiblenodes, visiblenodes):
       
   145             ui.write_err('cannot drop revision with children')
       
   146             return 1
       
   147 
       
   148         if markers:
       
   149             # strip them
       
   150             with timed(ui, 'strip obsmarker'):
       
   151                 stripmarker(ui, repo, markers)
       
   152         # strip the changeset
       
   153         with timed(ui, 'strip nodes'):
       
   154             repair.strip(ui, repo, allnodes, backup="all", topic='drophack')
       
   155 
       
   156     finally:
       
   157         lockmod.release(lock, wlock)
       
   158 
       
   159     # rewrite the whole file.
       
   160     # print data.
       
   161     # - time to compute the chain
       
   162     # - time to strip the changeset
       
   163     # - time to strip the obs marker.