hgext/drophack.py
changeset 1809 7edd4ceefce0
parent 1808 202ac6c94b7f
child 1810 ce4018f03520
equal deleted inserted replaced
1808:202ac6c94b7f 1809:7edd4ceefce0
     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     newmarkers = [m for m in oldmarkers if m not in markers]
       
    75     tr = repo.transaction('drophack')
       
    76     try:
       
    77         newstore.add(tr, newmarkers)
       
    78         tr.close()
       
    79     finally:
       
    80         tr.release()
       
    81     repo.destroyed()
       
    82 
       
    83 
       
    84 @command('drop', [('r', 'rev', [], 'revision to update')], _('[-r] revs'))
       
    85 def cmddrop(ui, repo, *revs, **opts):
       
    86     """I'm hacky do not use me!
       
    87 
       
    88     This command strip a changeset, its precursors and all obsolescence marker
       
    89     associated to its chain.
       
    90 
       
    91     There is no way to limit the extend of the purge yet. You may have to
       
    92     repull from other source to get some changeset and obsolescence marker
       
    93     back.
       
    94 
       
    95     This intended for Matt Mackall usage only. do not use me.
       
    96     """
       
    97     revs = list(revs)
       
    98     revs.extend(opts['rev'])
       
    99     if not revs:
       
   100         revs = ['.']
       
   101     # get the changeset
       
   102     revs = scmutil.revrange(repo, revs)
       
   103     if not revs:
       
   104         ui.write_err('no revision to drop\n')
       
   105         return 1
       
   106     # lock from the beginning to prevent race
       
   107     wlock = lock = None
       
   108     try:
       
   109         wlock = repo.wlock()
       
   110         lock = repo.lock()
       
   111         # check they have no children
       
   112         if repo.revs('%ld and public()', revs):
       
   113             ui.write_err('cannot drop public revision')
       
   114             return 1
       
   115         if repo.revs('children(%ld) - %ld', revs, revs):
       
   116             ui.write_err('cannot drop revision with children')
       
   117             return 1
       
   118         if repo.revs('. and %ld', revs):
       
   119             newrevs = repo.revs('max(::. - %ld)', revs)
       
   120             if newrevs:
       
   121                 assert len(newrevs) == 1
       
   122                 newrev = newrevs.first()
       
   123             else:
       
   124                 newrev = -1
       
   125             commands.update(ui, repo, newrev)
       
   126             ui.status(_('working directory now at %s\n') % repo[newrev])
       
   127         # get all markers and successors up to root
       
   128         nodes = [repo[r].node() for r in revs]
       
   129         with timed(ui, 'search obsmarker'):
       
   130             markers = set(obsmarkerchainfrom(repo.obsstore, nodes))
       
   131         ui.write('%i obsmarkers found\n' % len(markers))
       
   132         cl = repo.unfiltered().changelog
       
   133         with timed(ui, 'search nodes'):
       
   134             allnodes = set(nodes)
       
   135             allnodes.update(m[0] for m in markers if cl.hasnode(m[0]))
       
   136         ui.write('%i nodes found\n' % len(allnodes))
       
   137         cl = repo.changelog
       
   138         visiblenodes = set(n for n in allnodes if cl.hasnode(n))
       
   139         # check constraint again
       
   140         if repo.revs('%ln and public()', visiblenodes):
       
   141             ui.write_err('cannot drop public revision')
       
   142             return 1
       
   143         if repo.revs('children(%ln) - %ln', visiblenodes, visiblenodes):
       
   144             ui.write_err('cannot drop revision with children')
       
   145             return 1
       
   146 
       
   147         if markers:
       
   148             # strip them
       
   149             with timed(ui, 'strip obsmarker'):
       
   150                 stripmarker(ui, repo, markers)
       
   151         # strip the changeset
       
   152         with timed(ui, 'strip nodes'):
       
   153             repair.strip(ui, repo, list(allnodes), backup="all",
       
   154                          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.