##// END OF EJS Templates
fastannotate: adapt to buffer() going a way in Python 3...
Augie Fackler -
r41300:dfc73c80 default
parent child Browse files
Show More
@@ -1,228 +1,228 b''
1 1 # Copyright 2016-present Facebook. All Rights Reserved.
2 2 #
3 3 # protocol: logic for a server providing fastannotate support
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10 import os
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 error,
15 15 extensions,
16 16 hg,
17 17 util,
18 18 wireprotov1peer,
19 19 wireprotov1server,
20 20 )
21 21 from . import context
22 22
23 23 # common
24 24
25 25 def _getmaster(ui):
26 26 """get the mainbranch, and enforce it is set"""
27 27 master = ui.config('fastannotate', 'mainbranch')
28 28 if not master:
29 29 raise error.Abort(_('fastannotate.mainbranch is required '
30 30 'for both the client and the server'))
31 31 return master
32 32
33 33 # server-side
34 34
35 35 def _capabilities(orig, repo, proto):
36 36 result = orig(repo, proto)
37 37 result.append('getannotate')
38 38 return result
39 39
40 40 def _getannotate(repo, proto, path, lastnode):
41 41 # output:
42 42 # FILE := vfspath + '\0' + str(size) + '\0' + content
43 43 # OUTPUT := '' | FILE + OUTPUT
44 44 result = ''
45 45 buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand',
46 46 True)
47 47 with context.annotatecontext(repo, path) as actx:
48 48 if buildondemand:
49 49 # update before responding to the client
50 50 master = _getmaster(repo.ui)
51 51 try:
52 52 if not actx.isuptodate(master):
53 53 actx.annotate(master, master)
54 54 except Exception:
55 55 # non-fast-forward move or corrupted. rebuild automically.
56 56 actx.rebuild()
57 57 try:
58 58 actx.annotate(master, master)
59 59 except Exception:
60 60 actx.rebuild() # delete files
61 61 finally:
62 62 # although the "with" context will also do a close/flush, we
63 63 # need to do it early so we can send the correct respond to
64 64 # client.
65 65 actx.close()
66 66 # send back the full content of revmap and linelog, in the future we
67 67 # may want to do some rsync-like fancy updating.
68 68 # the lastnode check is not necessary if the client and the server
69 69 # agree where the main branch is.
70 70 if actx.lastnode != lastnode:
71 71 for p in [actx.revmappath, actx.linelogpath]:
72 72 if not os.path.exists(p):
73 73 continue
74 74 content = ''
75 75 with open(p, 'rb') as f:
76 76 content = f.read()
77 77 vfsbaselen = len(repo.vfs.base + '/')
78 78 relpath = p[vfsbaselen:]
79 79 result += '%s\0%d\0%s' % (relpath, len(content), content)
80 80 return result
81 81
82 82 def _registerwireprotocommand():
83 83 if 'getannotate' in wireprotov1server.commands:
84 84 return
85 85 wireprotov1server.wireprotocommand(
86 86 'getannotate', 'path lastnode')(_getannotate)
87 87
88 88 def serveruisetup(ui):
89 89 _registerwireprotocommand()
90 90 extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
91 91
92 92 # client-side
93 93
94 94 def _parseresponse(payload):
95 95 result = {}
96 96 i = 0
97 97 l = len(payload) - 1
98 98 state = 0 # 0: vfspath, 1: size
99 99 vfspath = size = ''
100 100 while i < l:
101 101 ch = payload[i:i + 1]
102 102 if ch == '\0':
103 103 if state == 1:
104 result[vfspath] = buffer(payload, i + 1, int(size))
104 result[vfspath] = payload[i + 1:i + 1 + int(size)]
105 105 i += int(size)
106 106 state = 0
107 107 vfspath = size = ''
108 108 elif state == 0:
109 109 state = 1
110 110 else:
111 111 if state == 1:
112 112 size += ch
113 113 elif state == 0:
114 114 vfspath += ch
115 115 i += 1
116 116 return result
117 117
118 118 def peersetup(ui, peer):
119 119 class fastannotatepeer(peer.__class__):
120 120 @wireprotov1peer.batchable
121 121 def getannotate(self, path, lastnode=None):
122 122 if not self.capable('getannotate'):
123 123 ui.warn(_('remote peer cannot provide annotate cache\n'))
124 124 yield None, None
125 125 else:
126 126 args = {'path': path, 'lastnode': lastnode or ''}
127 127 f = wireprotov1peer.future()
128 128 yield args, f
129 129 yield _parseresponse(f.value)
130 130 peer.__class__ = fastannotatepeer
131 131
132 132 @contextlib.contextmanager
133 133 def annotatepeer(repo):
134 134 ui = repo.ui
135 135
136 136 remotepath = ui.expandpath(
137 137 ui.config('fastannotate', 'remotepath', 'default'))
138 138 peer = hg.peer(ui, {}, remotepath)
139 139
140 140 try:
141 141 yield peer
142 142 finally:
143 143 peer.close()
144 144
145 145 def clientfetch(repo, paths, lastnodemap=None, peer=None):
146 146 """download annotate cache from the server for paths"""
147 147 if not paths:
148 148 return
149 149
150 150 if peer is None:
151 151 with annotatepeer(repo) as peer:
152 152 return clientfetch(repo, paths, lastnodemap, peer)
153 153
154 154 if lastnodemap is None:
155 155 lastnodemap = {}
156 156
157 157 ui = repo.ui
158 158 results = []
159 159 with peer.commandexecutor() as batcher:
160 160 ui.debug('fastannotate: requesting %d files\n' % len(paths))
161 161 for p in paths:
162 162 results.append(batcher.callcommand(
163 163 'getannotate',
164 164 {'path': p, 'lastnode':lastnodemap.get(p)}))
165 165
166 166 for result in results:
167 167 r = result.result()
168 168 # TODO: pconvert these paths on the server?
169 169 r = {util.pconvert(p): v for p, v in r.iteritems()}
170 170 for path in sorted(r):
171 171 # ignore malicious paths
172 172 if (not path.startswith('fastannotate/')
173 173 or '/../' in (path + '/')):
174 174 ui.debug('fastannotate: ignored malicious path %s\n' % path)
175 175 continue
176 176 content = r[path]
177 177 if ui.debugflag:
178 178 ui.debug('fastannotate: writing %d bytes to %s\n'
179 179 % (len(content), path))
180 180 repo.vfs.makedirs(os.path.dirname(path))
181 181 with repo.vfs(path, 'wb') as f:
182 182 f.write(content)
183 183
184 184 def _filterfetchpaths(repo, paths):
185 185 """return a subset of paths whose history is long and need to fetch linelog
186 186 from the server. works with remotefilelog and non-remotefilelog repos.
187 187 """
188 188 threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
189 189 if threshold <= 0:
190 190 return paths
191 191
192 192 result = []
193 193 for path in paths:
194 194 try:
195 195 if len(repo.file(path)) >= threshold:
196 196 result.append(path)
197 197 except Exception: # file not found etc.
198 198 result.append(path)
199 199
200 200 return result
201 201
202 202 def localreposetup(ui, repo):
203 203 class fastannotaterepo(repo.__class__):
204 204 def prefetchfastannotate(self, paths, peer=None):
205 205 master = _getmaster(self.ui)
206 206 needupdatepaths = []
207 207 lastnodemap = {}
208 208 try:
209 209 for path in _filterfetchpaths(self, paths):
210 210 with context.annotatecontext(self, path) as actx:
211 211 if not actx.isuptodate(master, strict=False):
212 212 needupdatepaths.append(path)
213 213 lastnodemap[path] = actx.lastnode
214 214 if needupdatepaths:
215 215 clientfetch(self, needupdatepaths, lastnodemap, peer)
216 216 except Exception as ex:
217 217 # could be directory not writable or so, not fatal
218 218 self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
219 219 repo.__class__ = fastannotaterepo
220 220
221 221 def clientreposetup(ui, repo):
222 222 _registerwireprotocommand()
223 223 if repo.local():
224 224 localreposetup(ui, repo)
225 225 # TODO: this mutates global state, but only if at least one repo
226 226 # has the extension enabled. This is probably bad for hgweb.
227 227 if peersetup not in hg.wirepeersetupfuncs:
228 228 hg.wirepeersetupfuncs.append(peersetup)
General Comments 0
You need to be logged in to leave comments. Login now