##// END OF EJS Templates
fastannotate: slice strings to get single character...
Augie Fackler -
r41298:e40b7a50 default
parent child Browse files
Show More
@@ -1,164 +1,164
1 # Copyright 2016-present Facebook. All Rights Reserved.
1 # Copyright 2016-present Facebook. All Rights Reserved.
2 #
2 #
3 # format: defines the format used to output annotate result
3 # format: defines the format used to output annotate result
4 #
4 #
5 # This software may be used and distributed according to the terms of the
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.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 from mercurial import (
9 from mercurial import (
10 encoding,
10 encoding,
11 node,
11 node,
12 pycompat,
12 pycompat,
13 templatefilters,
13 templatefilters,
14 util,
14 util,
15 )
15 )
16 from mercurial.utils import (
16 from mercurial.utils import (
17 dateutil,
17 dateutil,
18 )
18 )
19
19
20 # imitating mercurial.commands.annotate, not using the vanilla formatter since
20 # imitating mercurial.commands.annotate, not using the vanilla formatter since
21 # the data structures are a bit different, and we have some fast paths.
21 # the data structures are a bit different, and we have some fast paths.
22 class defaultformatter(object):
22 class defaultformatter(object):
23 """the default formatter that does leftpad and support some common flags"""
23 """the default formatter that does leftpad and support some common flags"""
24
24
25 def __init__(self, ui, repo, opts):
25 def __init__(self, ui, repo, opts):
26 self.ui = ui
26 self.ui = ui
27 self.opts = opts
27 self.opts = opts
28
28
29 if ui.quiet:
29 if ui.quiet:
30 datefunc = dateutil.shortdate
30 datefunc = dateutil.shortdate
31 else:
31 else:
32 datefunc = dateutil.datestr
32 datefunc = dateutil.datestr
33 datefunc = util.cachefunc(datefunc)
33 datefunc = util.cachefunc(datefunc)
34 getctx = util.cachefunc(lambda x: repo[x[0]])
34 getctx = util.cachefunc(lambda x: repo[x[0]])
35 hexfunc = self._hexfunc
35 hexfunc = self._hexfunc
36
36
37 # special handling working copy "changeset" and "rev" functions
37 # special handling working copy "changeset" and "rev" functions
38 if self.opts.get('rev') == 'wdir()':
38 if self.opts.get('rev') == 'wdir()':
39 orig = hexfunc
39 orig = hexfunc
40 hexfunc = lambda x: None if x is None else orig(x)
40 hexfunc = lambda x: None if x is None else orig(x)
41 wnode = hexfunc(repo[None].p1().node()) + '+'
41 wnode = hexfunc(repo[None].p1().node()) + '+'
42 wrev = '%d' % repo[None].p1().rev()
42 wrev = '%d' % repo[None].p1().rev()
43 wrevpad = ''
43 wrevpad = ''
44 if not opts.get('changeset'): # only show + if changeset is hidden
44 if not opts.get('changeset'): # only show + if changeset is hidden
45 wrev += '+'
45 wrev += '+'
46 wrevpad = ' '
46 wrevpad = ' '
47 revenc = lambda x: wrev if x is None else ('%d' % x) + wrevpad
47 revenc = lambda x: wrev if x is None else ('%d' % x) + wrevpad
48 def csetenc(x):
48 def csetenc(x):
49 if x is None:
49 if x is None:
50 return wnode
50 return wnode
51 return pycompat.bytestr(x) + ' '
51 return pycompat.bytestr(x) + ' '
52 else:
52 else:
53 revenc = csetenc = pycompat.bytestr
53 revenc = csetenc = pycompat.bytestr
54
54
55 # opt name, separator, raw value (for json/plain), encoder (for plain)
55 # opt name, separator, raw value (for json/plain), encoder (for plain)
56 opmap = [('user', ' ', lambda x: getctx(x).user(), ui.shortuser),
56 opmap = [('user', ' ', lambda x: getctx(x).user(), ui.shortuser),
57 ('number', ' ', lambda x: getctx(x).rev(), revenc),
57 ('number', ' ', lambda x: getctx(x).rev(), revenc),
58 ('changeset', ' ', lambda x: hexfunc(x[0]), csetenc),
58 ('changeset', ' ', lambda x: hexfunc(x[0]), csetenc),
59 ('date', ' ', lambda x: getctx(x).date(), datefunc),
59 ('date', ' ', lambda x: getctx(x).date(), datefunc),
60 ('file', ' ', lambda x: x[2], pycompat.bytestr),
60 ('file', ' ', lambda x: x[2], pycompat.bytestr),
61 ('line_number', ':', lambda x: x[1] + 1, pycompat.bytestr)]
61 ('line_number', ':', lambda x: x[1] + 1, pycompat.bytestr)]
62 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
62 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
63 funcmap = [(get, sep, fieldnamemap.get(op, op), enc)
63 funcmap = [(get, sep, fieldnamemap.get(op, op), enc)
64 for op, sep, get, enc in opmap
64 for op, sep, get, enc in opmap
65 if opts.get(op)]
65 if opts.get(op)]
66 # no separator for first column
66 # no separator for first column
67 funcmap[0] = list(funcmap[0])
67 funcmap[0] = list(funcmap[0])
68 funcmap[0][1] = ''
68 funcmap[0][1] = ''
69 self.funcmap = funcmap
69 self.funcmap = funcmap
70
70
71 def write(self, annotatedresult, lines=None, existinglines=None):
71 def write(self, annotatedresult, lines=None, existinglines=None):
72 """(annotateresult, [str], set([rev, linenum])) -> None. write output.
72 """(annotateresult, [str], set([rev, linenum])) -> None. write output.
73 annotateresult can be [(node, linenum, path)], or [(node, linenum)]
73 annotateresult can be [(node, linenum, path)], or [(node, linenum)]
74 """
74 """
75 pieces = [] # [[str]]
75 pieces = [] # [[str]]
76 maxwidths = [] # [int]
76 maxwidths = [] # [int]
77
77
78 # calculate padding
78 # calculate padding
79 for f, sep, name, enc in self.funcmap:
79 for f, sep, name, enc in self.funcmap:
80 l = [enc(f(x)) for x in annotatedresult]
80 l = [enc(f(x)) for x in annotatedresult]
81 pieces.append(l)
81 pieces.append(l)
82 if name in ['node', 'date']: # node and date has fixed size
82 if name in ['node', 'date']: # node and date has fixed size
83 l = l[:1]
83 l = l[:1]
84 widths = pycompat.maplist(encoding.colwidth, set(l))
84 widths = pycompat.maplist(encoding.colwidth, set(l))
85 maxwidth = (max(widths) if widths else 0)
85 maxwidth = (max(widths) if widths else 0)
86 maxwidths.append(maxwidth)
86 maxwidths.append(maxwidth)
87
87
88 # buffered output
88 # buffered output
89 result = ''
89 result = ''
90 for i in pycompat.xrange(len(annotatedresult)):
90 for i in pycompat.xrange(len(annotatedresult)):
91 for j, p in enumerate(pieces):
91 for j, p in enumerate(pieces):
92 sep = self.funcmap[j][1]
92 sep = self.funcmap[j][1]
93 padding = ' ' * (maxwidths[j] - len(p[i]))
93 padding = ' ' * (maxwidths[j] - len(p[i]))
94 result += sep + padding + p[i]
94 result += sep + padding + p[i]
95 if lines:
95 if lines:
96 if existinglines is None:
96 if existinglines is None:
97 result += ': ' + lines[i]
97 result += ': ' + lines[i]
98 else: # extra formatting showing whether a line exists
98 else: # extra formatting showing whether a line exists
99 key = (annotatedresult[i][0], annotatedresult[i][1])
99 key = (annotatedresult[i][0], annotatedresult[i][1])
100 if key in existinglines:
100 if key in existinglines:
101 result += ': ' + lines[i]
101 result += ': ' + lines[i]
102 else:
102 else:
103 result += ': ' + self.ui.label('-' + lines[i],
103 result += ': ' + self.ui.label('-' + lines[i],
104 'diff.deleted')
104 'diff.deleted')
105
105
106 if result[-1] != '\n':
106 if result[-1:] != '\n':
107 result += '\n'
107 result += '\n'
108
108
109 self.ui.write(result)
109 self.ui.write(result)
110
110
111 @util.propertycache
111 @util.propertycache
112 def _hexfunc(self):
112 def _hexfunc(self):
113 if self.ui.debugflag or self.opts.get('long_hash'):
113 if self.ui.debugflag or self.opts.get('long_hash'):
114 return node.hex
114 return node.hex
115 else:
115 else:
116 return node.short
116 return node.short
117
117
118 def end(self):
118 def end(self):
119 pass
119 pass
120
120
121 class jsonformatter(defaultformatter):
121 class jsonformatter(defaultformatter):
122 def __init__(self, ui, repo, opts):
122 def __init__(self, ui, repo, opts):
123 super(jsonformatter, self).__init__(ui, repo, opts)
123 super(jsonformatter, self).__init__(ui, repo, opts)
124 self.ui.write('[')
124 self.ui.write('[')
125 self.needcomma = False
125 self.needcomma = False
126
126
127 def write(self, annotatedresult, lines=None, existinglines=None):
127 def write(self, annotatedresult, lines=None, existinglines=None):
128 if annotatedresult:
128 if annotatedresult:
129 self._writecomma()
129 self._writecomma()
130
130
131 pieces = [(name, map(f, annotatedresult))
131 pieces = [(name, map(f, annotatedresult))
132 for f, sep, name, enc in self.funcmap]
132 for f, sep, name, enc in self.funcmap]
133 if lines is not None:
133 if lines is not None:
134 pieces.append(('line', lines))
134 pieces.append(('line', lines))
135 pieces.sort()
135 pieces.sort()
136
136
137 seps = [','] * len(pieces[:-1]) + ['']
137 seps = [','] * len(pieces[:-1]) + ['']
138
138
139 result = ''
139 result = ''
140 lasti = len(annotatedresult) - 1
140 lasti = len(annotatedresult) - 1
141 for i in pycompat.xrange(len(annotatedresult)):
141 for i in pycompat.xrange(len(annotatedresult)):
142 result += '\n {\n'
142 result += '\n {\n'
143 for j, p in enumerate(pieces):
143 for j, p in enumerate(pieces):
144 k, vs = p
144 k, vs = p
145 result += (' "%s": %s%s\n'
145 result += (' "%s": %s%s\n'
146 % (k, templatefilters.json(vs[i], paranoid=False),
146 % (k, templatefilters.json(vs[i], paranoid=False),
147 seps[j]))
147 seps[j]))
148 result += ' }%s' % ('' if i == lasti else ',')
148 result += ' }%s' % ('' if i == lasti else ',')
149 if lasti >= 0:
149 if lasti >= 0:
150 self.needcomma = True
150 self.needcomma = True
151
151
152 self.ui.write(result)
152 self.ui.write(result)
153
153
154 def _writecomma(self):
154 def _writecomma(self):
155 if self.needcomma:
155 if self.needcomma:
156 self.ui.write(',')
156 self.ui.write(',')
157 self.needcomma = False
157 self.needcomma = False
158
158
159 @util.propertycache
159 @util.propertycache
160 def _hexfunc(self):
160 def _hexfunc(self):
161 return node.hex
161 return node.hex
162
162
163 def end(self):
163 def end(self):
164 self.ui.write('\n]\n')
164 self.ui.write('\n]\n')
@@ -1,228 +1,228
1 # Copyright 2016-present Facebook. All Rights Reserved.
1 # Copyright 2016-present Facebook. All Rights Reserved.
2 #
2 #
3 # protocol: logic for a server providing fastannotate support
3 # protocol: logic for a server providing fastannotate support
4 #
4 #
5 # This software may be used and distributed according to the terms of the
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.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import os
10 import os
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 error,
14 error,
15 extensions,
15 extensions,
16 hg,
16 hg,
17 util,
17 util,
18 wireprotov1peer,
18 wireprotov1peer,
19 wireprotov1server,
19 wireprotov1server,
20 )
20 )
21 from . import context
21 from . import context
22
22
23 # common
23 # common
24
24
25 def _getmaster(ui):
25 def _getmaster(ui):
26 """get the mainbranch, and enforce it is set"""
26 """get the mainbranch, and enforce it is set"""
27 master = ui.config('fastannotate', 'mainbranch')
27 master = ui.config('fastannotate', 'mainbranch')
28 if not master:
28 if not master:
29 raise error.Abort(_('fastannotate.mainbranch is required '
29 raise error.Abort(_('fastannotate.mainbranch is required '
30 'for both the client and the server'))
30 'for both the client and the server'))
31 return master
31 return master
32
32
33 # server-side
33 # server-side
34
34
35 def _capabilities(orig, repo, proto):
35 def _capabilities(orig, repo, proto):
36 result = orig(repo, proto)
36 result = orig(repo, proto)
37 result.append('getannotate')
37 result.append('getannotate')
38 return result
38 return result
39
39
40 def _getannotate(repo, proto, path, lastnode):
40 def _getannotate(repo, proto, path, lastnode):
41 # output:
41 # output:
42 # FILE := vfspath + '\0' + str(size) + '\0' + content
42 # FILE := vfspath + '\0' + str(size) + '\0' + content
43 # OUTPUT := '' | FILE + OUTPUT
43 # OUTPUT := '' | FILE + OUTPUT
44 result = ''
44 result = ''
45 buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand',
45 buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand',
46 True)
46 True)
47 with context.annotatecontext(repo, path) as actx:
47 with context.annotatecontext(repo, path) as actx:
48 if buildondemand:
48 if buildondemand:
49 # update before responding to the client
49 # update before responding to the client
50 master = _getmaster(repo.ui)
50 master = _getmaster(repo.ui)
51 try:
51 try:
52 if not actx.isuptodate(master):
52 if not actx.isuptodate(master):
53 actx.annotate(master, master)
53 actx.annotate(master, master)
54 except Exception:
54 except Exception:
55 # non-fast-forward move or corrupted. rebuild automically.
55 # non-fast-forward move or corrupted. rebuild automically.
56 actx.rebuild()
56 actx.rebuild()
57 try:
57 try:
58 actx.annotate(master, master)
58 actx.annotate(master, master)
59 except Exception:
59 except Exception:
60 actx.rebuild() # delete files
60 actx.rebuild() # delete files
61 finally:
61 finally:
62 # although the "with" context will also do a close/flush, we
62 # although the "with" context will also do a close/flush, we
63 # need to do it early so we can send the correct respond to
63 # need to do it early so we can send the correct respond to
64 # client.
64 # client.
65 actx.close()
65 actx.close()
66 # send back the full content of revmap and linelog, in the future we
66 # send back the full content of revmap and linelog, in the future we
67 # may want to do some rsync-like fancy updating.
67 # may want to do some rsync-like fancy updating.
68 # the lastnode check is not necessary if the client and the server
68 # the lastnode check is not necessary if the client and the server
69 # agree where the main branch is.
69 # agree where the main branch is.
70 if actx.lastnode != lastnode:
70 if actx.lastnode != lastnode:
71 for p in [actx.revmappath, actx.linelogpath]:
71 for p in [actx.revmappath, actx.linelogpath]:
72 if not os.path.exists(p):
72 if not os.path.exists(p):
73 continue
73 continue
74 content = ''
74 content = ''
75 with open(p, 'rb') as f:
75 with open(p, 'rb') as f:
76 content = f.read()
76 content = f.read()
77 vfsbaselen = len(repo.vfs.base + '/')
77 vfsbaselen = len(repo.vfs.base + '/')
78 relpath = p[vfsbaselen:]
78 relpath = p[vfsbaselen:]
79 result += '%s\0%d\0%s' % (relpath, len(content), content)
79 result += '%s\0%d\0%s' % (relpath, len(content), content)
80 return result
80 return result
81
81
82 def _registerwireprotocommand():
82 def _registerwireprotocommand():
83 if 'getannotate' in wireprotov1server.commands:
83 if 'getannotate' in wireprotov1server.commands:
84 return
84 return
85 wireprotov1server.wireprotocommand(
85 wireprotov1server.wireprotocommand(
86 'getannotate', 'path lastnode')(_getannotate)
86 'getannotate', 'path lastnode')(_getannotate)
87
87
88 def serveruisetup(ui):
88 def serveruisetup(ui):
89 _registerwireprotocommand()
89 _registerwireprotocommand()
90 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
90 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
91
91
92 # client-side
92 # client-side
93
93
94 def _parseresponse(payload):
94 def _parseresponse(payload):
95 result = {}
95 result = {}
96 i = 0
96 i = 0
97 l = len(payload) - 1
97 l = len(payload) - 1
98 state = 0 # 0: vfspath, 1: size
98 state = 0 # 0: vfspath, 1: size
99 vfspath = size = ''
99 vfspath = size = ''
100 while i < l:
100 while i < l:
101 ch = payload[i]
101 ch = payload[i:i + 1]
102 if ch == '\0':
102 if ch == '\0':
103 if state == 1:
103 if state == 1:
104 result[vfspath] = buffer(payload, i + 1, int(size))
104 result[vfspath] = buffer(payload, i + 1, int(size))
105 i += int(size)
105 i += int(size)
106 state = 0
106 state = 0
107 vfspath = size = ''
107 vfspath = size = ''
108 elif state == 0:
108 elif state == 0:
109 state = 1
109 state = 1
110 else:
110 else:
111 if state == 1:
111 if state == 1:
112 size += ch
112 size += ch
113 elif state == 0:
113 elif state == 0:
114 vfspath += ch
114 vfspath += ch
115 i += 1
115 i += 1
116 return result
116 return result
117
117
118 def peersetup(ui, peer):
118 def peersetup(ui, peer):
119 class fastannotatepeer(peer.__class__):
119 class fastannotatepeer(peer.__class__):
120 @wireprotov1peer.batchable
120 @wireprotov1peer.batchable
121 def getannotate(self, path, lastnode=None):
121 def getannotate(self, path, lastnode=None):
122 if not self.capable('getannotate'):
122 if not self.capable('getannotate'):
123 ui.warn(_('remote peer cannot provide annotate cache\n'))
123 ui.warn(_('remote peer cannot provide annotate cache\n'))
124 yield None, None
124 yield None, None
125 else:
125 else:
126 args = {'path': path, 'lastnode': lastnode or ''}
126 args = {'path': path, 'lastnode': lastnode or ''}
127 f = wireprotov1peer.future()
127 f = wireprotov1peer.future()
128 yield args, f
128 yield args, f
129 yield _parseresponse(f.value)
129 yield _parseresponse(f.value)
130 peer.__class__ = fastannotatepeer
130 peer.__class__ = fastannotatepeer
131
131
132 @contextlib.contextmanager
132 @contextlib.contextmanager
133 def annotatepeer(repo):
133 def annotatepeer(repo):
134 ui = repo.ui
134 ui = repo.ui
135
135
136 remotepath = ui.expandpath(
136 remotepath = ui.expandpath(
137 ui.config('fastannotate', 'remotepath', 'default'))
137 ui.config('fastannotate', 'remotepath', 'default'))
138 peer = hg.peer(ui, {}, remotepath)
138 peer = hg.peer(ui, {}, remotepath)
139
139
140 try:
140 try:
141 yield peer
141 yield peer
142 finally:
142 finally:
143 peer.close()
143 peer.close()
144
144
145 def clientfetch(repo, paths, lastnodemap=None, peer=None):
145 def clientfetch(repo, paths, lastnodemap=None, peer=None):
146 """download annotate cache from the server for paths"""
146 """download annotate cache from the server for paths"""
147 if not paths:
147 if not paths:
148 return
148 return
149
149
150 if peer is None:
150 if peer is None:
151 with annotatepeer(repo) as peer:
151 with annotatepeer(repo) as peer:
152 return clientfetch(repo, paths, lastnodemap, peer)
152 return clientfetch(repo, paths, lastnodemap, peer)
153
153
154 if lastnodemap is None:
154 if lastnodemap is None:
155 lastnodemap = {}
155 lastnodemap = {}
156
156
157 ui = repo.ui
157 ui = repo.ui
158 results = []
158 results = []
159 with peer.commandexecutor() as batcher:
159 with peer.commandexecutor() as batcher:
160 ui.debug('fastannotate: requesting %d files\n' % len(paths))
160 ui.debug('fastannotate: requesting %d files\n' % len(paths))
161 for p in paths:
161 for p in paths:
162 results.append(batcher.callcommand(
162 results.append(batcher.callcommand(
163 'getannotate',
163 'getannotate',
164 {'path': p, 'lastnode':lastnodemap.get(p)}))
164 {'path': p, 'lastnode':lastnodemap.get(p)}))
165
165
166 for result in results:
166 for result in results:
167 r = result.result()
167 r = result.result()
168 # TODO: pconvert these paths on the server?
168 # TODO: pconvert these paths on the server?
169 r = {util.pconvert(p): v for p, v in r.iteritems()}
169 r = {util.pconvert(p): v for p, v in r.iteritems()}
170 for path in sorted(r):
170 for path in sorted(r):
171 # ignore malicious paths
171 # ignore malicious paths
172 if (not path.startswith('fastannotate/')
172 if (not path.startswith('fastannotate/')
173 or '/../' in (path + '/')):
173 or '/../' in (path + '/')):
174 ui.debug('fastannotate: ignored malicious path %s\n' % path)
174 ui.debug('fastannotate: ignored malicious path %s\n' % path)
175 continue
175 continue
176 content = r[path]
176 content = r[path]
177 if ui.debugflag:
177 if ui.debugflag:
178 ui.debug('fastannotate: writing %d bytes to %s\n'
178 ui.debug('fastannotate: writing %d bytes to %s\n'
179 % (len(content), path))
179 % (len(content), path))
180 repo.vfs.makedirs(os.path.dirname(path))
180 repo.vfs.makedirs(os.path.dirname(path))
181 with repo.vfs(path, 'wb') as f:
181 with repo.vfs(path, 'wb') as f:
182 f.write(content)
182 f.write(content)
183
183
184 def _filterfetchpaths(repo, paths):
184 def _filterfetchpaths(repo, paths):
185 """return a subset of paths whose history is long and need to fetch linelog
185 """return a subset of paths whose history is long and need to fetch linelog
186 from the server. works with remotefilelog and non-remotefilelog repos.
186 from the server. works with remotefilelog and non-remotefilelog repos.
187 """
187 """
188 threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
188 threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
189 if threshold <= 0:
189 if threshold <= 0:
190 return paths
190 return paths
191
191
192 result = []
192 result = []
193 for path in paths:
193 for path in paths:
194 try:
194 try:
195 if len(repo.file(path)) >= threshold:
195 if len(repo.file(path)) >= threshold:
196 result.append(path)
196 result.append(path)
197 except Exception: # file not found etc.
197 except Exception: # file not found etc.
198 result.append(path)
198 result.append(path)
199
199
200 return result
200 return result
201
201
202 def localreposetup(ui, repo):
202 def localreposetup(ui, repo):
203 class fastannotaterepo(repo.__class__):
203 class fastannotaterepo(repo.__class__):
204 def prefetchfastannotate(self, paths, peer=None):
204 def prefetchfastannotate(self, paths, peer=None):
205 master = _getmaster(self.ui)
205 master = _getmaster(self.ui)
206 needupdatepaths = []
206 needupdatepaths = []
207 lastnodemap = {}
207 lastnodemap = {}
208 try:
208 try:
209 for path in _filterfetchpaths(self, paths):
209 for path in _filterfetchpaths(self, paths):
210 with context.annotatecontext(self, path) as actx:
210 with context.annotatecontext(self, path) as actx:
211 if not actx.isuptodate(master, strict=False):
211 if not actx.isuptodate(master, strict=False):
212 needupdatepaths.append(path)
212 needupdatepaths.append(path)
213 lastnodemap[path] = actx.lastnode
213 lastnodemap[path] = actx.lastnode
214 if needupdatepaths:
214 if needupdatepaths:
215 clientfetch(self, needupdatepaths, lastnodemap, peer)
215 clientfetch(self, needupdatepaths, lastnodemap, peer)
216 except Exception as ex:
216 except Exception as ex:
217 # could be directory not writable or so, not fatal
217 # could be directory not writable or so, not fatal
218 self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
218 self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
219 repo.__class__ = fastannotaterepo
219 repo.__class__ = fastannotaterepo
220
220
221 def clientreposetup(ui, repo):
221 def clientreposetup(ui, repo):
222 _registerwireprotocommand()
222 _registerwireprotocommand()
223 if repo.local():
223 if repo.local():
224 localreposetup(ui, repo)
224 localreposetup(ui, repo)
225 # TODO: this mutates global state, but only if at least one repo
225 # TODO: this mutates global state, but only if at least one repo
226 # has the extension enabled. This is probably bad for hgweb.
226 # has the extension enabled. This is probably bad for hgweb.
227 if peersetup not in hg.wirepeersetupfuncs:
227 if peersetup not in hg.wirepeersetupfuncs:
228 hg.wirepeersetupfuncs.append(peersetup)
228 hg.wirepeersetupfuncs.append(peersetup)
General Comments 0
You need to be logged in to leave comments. Login now