1 """reduce the changesets evolution feature scope for early and noob friendly ui |
|
2 |
|
3 the full scale changeset evolution have some massive bleeding edge and it is |
|
4 very easy for people not very intimate with the concept to end up in intricate |
|
5 situation. in order to get some of the benefit sooner, this extension is |
|
6 disabling some of the less polished aspect of evolution. it should gradually |
|
7 get thinner and thinner as changeset evolution will get more polished. this |
|
8 extension is only recommended for large scale organisations. individual user |
|
9 should probably stick on using evolution in its current state, understand its |
|
10 concept and provide feedback |
|
11 |
|
12 This extension provides the ability to "inhibit" obsolescence markers. obsolete |
|
13 revision can be cheaply brought back to life that way. |
|
14 However as the inhibitor are not fitting in an append only model, this is |
|
15 incompatible with sharing mutable history. |
|
16 """ |
|
17 from mercurial import localrepo |
|
18 from mercurial import obsolete |
|
19 from mercurial import extensions |
|
20 from mercurial import cmdutil |
|
21 from mercurial import error |
|
22 from mercurial import scmutil |
|
23 from mercurial import commands |
|
24 from mercurial import lock as lockmod |
|
25 from mercurial import bookmarks |
|
26 from mercurial import util |
|
27 from mercurial.i18n import _ |
|
28 |
|
29 cmdtable = {} |
|
30 command = cmdutil.command(cmdtable) |
|
31 |
|
32 def _inhibitenabled(repo): |
|
33 return util.safehasattr(repo, '_obsinhibit') |
|
34 |
|
35 def reposetup(ui, repo): |
|
36 |
|
37 class obsinhibitedrepo(repo.__class__): |
|
38 |
|
39 @localrepo.storecache('obsinhibit') |
|
40 def _obsinhibit(self): |
|
41 # XXX we should make sure it is invalidated by transaction failure |
|
42 obsinhibit = set() |
|
43 raw = self.svfs.tryread('obsinhibit') |
|
44 for i in xrange(0, len(raw), 20): |
|
45 obsinhibit.add(raw[i:i+20]) |
|
46 return obsinhibit |
|
47 |
|
48 def commit(self, *args, **kwargs): |
|
49 newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs) |
|
50 if newnode is not None: |
|
51 _inhibitmarkers(repo, [newnode]) |
|
52 return newnode |
|
53 |
|
54 repo.__class__ = obsinhibitedrepo |
|
55 |
|
56 def _update(orig, ui, repo, *args, **kwargs): |
|
57 """ |
|
58 When moving to a commit we want to inhibit any obsolete commit affecting |
|
59 the changeset we are updating to. In other words we don't want any visible |
|
60 commit to be obsolete. |
|
61 """ |
|
62 wlock = None |
|
63 try: |
|
64 # Evolve is running a hook on lock release to display a warning message |
|
65 # if the workind dir's parent is obsolete. |
|
66 # We take the lock here to make sure that we inhibit the parent before |
|
67 # that hook get a chance to run. |
|
68 wlock = repo.wlock() |
|
69 res = orig(ui, repo, *args, **kwargs) |
|
70 newhead = repo['.'].node() |
|
71 _inhibitmarkers(repo, [newhead]) |
|
72 return res |
|
73 finally: |
|
74 lockmod.release(wlock) |
|
75 |
|
76 def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs): |
|
77 """ Add inhibition markers to every obsolete bookmarks """ |
|
78 repo = bkmstoreinst._repo |
|
79 bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()] |
|
80 _inhibitmarkers(repo, bkmstorenodes) |
|
81 return orig(bkmstoreinst, *args, **kwargs) |
|
82 |
|
83 def _bookmark(orig, ui, repo, *bookmarks, **opts): |
|
84 """ Add a -D option to the bookmark command, map it to prune -B """ |
|
85 haspruneopt = opts.get('prune', False) |
|
86 if not haspruneopt: |
|
87 return orig(ui, repo, *bookmarks, **opts) |
|
88 |
|
89 # Call prune -B |
|
90 evolve = extensions.find('evolve') |
|
91 optsdict = { |
|
92 'new': [], |
|
93 'succ': [], |
|
94 'rev': [], |
|
95 'bookmark': bookmarks[0], |
|
96 'keep': None, |
|
97 'biject': False, |
|
98 } |
|
99 evolve.cmdprune(ui, repo, **optsdict) |
|
100 |
|
101 # obsolescence inhibitor |
|
102 ######################## |
|
103 |
|
104 def _schedulewrite(tr, obsinhibit): |
|
105 """Make sure on disk content will be updated on transaction commit""" |
|
106 def writer(fp): |
|
107 """Serialize the inhibited list to disk. |
|
108 """ |
|
109 raw = ''.join(obsinhibit) |
|
110 fp.write(raw) |
|
111 tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer) |
|
112 tr.hookargs['obs_inbihited'] = '1' |
|
113 |
|
114 def _filterpublic(repo, nodes): |
|
115 """filter out inhibitor on public changeset |
|
116 |
|
117 Public changesets are already immune to obsolescence""" |
|
118 getrev = repo.changelog.nodemap.get |
|
119 getphase = repo._phasecache.phase |
|
120 return (n for n in repo._obsinhibit |
|
121 if getrev(n) is not None and getphase(repo, getrev(n))) |
|
122 |
|
123 def _inhibitmarkers(repo, nodes): |
|
124 """add marker inhibitor for all obsolete revision under <nodes> |
|
125 |
|
126 Content of <nodes> and all mutable ancestors are considered. Marker for |
|
127 obsolete revision only are created. |
|
128 """ |
|
129 if not _inhibitenabled(repo): |
|
130 return |
|
131 |
|
132 newinhibit = repo.set('::%ln and obsolete()', nodes) |
|
133 if newinhibit: |
|
134 lock = tr = None |
|
135 try: |
|
136 lock = repo.lock() |
|
137 tr = repo.transaction('obsinhibit') |
|
138 repo._obsinhibit.update(c.node() for c in newinhibit) |
|
139 _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit)) |
|
140 repo.invalidatevolatilesets() |
|
141 tr.close() |
|
142 finally: |
|
143 lockmod.release(tr, lock) |
|
144 |
|
145 def _deinhibitmarkers(repo, nodes): |
|
146 """lift obsolescence inhibition on a set of nodes |
|
147 |
|
148 This will be triggered when inhibited nodes received new obsolescence |
|
149 markers. Otherwise the new obsolescence markers would also be inhibited. |
|
150 """ |
|
151 if not _inhibitenabled(repo): |
|
152 return |
|
153 |
|
154 deinhibited = repo._obsinhibit & set(nodes) |
|
155 if deinhibited: |
|
156 tr = repo.transaction('obsinhibit') |
|
157 try: |
|
158 repo._obsinhibit -= deinhibited |
|
159 _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit)) |
|
160 repo.invalidatevolatilesets() |
|
161 tr.close() |
|
162 finally: |
|
163 tr.release() |
|
164 |
|
165 def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None): |
|
166 """wrap markers create to make sure we de-inhibit target nodes""" |
|
167 # wrapping transactio to unify the one in each function |
|
168 lock = tr = None |
|
169 try: |
|
170 lock = repo.lock() |
|
171 tr = repo.transaction('add-obsolescence-marker') |
|
172 orig(repo, relations, flag, date, metadata) |
|
173 precs = (r[0].node() for r in relations) |
|
174 _deinhibitmarkers(repo, precs) |
|
175 tr.close() |
|
176 finally: |
|
177 lockmod.release(tr, lock) |
|
178 |
|
179 def transactioncallback(orig, repo, desc, *args, **kwargs): |
|
180 """ Wrap localrepo.transaction to inhibit new obsolete changes """ |
|
181 def inhibitposttransaction(transaction): |
|
182 # At the end of the transaction we catch all the new visible and |
|
183 # obsolete commit to inhibit them |
|
184 visibleobsolete = repo.revs('obsolete() - hidden()') |
|
185 ignoreset = set(getattr(repo, '_rebaseset', [])) |
|
186 visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset) |
|
187 if visibleobsolete: |
|
188 _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete]) |
|
189 transaction = orig(repo, desc, *args, **kwargs) |
|
190 if desc != 'strip' and _inhibitenabled(repo): |
|
191 transaction.addpostclose('inhibitposttransaction', inhibitposttransaction) |
|
192 return transaction |
|
193 |
|
194 def extsetup(ui): |
|
195 # lets wrap the computation of the obsolete set |
|
196 # We apply inhibition there |
|
197 obsfunc = obsolete.cachefuncs['obsolete'] |
|
198 def _computeobsoleteset(repo): |
|
199 """remove any inhibited nodes from the obsolete set |
|
200 |
|
201 This will trickle down to other part of mercurial (hidden, log, etc)""" |
|
202 obs = obsfunc(repo) |
|
203 if _inhibitenabled(repo): |
|
204 getrev = repo.changelog.nodemap.get |
|
205 for n in repo._obsinhibit: |
|
206 obs.discard(getrev(n)) |
|
207 return obs |
|
208 try: |
|
209 extensions.find('directaccess') |
|
210 except KeyError: |
|
211 errormsg = _('cannot use inhibit without the direct access extension\n') |
|
212 hint = _("(please enable it or inhibit won\'t work)\n") |
|
213 ui.warn(errormsg) |
|
214 ui.warn(hint) |
|
215 return |
|
216 |
|
217 # Wrapping this to inhibit obsolete revs resulting from a transaction |
|
218 extensions.wrapfunction(localrepo.localrepository, |
|
219 'transaction', transactioncallback) |
|
220 |
|
221 obsolete.cachefuncs['obsolete'] = _computeobsoleteset |
|
222 # wrap create marker to make it able to lift the inhibition |
|
223 extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers) |
|
224 # drop divergence computation since it is incompatible with "light revive" |
|
225 obsolete.cachefuncs['divergent'] = lambda repo: set() |
|
226 # drop bumped computation since it is incompatible with "light revive" |
|
227 obsolete.cachefuncs['bumped'] = lambda repo: set() |
|
228 # wrap update to make sure that no obsolete commit is visible after an |
|
229 # update |
|
230 extensions.wrapcommand(commands.table, 'update', _update) |
|
231 # There are two ways to save bookmark changes during a transation, we |
|
232 # wrap both to add inhibition markers. |
|
233 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged) |
|
234 extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged) |
|
235 # Add bookmark -D option |
|
236 entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark) |
|
237 entry[1].append(('D','prune',None, |
|
238 _('delete the bookmark and prune the commits underneath'))) |
|
239 |
|
240 @command('debugobsinhibit', [], '') |
|
241 def cmddebugobsinhibit(ui, repo, *revs): |
|
242 """inhibit obsolescence markers effect on a set of revs""" |
|
243 nodes = (repo[r].node() for r in scmutil.revrange(repo, revs)) |
|
244 _inhibitmarkers(repo, nodes) |
|