##// END OF EJS Templates
remotefilelog: implement __bool__ as well as __nonzero__ for py3...
Augie Fackler -
r41287:beb0d944 default
parent child Browse files
Show More
@@ -1,452 +1,454 b''
1 1 # remotefilelog.py - filelog implementation where filelog history is stored
2 2 # remotely
3 3 #
4 4 # Copyright 2013 Facebook, Inc.
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import os
12 12
13 13 from mercurial.node import bin, nullid
14 14 from mercurial.i18n import _
15 15 from mercurial import (
16 16 ancestor,
17 17 error,
18 18 mdiff,
19 19 revlog,
20 20 )
21 21 from mercurial.utils import storageutil
22 22
23 23 from . import (
24 24 constants,
25 25 fileserverclient,
26 26 shallowutil,
27 27 )
28 28
29 29 class remotefilelognodemap(object):
30 30 def __init__(self, filename, store):
31 31 self._filename = filename
32 32 self._store = store
33 33
34 34 def __contains__(self, node):
35 35 missing = self._store.getmissing([(self._filename, node)])
36 36 return not bool(missing)
37 37
38 38 def __get__(self, node):
39 39 if node not in self:
40 40 raise KeyError(node)
41 41 return node
42 42
43 43 class remotefilelog(object):
44 44
45 45 _generaldelta = True
46 46
47 47 def __init__(self, opener, path, repo):
48 48 self.opener = opener
49 49 self.filename = path
50 50 self.repo = repo
51 51 self.nodemap = remotefilelognodemap(self.filename, repo.contentstore)
52 52
53 53 self.version = 1
54 54
55 55 def read(self, node):
56 56 """returns the file contents at this node"""
57 57 t = self.revision(node)
58 58 if not t.startswith('\1\n'):
59 59 return t
60 60 s = t.index('\1\n', 2)
61 61 return t[s + 2:]
62 62
63 63 def add(self, text, meta, transaction, linknode, p1=None, p2=None):
64 64 hashtext = text
65 65
66 66 # hash with the metadata, like in vanilla filelogs
67 67 hashtext = shallowutil.createrevlogtext(text, meta.get('copy'),
68 68 meta.get('copyrev'))
69 69 node = storageutil.hashrevisionsha1(hashtext, p1, p2)
70 70 return self.addrevision(hashtext, transaction, linknode, p1, p2,
71 71 node=node)
72 72
73 73 def _createfileblob(self, text, meta, flags, p1, p2, node, linknode):
74 74 # text passed to "_createfileblob" does not include filelog metadata
75 75 header = shallowutil.buildfileblobheader(len(text), flags)
76 76 data = "%s\0%s" % (header, text)
77 77
78 78 realp1 = p1
79 79 copyfrom = ""
80 80 if meta and 'copy' in meta:
81 81 copyfrom = meta['copy']
82 82 realp1 = bin(meta['copyrev'])
83 83
84 84 data += "%s%s%s%s%s\0" % (node, realp1, p2, linknode, copyfrom)
85 85
86 86 visited = set()
87 87
88 88 pancestors = {}
89 89 queue = []
90 90 if realp1 != nullid:
91 91 p1flog = self
92 92 if copyfrom:
93 93 p1flog = remotefilelog(self.opener, copyfrom, self.repo)
94 94
95 95 pancestors.update(p1flog.ancestormap(realp1))
96 96 queue.append(realp1)
97 97 visited.add(realp1)
98 98 if p2 != nullid:
99 99 pancestors.update(self.ancestormap(p2))
100 100 queue.append(p2)
101 101 visited.add(p2)
102 102
103 103 ancestortext = ""
104 104
105 105 # add the ancestors in topological order
106 106 while queue:
107 107 c = queue.pop(0)
108 108 pa1, pa2, ancestorlinknode, pacopyfrom = pancestors[c]
109 109
110 110 pacopyfrom = pacopyfrom or ''
111 111 ancestortext += "%s%s%s%s%s\0" % (
112 112 c, pa1, pa2, ancestorlinknode, pacopyfrom)
113 113
114 114 if pa1 != nullid and pa1 not in visited:
115 115 queue.append(pa1)
116 116 visited.add(pa1)
117 117 if pa2 != nullid and pa2 not in visited:
118 118 queue.append(pa2)
119 119 visited.add(pa2)
120 120
121 121 data += ancestortext
122 122
123 123 return data
124 124
125 125 def addrevision(self, text, transaction, linknode, p1, p2, cachedelta=None,
126 126 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS):
127 127 # text passed to "addrevision" includes hg filelog metadata header
128 128 if node is None:
129 129 node = storageutil.hashrevisionsha1(text, p1, p2)
130 130
131 131 meta, metaoffset = storageutil.parsemeta(text)
132 132 rawtext, validatehash = self._processflags(text, flags, 'write')
133 133 return self.addrawrevision(rawtext, transaction, linknode, p1, p2,
134 134 node, flags, cachedelta,
135 135 _metatuple=(meta, metaoffset))
136 136
137 137 def addrawrevision(self, rawtext, transaction, linknode, p1, p2, node,
138 138 flags, cachedelta=None, _metatuple=None):
139 139 if _metatuple:
140 140 # _metatuple: used by "addrevision" internally by remotefilelog
141 141 # meta was parsed confidently
142 142 meta, metaoffset = _metatuple
143 143 else:
144 144 # not from self.addrevision, but something else (repo._filecommit)
145 145 # calls addrawrevision directly. remotefilelog needs to get and
146 146 # strip filelog metadata.
147 147 # we don't have confidence about whether rawtext contains filelog
148 148 # metadata or not (flag processor could replace it), so we just
149 149 # parse it as best-effort.
150 150 # in LFS (flags != 0)'s case, the best way is to call LFS code to
151 151 # get the meta information, instead of storageutil.parsemeta.
152 152 meta, metaoffset = storageutil.parsemeta(rawtext)
153 153 if flags != 0:
154 154 # when flags != 0, be conservative and do not mangle rawtext, since
155 155 # a read flag processor expects the text not being mangled at all.
156 156 metaoffset = 0
157 157 if metaoffset:
158 158 # remotefilelog fileblob stores copy metadata in its ancestortext,
159 159 # not its main blob. so we need to remove filelog metadata
160 160 # (containing copy information) from text.
161 161 blobtext = rawtext[metaoffset:]
162 162 else:
163 163 blobtext = rawtext
164 164 data = self._createfileblob(blobtext, meta, flags, p1, p2, node,
165 165 linknode)
166 166 self.repo.contentstore.addremotefilelognode(self.filename, node, data)
167 167
168 168 return node
169 169
170 170 def renamed(self, node):
171 171 ancestors = self.repo.metadatastore.getancestors(self.filename, node)
172 172 p1, p2, linknode, copyfrom = ancestors[node]
173 173 if copyfrom:
174 174 return (copyfrom, p1)
175 175
176 176 return False
177 177
178 178 def size(self, node):
179 179 """return the size of a given revision"""
180 180 return len(self.read(node))
181 181
182 182 rawsize = size
183 183
184 184 def cmp(self, node, text):
185 185 """compare text with a given file revision
186 186
187 187 returns True if text is different than what is stored.
188 188 """
189 189
190 190 if node == nullid:
191 191 return True
192 192
193 193 nodetext = self.read(node)
194 194 return nodetext != text
195 195
196 196 def __nonzero__(self):
197 197 return True
198 198
199 __bool__ = __nonzero__
200
199 201 def __len__(self):
200 202 if self.filename == '.hgtags':
201 203 # The length of .hgtags is used to fast path tag checking.
202 204 # remotefilelog doesn't support .hgtags since the entire .hgtags
203 205 # history is needed. Use the excludepattern setting to make
204 206 # .hgtags a normal filelog.
205 207 return 0
206 208
207 209 raise RuntimeError("len not supported")
208 210
209 211 def empty(self):
210 212 return False
211 213
212 214 def flags(self, node):
213 215 if isinstance(node, int):
214 216 raise error.ProgrammingError(
215 217 'remotefilelog does not accept integer rev for flags')
216 218 store = self.repo.contentstore
217 219 return store.getmeta(self.filename, node).get(constants.METAKEYFLAG, 0)
218 220
219 221 def parents(self, node):
220 222 if node == nullid:
221 223 return nullid, nullid
222 224
223 225 ancestormap = self.repo.metadatastore.getancestors(self.filename, node)
224 226 p1, p2, linknode, copyfrom = ancestormap[node]
225 227 if copyfrom:
226 228 p1 = nullid
227 229
228 230 return p1, p2
229 231
230 232 def parentrevs(self, rev):
231 233 # TODO(augie): this is a node and should be a rev, but for now
232 234 # nothing in core seems to actually break.
233 235 return self.parents(rev)
234 236
235 237 def linknode(self, node):
236 238 ancestormap = self.repo.metadatastore.getancestors(self.filename, node)
237 239 p1, p2, linknode, copyfrom = ancestormap[node]
238 240 return linknode
239 241
240 242 def linkrev(self, node):
241 243 return self.repo.unfiltered().changelog.rev(self.linknode(node))
242 244
243 245 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
244 246 assumehaveparentrevisions=False, deltaprevious=False,
245 247 deltamode=None):
246 248 # we don't use any of these parameters here
247 249 del nodesorder, revisiondata, assumehaveparentrevisions, deltaprevious
248 250 del deltamode
249 251 prevnode = None
250 252 for node in nodes:
251 253 p1, p2 = self.parents(node)
252 254 if prevnode is None:
253 255 basenode = prevnode = p1
254 256 if basenode == node:
255 257 basenode = nullid
256 258 if basenode != nullid:
257 259 revision = None
258 260 delta = self.revdiff(basenode, node)
259 261 else:
260 262 revision = self.revision(node, raw=True)
261 263 delta = None
262 264 yield revlog.revlogrevisiondelta(
263 265 node=node,
264 266 p1node=p1,
265 267 p2node=p2,
266 268 linknode=self.linknode(node),
267 269 basenode=basenode,
268 270 flags=self.flags(node),
269 271 baserevisionsize=None,
270 272 revision=revision,
271 273 delta=delta,
272 274 )
273 275
274 276 def revdiff(self, node1, node2):
275 277 return mdiff.textdiff(self.revision(node1, raw=True),
276 278 self.revision(node2, raw=True))
277 279
278 280 def lookup(self, node):
279 281 if len(node) == 40:
280 282 node = bin(node)
281 283 if len(node) != 20:
282 284 raise error.LookupError(node, self.filename,
283 285 _('invalid lookup input'))
284 286
285 287 return node
286 288
287 289 def rev(self, node):
288 290 # This is a hack to make TortoiseHG work.
289 291 return node
290 292
291 293 def node(self, rev):
292 294 # This is a hack.
293 295 if isinstance(rev, int):
294 296 raise error.ProgrammingError(
295 297 'remotefilelog does not convert integer rev to node')
296 298 return rev
297 299
298 300 def revision(self, node, raw=False):
299 301 """returns the revlog contents at this node.
300 302 this includes the meta data traditionally included in file revlogs.
301 303 this is generally only used for bundling and communicating with vanilla
302 304 hg clients.
303 305 """
304 306 if node == nullid:
305 307 return ""
306 308 if len(node) != 20:
307 309 raise error.LookupError(node, self.filename,
308 310 _('invalid revision input'))
309 311
310 312 store = self.repo.contentstore
311 313 rawtext = store.get(self.filename, node)
312 314 if raw:
313 315 return rawtext
314 316 flags = store.getmeta(self.filename, node).get(constants.METAKEYFLAG, 0)
315 317 if flags == 0:
316 318 return rawtext
317 319 text, verifyhash = self._processflags(rawtext, flags, 'read')
318 320 return text
319 321
320 322 def _processflags(self, text, flags, operation, raw=False):
321 323 # mostly copied from hg/mercurial/revlog.py
322 324 validatehash = True
323 325 orderedflags = revlog.REVIDX_FLAGS_ORDER
324 326 if operation == 'write':
325 327 orderedflags = reversed(orderedflags)
326 328 for flag in orderedflags:
327 329 if flag & flags:
328 330 vhash = True
329 331 if flag not in revlog._flagprocessors:
330 332 message = _("missing processor for flag '%#x'") % (flag)
331 333 raise revlog.RevlogError(message)
332 334 readfunc, writefunc, rawfunc = revlog._flagprocessors[flag]
333 335 if raw:
334 336 vhash = rawfunc(self, text)
335 337 elif operation == 'read':
336 338 text, vhash = readfunc(self, text)
337 339 elif operation == 'write':
338 340 text, vhash = writefunc(self, text)
339 341 validatehash = validatehash and vhash
340 342 return text, validatehash
341 343
342 344 def _read(self, id):
343 345 """reads the raw file blob from disk, cache, or server"""
344 346 fileservice = self.repo.fileservice
345 347 localcache = fileservice.localcache
346 348 cachekey = fileserverclient.getcachekey(self.repo.name, self.filename,
347 349 id)
348 350 try:
349 351 return localcache.read(cachekey)
350 352 except KeyError:
351 353 pass
352 354
353 355 localkey = fileserverclient.getlocalkey(self.filename, id)
354 356 localpath = os.path.join(self.localpath, localkey)
355 357 try:
356 358 return shallowutil.readfile(localpath)
357 359 except IOError:
358 360 pass
359 361
360 362 fileservice.prefetch([(self.filename, id)])
361 363 try:
362 364 return localcache.read(cachekey)
363 365 except KeyError:
364 366 pass
365 367
366 368 raise error.LookupError(id, self.filename, _('no node'))
367 369
368 370 def ancestormap(self, node):
369 371 return self.repo.metadatastore.getancestors(self.filename, node)
370 372
371 373 def ancestor(self, a, b):
372 374 if a == nullid or b == nullid:
373 375 return nullid
374 376
375 377 revmap, parentfunc = self._buildrevgraph(a, b)
376 378 nodemap = dict(((v, k) for (k, v) in revmap.iteritems()))
377 379
378 380 ancs = ancestor.ancestors(parentfunc, revmap[a], revmap[b])
379 381 if ancs:
380 382 # choose a consistent winner when there's a tie
381 383 return min(map(nodemap.__getitem__, ancs))
382 384 return nullid
383 385
384 386 def commonancestorsheads(self, a, b):
385 387 """calculate all the heads of the common ancestors of nodes a and b"""
386 388
387 389 if a == nullid or b == nullid:
388 390 return nullid
389 391
390 392 revmap, parentfunc = self._buildrevgraph(a, b)
391 393 nodemap = dict(((v, k) for (k, v) in revmap.iteritems()))
392 394
393 395 ancs = ancestor.commonancestorsheads(parentfunc, revmap[a], revmap[b])
394 396 return map(nodemap.__getitem__, ancs)
395 397
396 398 def _buildrevgraph(self, a, b):
397 399 """Builds a numeric revision graph for the given two nodes.
398 400 Returns a node->rev map and a rev->[revs] parent function.
399 401 """
400 402 amap = self.ancestormap(a)
401 403 bmap = self.ancestormap(b)
402 404
403 405 # Union the two maps
404 406 parentsmap = collections.defaultdict(list)
405 407 allparents = set()
406 408 for mapping in (amap, bmap):
407 409 for node, pdata in mapping.iteritems():
408 410 parents = parentsmap[node]
409 411 p1, p2, linknode, copyfrom = pdata
410 412 # Don't follow renames (copyfrom).
411 413 # remotefilectx.ancestor does that.
412 414 if p1 != nullid and not copyfrom:
413 415 parents.append(p1)
414 416 allparents.add(p1)
415 417 if p2 != nullid:
416 418 parents.append(p2)
417 419 allparents.add(p2)
418 420
419 421 # Breadth first traversal to build linkrev graph
420 422 parentrevs = collections.defaultdict(list)
421 423 revmap = {}
422 424 queue = collections.deque(((None, n) for n in parentsmap
423 425 if n not in allparents))
424 426 while queue:
425 427 prevrev, current = queue.pop()
426 428 if current in revmap:
427 429 if prevrev:
428 430 parentrevs[prevrev].append(revmap[current])
429 431 continue
430 432
431 433 # Assign linkrevs in reverse order, so start at
432 434 # len(parentsmap) and work backwards.
433 435 currentrev = len(parentsmap) - len(revmap) - 1
434 436 revmap[current] = currentrev
435 437
436 438 if prevrev:
437 439 parentrevs[prevrev].append(currentrev)
438 440
439 441 for parent in parentsmap.get(current):
440 442 queue.appendleft((currentrev, parent))
441 443
442 444 return revmap, parentrevs.__getitem__
443 445
444 446 def strip(self, minlink, transaction):
445 447 pass
446 448
447 449 # misc unused things
448 450 def files(self):
449 451 return []
450 452
451 453 def checksize(self):
452 454 return 0, 0
General Comments 0
You need to be logged in to leave comments. Login now