Mercurial > evolve
comparison hgext3rd/topic/__init__.py @ 1901:85390446f8c1
packaging: fix setup.py and install as hgext3rd.topic
This changeset is doing two things (gasp):
- It fixes various errors in the setup.py
- It move the topic source and install into hgext3rd.topic.
This last part (code source move) use hgext3rd as namespace package to prevent
installation nightmare. This won't be officially supported until Mercurial 3.8,
but in the meantime, 3.7 user can enable it using the full package name:
[extensions]
hgext3rd.topic=
Thanks goes to Julien Cristau <julien.cristau@logilab.fr> for the initial
version of this.
author | Pierre-Yves David <pierre-yves.david@fb.com> |
---|---|
date | Thu, 17 Mar 2016 09:12:18 -0700 |
parents | src/topic/__init__.py@c8e4c6e03957 |
children | 58cdf061d49a |
comparison
equal
deleted
inserted
replaced
1899:b65f39791f92 | 1901:85390446f8c1 |
---|---|
1 # __init__.py - topic extension | |
2 # | |
3 # This software may be used and distributed according to the terms of the | |
4 # GNU General Public License version 2 or any later version. | |
5 """support for topic branches | |
6 | |
7 Topic branches are lightweight branches which | |
8 disappear when changes are finalized. | |
9 | |
10 This is sort of similar to a bookmark, but it applies to a whole | |
11 series instead of a single revision. | |
12 """ | |
13 import functools | |
14 import contextlib | |
15 | |
16 from mercurial.i18n import _ | |
17 from mercurial import branchmap | |
18 from mercurial import bundle2 | |
19 from mercurial import changegroup | |
20 from mercurial import cmdutil | |
21 from mercurial import commands | |
22 from mercurial import context | |
23 from mercurial import discovery as discoverymod | |
24 from mercurial import error | |
25 from mercurial import exchange | |
26 from mercurial import extensions | |
27 from mercurial import localrepo | |
28 from mercurial import lock | |
29 from mercurial import merge | |
30 from mercurial import namespaces | |
31 from mercurial import node | |
32 from mercurial import obsolete | |
33 from mercurial import patch | |
34 from mercurial import phases | |
35 from mercurial import util | |
36 from mercurial import wireproto | |
37 | |
38 from . import constants | |
39 from . import revset as topicrevset | |
40 from . import destination | |
41 from . import stack | |
42 from . import topicmap | |
43 from . import discovery | |
44 | |
45 cmdtable = {} | |
46 command = cmdutil.command(cmdtable) | |
47 | |
48 testedwith = '3.7' | |
49 | |
50 def _contexttopic(self): | |
51 return self.extra().get(constants.extrakey, '') | |
52 context.basectx.topic = _contexttopic | |
53 | |
54 def _namemap(repo, name): | |
55 return [ctx.node() for ctx in | |
56 repo.set('not public() and extra(topic, %s)', name)] | |
57 | |
58 def _nodemap(repo, node): | |
59 ctx = repo[node] | |
60 t = ctx.topic() | |
61 if t and ctx.phase() > phases.public: | |
62 return [t] | |
63 return [] | |
64 | |
65 def uisetup(ui): | |
66 destination.setupdest() | |
67 | |
68 @contextlib.contextmanager | |
69 def usetopicmap(repo): | |
70 """use awful monkey patching to update the topic cache""" | |
71 oldbranchcache = branchmap.branchcache | |
72 oldfilename = branchmap._filename | |
73 oldread = branchmap.read | |
74 oldcaches = getattr(repo, '_branchcaches', {}) | |
75 try: | |
76 branchmap.branchcache = topicmap.topiccache | |
77 branchmap._filename = topicmap._filename | |
78 branchmap.read = topicmap.readtopicmap | |
79 repo._branchcaches = getattr(repo, '_topiccaches', {}) | |
80 yield | |
81 repo._topiccaches = repo._branchcaches | |
82 finally: | |
83 repo._branchcaches = oldcaches | |
84 branchmap.branchcache = oldbranchcache | |
85 branchmap._filename = oldfilename | |
86 branchmap.read = oldread | |
87 | |
88 def cgapply(orig, repo, *args, **kwargs): | |
89 with usetopicmap(repo): | |
90 return orig(repo, *args, **kwargs) | |
91 | |
92 def reposetup(ui, repo): | |
93 orig = repo.__class__ | |
94 if not isinstance(repo, localrepo.localrepository): | |
95 return # this can be a peer in the ssh case (puzzling) | |
96 class topicrepo(repo.__class__): | |
97 def commit(self, *args, **kwargs): | |
98 backup = self.ui.backupconfig('ui', 'allowemptycommit') | |
99 try: | |
100 if repo.currenttopic != repo['.'].topic(): | |
101 # bypass the core "nothing changed" logic | |
102 self.ui.setconfig('ui', 'allowemptycommit', True) | |
103 return orig.commit(self, *args, **kwargs) | |
104 finally: | |
105 self.ui.restoreconfig(backup) | |
106 | |
107 def commitctx(self, ctx, error=None): | |
108 if isinstance(ctx, context.workingcommitctx): | |
109 current = self.currenttopic | |
110 if current: | |
111 ctx.extra()[constants.extrakey] = current | |
112 if (isinstance(ctx, context.memctx) and | |
113 ctx.extra().get('amend_source') and | |
114 ctx.topic() and | |
115 not self.currenttopic): | |
116 # we are amending and need to remove a topic | |
117 del ctx.extra()[constants.extrakey] | |
118 with usetopicmap(self): | |
119 return orig.commitctx(self, ctx, error=error) | |
120 | |
121 @property | |
122 def topics(self): | |
123 topics = set(['', self.currenttopic]) | |
124 for c in self.set('not public()'): | |
125 topics.add(c.topic()) | |
126 topics.remove('') | |
127 return topics | |
128 | |
129 @property | |
130 def currenttopic(self): | |
131 return self.vfs.tryread('topic') | |
132 | |
133 def branchmap(self, topic=True): | |
134 if not topic: | |
135 super(topicrepo, self).branchmap() | |
136 with usetopicmap(self): | |
137 branchmap.updatecache(self) | |
138 return self._topiccaches[self.filtername] | |
139 | |
140 def destroyed(self, *args, **kwargs): | |
141 with usetopicmap(self): | |
142 return super(topicrepo, self).destroyed(*args, **kwargs) | |
143 | |
144 def invalidatecaches(self): | |
145 super(topicrepo, self).invalidatecaches() | |
146 if '_topiccaches' in vars(self.unfiltered()): | |
147 self.unfiltered()._topiccaches.clear() | |
148 | |
149 def peer(self): | |
150 peer = super(topicrepo, self).peer() | |
151 if getattr(peer, '_repo', None) is not None: # localpeer | |
152 class topicpeer(peer.__class__): | |
153 def branchmap(self): | |
154 usetopic = not self._repo.publishing() | |
155 return self._repo.branchmap(topic=usetopic) | |
156 peer.__class__ = topicpeer | |
157 return peer | |
158 | |
159 | |
160 repo.__class__ = topicrepo | |
161 if util.safehasattr(repo, 'names'): | |
162 repo.names.addnamespace(namespaces.namespace( | |
163 'topics', 'topic', namemap=_namemap, nodemap=_nodemap, | |
164 listnames=lambda repo: repo.topics)) | |
165 | |
166 @command('topics [TOPIC]', [ | |
167 ('', 'clear', False, 'clear active topic if any'), | |
168 ('', 'change', '', 'revset of existing revisions to change topic'), | |
169 ('l', 'list', False, 'show the stack of changeset in the topic'), | |
170 ]) | |
171 def topics(ui, repo, topic='', clear=False, change=None, list=False): | |
172 """View current topic, set current topic, or see all topics.""" | |
173 if list: | |
174 if clear or change: | |
175 raise error.Abort(_("cannot use --clear or --change with --list")) | |
176 return stack.showstack(ui, repo, topic) | |
177 | |
178 if change: | |
179 if not obsolete.isenabled(repo, obsolete.createmarkersopt): | |
180 raise error.Abort(_('must have obsolete enabled to use --change')) | |
181 if not topic and not clear: | |
182 raise error.Abort('changing topic requires a topic name or --clear') | |
183 if any(not c.mutable() for c in repo.set('%r and public()', change)): | |
184 raise error.Abort("can't change topic of a public change") | |
185 rewrote = 0 | |
186 needevolve = False | |
187 l = repo.lock() | |
188 txn = repo.transaction('rewrite-topics') | |
189 try: | |
190 for c in repo.set('%r', change): | |
191 def filectxfn(repo, ctx, path): | |
192 try: | |
193 return c[path] | |
194 except error.ManifestLookupError: | |
195 return None | |
196 fixedextra = dict(c.extra()) | |
197 ui.debug('old node id is %s\n' % node.hex(c.node())) | |
198 ui.debug('origextra: %r\n' % fixedextra) | |
199 newtopic = None if clear else topic | |
200 oldtopic = fixedextra.get(constants.extrakey, None) | |
201 if oldtopic == newtopic: | |
202 continue | |
203 if clear: | |
204 del fixedextra[constants.extrakey] | |
205 else: | |
206 fixedextra[constants.extrakey] = topic | |
207 if 'amend_source' in fixedextra: | |
208 # TODO: right now the commitctx wrapper in | |
209 # topicrepo overwrites the topic in extra if | |
210 # amend_source is set to support 'hg commit | |
211 # --amend'. Support for amend should be adjusted | |
212 # to not be so invasive. | |
213 del fixedextra['amend_source'] | |
214 ui.debug('changing topic of %s from %s to %s\n' % ( | |
215 c, oldtopic, newtopic)) | |
216 ui.debug('fixedextra: %r\n' % fixedextra) | |
217 mc = context.memctx( | |
218 repo, (c.p1().node(), c.p2().node()), c.description(), | |
219 c.files(), filectxfn, | |
220 user=c.user(), date=c.date(), extra=fixedextra) | |
221 newnode = repo.commitctx(mc) | |
222 ui.debug('new node id is %s\n' % node.hex(newnode)) | |
223 needevolve = needevolve or (len(c.children()) > 0) | |
224 obsolete.createmarkers(repo, [(c, (repo[newnode],))]) | |
225 rewrote += 1 | |
226 txn.close() | |
227 except: | |
228 try: | |
229 txn.abort() | |
230 finally: | |
231 repo.invalidate() | |
232 raise | |
233 finally: | |
234 lock.release(txn, l) | |
235 ui.status('changed topic on %d changes\n' % rewrote) | |
236 if needevolve: | |
237 evolvetarget = 'topic(%s)' % topic if topic else 'not topic()' | |
238 ui.status('please run hg evolve --rev "%s" now\n' % evolvetarget) | |
239 if clear: | |
240 if repo.vfs.exists('topic'): | |
241 repo.vfs.unlink('topic') | |
242 return | |
243 if topic: | |
244 with repo.vfs.open('topic', 'w') as f: | |
245 f.write(topic) | |
246 return | |
247 current = repo.currenttopic | |
248 for t in sorted(repo.topics): | |
249 marker = '*' if t == current else ' ' | |
250 ui.write(' %s %s\n' % (marker, t)) | |
251 | |
252 def summaryhook(ui, repo): | |
253 t = repo.currenttopic | |
254 if not t: | |
255 return | |
256 # i18n: column positioning for "hg summary" | |
257 ui.write(_("topic: %s\n") % t) | |
258 | |
259 def commitwrap(orig, ui, repo, *args, **opts): | |
260 if opts.get('topic'): | |
261 t = opts['topic'] | |
262 with repo.vfs.open('topic', 'w') as f: | |
263 f.write(t) | |
264 return orig(ui, repo, *args, **opts) | |
265 | |
266 def committextwrap(orig, repo, ctx, subs, extramsg): | |
267 ret = orig(repo, ctx, subs, extramsg) | |
268 t = repo.currenttopic | |
269 if t: | |
270 ret = ret.replace("\nHG: branch", | |
271 "\nHG: topic '%s'\nHG: branch" % t) | |
272 return ret | |
273 | |
274 def mergeupdatewrap(orig, repo, node, branchmerge, force, *args, **kwargs): | |
275 partial = bool(len(args)) or 'matcher' in kwargs | |
276 wlock = repo.wlock() | |
277 try: | |
278 ret = orig(repo, node, branchmerge, force, *args, **kwargs) | |
279 if not partial and not branchmerge: | |
280 ot = repo.currenttopic | |
281 t = '' | |
282 pctx = repo[node] | |
283 if pctx.phase() > phases.public: | |
284 t = pctx.topic() | |
285 with repo.vfs.open('topic', 'w') as f: | |
286 f.write(t) | |
287 if t and t != ot: | |
288 repo.ui.status(_("switching to topic %s\n") % t) | |
289 return ret | |
290 finally: | |
291 wlock.release() | |
292 | |
293 def _fixrebase(loaded): | |
294 if not loaded: | |
295 return | |
296 | |
297 def savetopic(ctx, extra): | |
298 if ctx.topic(): | |
299 extra[constants.extrakey] = ctx.topic() | |
300 | |
301 def newmakeextrafn(orig, copiers): | |
302 return orig(copiers + [savetopic]) | |
303 | |
304 rebase = extensions.find("rebase") | |
305 extensions.wrapfunction(rebase, '_makeextrafn', newmakeextrafn) | |
306 | |
307 def _exporttopic(seq, ctx): | |
308 topic = ctx.topic() | |
309 if topic: | |
310 return 'EXP-Topic %s' % topic | |
311 return None | |
312 | |
313 def _importtopic(repo, patchdata, extra, opts): | |
314 if 'topic' in patchdata: | |
315 extra['topic'] = patchdata['topic'] | |
316 | |
317 extensions.afterloaded('rebase', _fixrebase) | |
318 | |
319 entry = extensions.wrapcommand(commands.table, 'commit', commitwrap) | |
320 entry[1].append(('t', 'topic', '', | |
321 _("use specified topic"), _('TOPIC'))) | |
322 | |
323 extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap) | |
324 extensions.wrapfunction(merge, 'update', mergeupdatewrap) | |
325 extensions.wrapfunction(discoverymod, '_headssummary', discovery._headssummary) | |
326 extensions.wrapfunction(wireproto, 'branchmap', discovery.wireprotobranchmap) | |
327 extensions.wrapfunction(bundle2, 'handlecheckheads', discovery.handlecheckheads) | |
328 bundle2.handlecheckheads.params = frozenset() # we need a proper wrape b2 part stuff | |
329 bundle2.parthandlermapping['check:heads'] = bundle2.handlecheckheads | |
330 extensions.wrapfunction(exchange, '_pushb2phases', discovery._pushb2phases) | |
331 extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply) | |
332 exchange.b2partsgenmapping['phase'] = exchange._pushb2phases | |
333 topicrevset.modsetup() | |
334 cmdutil.summaryhooks.add('topic', summaryhook) | |
335 | |
336 if util.safehasattr(cmdutil, 'extraexport'): | |
337 cmdutil.extraexport.append('topic') | |
338 cmdutil.extraexportmap['topic'] = _exporttopic | |
339 if util.safehasattr(cmdutil, 'extrapreimport'): | |
340 cmdutil.extrapreimport.append('topic') | |
341 cmdutil.extrapreimportmap['topic'] = _importtopic | |
342 if util.safehasattr(patch, 'patchheadermap'): | |
343 patch.patchheadermap.append(('EXP-Topic', 'topic')) |