|
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. |