|
1 # Copyright 2016-present Facebook. All Rights Reserved. |
|
2 # |
|
3 # support: fastannotate support for hgweb, and filectx |
|
4 # |
|
5 # This software may be used and distributed according to the terms of the |
|
6 # GNU General Public License version 2 or any later version. |
|
7 |
|
8 from __future__ import absolute_import |
|
9 |
|
10 from mercurial import ( |
|
11 context as hgcontext, |
|
12 dagop, |
|
13 extensions, |
|
14 hgweb, |
|
15 patch, |
|
16 util, |
|
17 ) |
|
18 |
|
19 from . import ( |
|
20 context, |
|
21 revmap, |
|
22 ) |
|
23 |
|
24 class _lazyfctx(object): |
|
25 """delegates to fctx but do not construct fctx when unnecessary""" |
|
26 |
|
27 def __init__(self, repo, node, path): |
|
28 self._node = node |
|
29 self._path = path |
|
30 self._repo = repo |
|
31 |
|
32 def node(self): |
|
33 return self._node |
|
34 |
|
35 def path(self): |
|
36 return self._path |
|
37 |
|
38 @util.propertycache |
|
39 def _fctx(self): |
|
40 return context.resolvefctx(self._repo, self._node, self._path) |
|
41 |
|
42 def __getattr__(self, name): |
|
43 return getattr(self._fctx, name) |
|
44 |
|
45 def _convertoutputs(repo, annotated, contents): |
|
46 """convert fastannotate outputs to vanilla annotate format""" |
|
47 # fastannotate returns: [(nodeid, linenum, path)], [linecontent] |
|
48 # convert to what fctx.annotate returns: [annotateline] |
|
49 results = [] |
|
50 fctxmap = {} |
|
51 annotateline = dagop.annotateline |
|
52 for i, (hsh, linenum, path) in enumerate(annotated): |
|
53 if (hsh, path) not in fctxmap: |
|
54 fctxmap[(hsh, path)] = _lazyfctx(repo, hsh, path) |
|
55 # linenum: the user wants 1-based, we have 0-based. |
|
56 lineno = linenum + 1 |
|
57 fctx = fctxmap[(hsh, path)] |
|
58 line = contents[i] |
|
59 results.append(annotateline(fctx=fctx, lineno=lineno, text=line)) |
|
60 return results |
|
61 |
|
62 def _getmaster(fctx): |
|
63 """(fctx) -> str""" |
|
64 return fctx._repo.ui.config('fastannotate', 'mainbranch') or 'default' |
|
65 |
|
66 def _doannotate(fctx, follow=True, diffopts=None): |
|
67 """like the vanilla fctx.annotate, but do it via fastannotate, and make |
|
68 the output format compatible with the vanilla fctx.annotate. |
|
69 may raise Exception, and always return line numbers. |
|
70 """ |
|
71 master = _getmaster(fctx) |
|
72 annotated = contents = None |
|
73 |
|
74 with context.fctxannotatecontext(fctx, follow, diffopts) as ac: |
|
75 try: |
|
76 annotated, contents = ac.annotate(fctx.rev(), master=master, |
|
77 showpath=True, showlines=True) |
|
78 except Exception: |
|
79 ac.rebuild() # try rebuild once |
|
80 fctx._repo.ui.debug('fastannotate: %s: rebuilding broken cache\n' |
|
81 % fctx._path) |
|
82 try: |
|
83 annotated, contents = ac.annotate(fctx.rev(), master=master, |
|
84 showpath=True, showlines=True) |
|
85 except Exception: |
|
86 raise |
|
87 |
|
88 assert annotated and contents |
|
89 return _convertoutputs(fctx._repo, annotated, contents) |
|
90 |
|
91 def _hgwebannotate(orig, fctx, ui): |
|
92 diffopts = patch.difffeatureopts(ui, untrusted=True, |
|
93 section='annotate', whitespace=True) |
|
94 return _doannotate(fctx, diffopts=diffopts) |
|
95 |
|
96 def _fctxannotate(orig, self, follow=False, linenumber=False, skiprevs=None, |
|
97 diffopts=None): |
|
98 if skiprevs: |
|
99 # skiprevs is not supported yet |
|
100 return orig(self, follow, linenumber, skiprevs=skiprevs, |
|
101 diffopts=diffopts) |
|
102 try: |
|
103 return _doannotate(self, follow, diffopts) |
|
104 except Exception as ex: |
|
105 self._repo.ui.debug('fastannotate: falling back to the vanilla ' |
|
106 'annotate: %r\n' % ex) |
|
107 return orig(self, follow=follow, skiprevs=skiprevs, |
|
108 diffopts=diffopts) |
|
109 |
|
110 def _remotefctxannotate(orig, self, follow=False, skiprevs=None, diffopts=None): |
|
111 # skipset: a set-like used to test if a fctx needs to be downloaded |
|
112 skipset = None |
|
113 with context.fctxannotatecontext(self, follow, diffopts) as ac: |
|
114 skipset = revmap.revmap(ac.revmappath) |
|
115 return orig(self, follow, skiprevs=skiprevs, diffopts=diffopts, |
|
116 prefetchskip=skipset) |
|
117 |
|
118 def replacehgwebannotate(): |
|
119 extensions.wrapfunction(hgweb.webutil, 'annotate', _hgwebannotate) |
|
120 |
|
121 def replacefctxannotate(): |
|
122 extensions.wrapfunction(hgcontext.basefilectx, 'annotate', _fctxannotate) |
|
123 |
|
124 def replaceremotefctxannotate(): |
|
125 try: |
|
126 r = extensions.find('remotefilelog') |
|
127 except KeyError: |
|
128 return |
|
129 else: |
|
130 extensions.wrapfunction(r.remotefilectx.remotefilectx, 'annotate', |
|
131 _remotefctxannotate) |