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'))