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