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