##// END OF EJS Templates
remotefilelog: fix bug in maybesparsematch returning alwaysmatcher...
Kyle Lippincott -
r41107:517a51d9 default
parent child Browse files
Show More
@@ -1,300 +1,305 b''
1 # shallowrepo.py - shallow repository that uses remote filelogs
1 # shallowrepo.py - shallow repository that uses remote filelogs
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
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 os
9 import os
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import hex, nullid, nullrev
12 from mercurial.node import hex, nullid, nullrev
13 from mercurial import (
13 from mercurial import (
14 encoding,
14 encoding,
15 error,
15 error,
16 localrepo,
16 localrepo,
17 match,
17 match,
18 scmutil,
18 scmutil,
19 sparse,
19 sparse,
20 util,
20 util,
21 )
21 )
22 from mercurial.utils import procutil
22 from mercurial.utils import procutil
23 from . import (
23 from . import (
24 connectionpool,
24 connectionpool,
25 constants,
25 constants,
26 contentstore,
26 contentstore,
27 datapack,
27 datapack,
28 fileserverclient,
28 fileserverclient,
29 historypack,
29 historypack,
30 metadatastore,
30 metadatastore,
31 remotefilectx,
31 remotefilectx,
32 remotefilelog,
32 remotefilelog,
33 shallowutil,
33 shallowutil,
34 )
34 )
35
35
36 if util.safehasattr(util, '_hgexecutable'):
36 if util.safehasattr(util, '_hgexecutable'):
37 # Before 5be286db
37 # Before 5be286db
38 _hgexecutable = util.hgexecutable
38 _hgexecutable = util.hgexecutable
39 else:
39 else:
40 from mercurial.utils import procutil
40 from mercurial.utils import procutil
41 _hgexecutable = procutil.hgexecutable
41 _hgexecutable = procutil.hgexecutable
42
42
43 # These make*stores functions are global so that other extensions can replace
43 # These make*stores functions are global so that other extensions can replace
44 # them.
44 # them.
45 def makelocalstores(repo):
45 def makelocalstores(repo):
46 """In-repo stores, like .hg/store/data; can not be discarded."""
46 """In-repo stores, like .hg/store/data; can not be discarded."""
47 localpath = os.path.join(repo.svfs.vfs.base, 'data')
47 localpath = os.path.join(repo.svfs.vfs.base, 'data')
48 if not os.path.exists(localpath):
48 if not os.path.exists(localpath):
49 os.makedirs(localpath)
49 os.makedirs(localpath)
50
50
51 # Instantiate local data stores
51 # Instantiate local data stores
52 localcontent = contentstore.remotefilelogcontentstore(
52 localcontent = contentstore.remotefilelogcontentstore(
53 repo, localpath, repo.name, shared=False)
53 repo, localpath, repo.name, shared=False)
54 localmetadata = metadatastore.remotefilelogmetadatastore(
54 localmetadata = metadatastore.remotefilelogmetadatastore(
55 repo, localpath, repo.name, shared=False)
55 repo, localpath, repo.name, shared=False)
56 return localcontent, localmetadata
56 return localcontent, localmetadata
57
57
58 def makecachestores(repo):
58 def makecachestores(repo):
59 """Typically machine-wide, cache of remote data; can be discarded."""
59 """Typically machine-wide, cache of remote data; can be discarded."""
60 # Instantiate shared cache stores
60 # Instantiate shared cache stores
61 cachepath = shallowutil.getcachepath(repo.ui)
61 cachepath = shallowutil.getcachepath(repo.ui)
62 cachecontent = contentstore.remotefilelogcontentstore(
62 cachecontent = contentstore.remotefilelogcontentstore(
63 repo, cachepath, repo.name, shared=True)
63 repo, cachepath, repo.name, shared=True)
64 cachemetadata = metadatastore.remotefilelogmetadatastore(
64 cachemetadata = metadatastore.remotefilelogmetadatastore(
65 repo, cachepath, repo.name, shared=True)
65 repo, cachepath, repo.name, shared=True)
66
66
67 repo.sharedstore = cachecontent
67 repo.sharedstore = cachecontent
68 repo.shareddatastores.append(cachecontent)
68 repo.shareddatastores.append(cachecontent)
69 repo.sharedhistorystores.append(cachemetadata)
69 repo.sharedhistorystores.append(cachemetadata)
70
70
71 return cachecontent, cachemetadata
71 return cachecontent, cachemetadata
72
72
73 def makeremotestores(repo, cachecontent, cachemetadata):
73 def makeremotestores(repo, cachecontent, cachemetadata):
74 """These stores fetch data from a remote server."""
74 """These stores fetch data from a remote server."""
75 # Instantiate remote stores
75 # Instantiate remote stores
76 repo.fileservice = fileserverclient.fileserverclient(repo)
76 repo.fileservice = fileserverclient.fileserverclient(repo)
77 remotecontent = contentstore.remotecontentstore(
77 remotecontent = contentstore.remotecontentstore(
78 repo.ui, repo.fileservice, cachecontent)
78 repo.ui, repo.fileservice, cachecontent)
79 remotemetadata = metadatastore.remotemetadatastore(
79 remotemetadata = metadatastore.remotemetadatastore(
80 repo.ui, repo.fileservice, cachemetadata)
80 repo.ui, repo.fileservice, cachemetadata)
81 return remotecontent, remotemetadata
81 return remotecontent, remotemetadata
82
82
83 def makepackstores(repo):
83 def makepackstores(repo):
84 """Packs are more efficient (to read from) cache stores."""
84 """Packs are more efficient (to read from) cache stores."""
85 # Instantiate pack stores
85 # Instantiate pack stores
86 packpath = shallowutil.getcachepackpath(repo,
86 packpath = shallowutil.getcachepackpath(repo,
87 constants.FILEPACK_CATEGORY)
87 constants.FILEPACK_CATEGORY)
88 packcontentstore = datapack.datapackstore(repo.ui, packpath)
88 packcontentstore = datapack.datapackstore(repo.ui, packpath)
89 packmetadatastore = historypack.historypackstore(repo.ui, packpath)
89 packmetadatastore = historypack.historypackstore(repo.ui, packpath)
90
90
91 repo.shareddatastores.append(packcontentstore)
91 repo.shareddatastores.append(packcontentstore)
92 repo.sharedhistorystores.append(packmetadatastore)
92 repo.sharedhistorystores.append(packmetadatastore)
93 shallowutil.reportpackmetrics(repo.ui, 'filestore', packcontentstore,
93 shallowutil.reportpackmetrics(repo.ui, 'filestore', packcontentstore,
94 packmetadatastore)
94 packmetadatastore)
95 return packcontentstore, packmetadatastore
95 return packcontentstore, packmetadatastore
96
96
97 def makeunionstores(repo):
97 def makeunionstores(repo):
98 """Union stores iterate the other stores and return the first result."""
98 """Union stores iterate the other stores and return the first result."""
99 repo.shareddatastores = []
99 repo.shareddatastores = []
100 repo.sharedhistorystores = []
100 repo.sharedhistorystores = []
101
101
102 packcontentstore, packmetadatastore = makepackstores(repo)
102 packcontentstore, packmetadatastore = makepackstores(repo)
103 cachecontent, cachemetadata = makecachestores(repo)
103 cachecontent, cachemetadata = makecachestores(repo)
104 localcontent, localmetadata = makelocalstores(repo)
104 localcontent, localmetadata = makelocalstores(repo)
105 remotecontent, remotemetadata = makeremotestores(repo, cachecontent,
105 remotecontent, remotemetadata = makeremotestores(repo, cachecontent,
106 cachemetadata)
106 cachemetadata)
107
107
108 # Instantiate union stores
108 # Instantiate union stores
109 repo.contentstore = contentstore.unioncontentstore(
109 repo.contentstore = contentstore.unioncontentstore(
110 packcontentstore, cachecontent,
110 packcontentstore, cachecontent,
111 localcontent, remotecontent, writestore=localcontent)
111 localcontent, remotecontent, writestore=localcontent)
112 repo.metadatastore = metadatastore.unionmetadatastore(
112 repo.metadatastore = metadatastore.unionmetadatastore(
113 packmetadatastore, cachemetadata, localmetadata, remotemetadata,
113 packmetadatastore, cachemetadata, localmetadata, remotemetadata,
114 writestore=localmetadata)
114 writestore=localmetadata)
115
115
116 fileservicedatawrite = cachecontent
116 fileservicedatawrite = cachecontent
117 fileservicehistorywrite = cachemetadata
117 fileservicehistorywrite = cachemetadata
118 repo.fileservice.setstore(repo.contentstore, repo.metadatastore,
118 repo.fileservice.setstore(repo.contentstore, repo.metadatastore,
119 fileservicedatawrite, fileservicehistorywrite)
119 fileservicedatawrite, fileservicehistorywrite)
120 shallowutil.reportpackmetrics(repo.ui, 'filestore',
120 shallowutil.reportpackmetrics(repo.ui, 'filestore',
121 packcontentstore, packmetadatastore)
121 packcontentstore, packmetadatastore)
122
122
123 def wraprepo(repo):
123 def wraprepo(repo):
124 class shallowrepository(repo.__class__):
124 class shallowrepository(repo.__class__):
125 @util.propertycache
125 @util.propertycache
126 def name(self):
126 def name(self):
127 return self.ui.config('remotefilelog', 'reponame')
127 return self.ui.config('remotefilelog', 'reponame')
128
128
129 @util.propertycache
129 @util.propertycache
130 def fallbackpath(self):
130 def fallbackpath(self):
131 path = repo.ui.config("remotefilelog", "fallbackpath",
131 path = repo.ui.config("remotefilelog", "fallbackpath",
132 repo.ui.config('paths', 'default'))
132 repo.ui.config('paths', 'default'))
133 if not path:
133 if not path:
134 raise error.Abort("no remotefilelog server "
134 raise error.Abort("no remotefilelog server "
135 "configured - is your .hg/hgrc trusted?")
135 "configured - is your .hg/hgrc trusted?")
136
136
137 return path
137 return path
138
138
139 def maybesparsematch(self, *revs, **kwargs):
139 def maybesparsematch(self, *revs, **kwargs):
140 '''
140 '''
141 A wrapper that allows the remotefilelog to invoke sparsematch() if
141 A wrapper that allows the remotefilelog to invoke sparsematch() if
142 this is a sparse repository, or returns None if this is not a
142 this is a sparse repository, or returns None if this is not a
143 sparse repository.
143 sparse repository.
144 '''
144 '''
145 if revs:
145 if revs:
146 return sparse.matcher(repo, revs=revs)
146 ret = sparse.matcher(repo, revs=revs)
147 return sparse.matcher(repo)
147 else:
148 ret = sparse.matcher(repo)
149
150 if ret.always():
151 return None
152 return ret
148
153
149 def file(self, f):
154 def file(self, f):
150 if f[0] == '/':
155 if f[0] == '/':
151 f = f[1:]
156 f = f[1:]
152
157
153 if self.shallowmatch(f):
158 if self.shallowmatch(f):
154 return remotefilelog.remotefilelog(self.svfs, f, self)
159 return remotefilelog.remotefilelog(self.svfs, f, self)
155 else:
160 else:
156 return super(shallowrepository, self).file(f)
161 return super(shallowrepository, self).file(f)
157
162
158 def filectx(self, path, *args, **kwargs):
163 def filectx(self, path, *args, **kwargs):
159 if self.shallowmatch(path):
164 if self.shallowmatch(path):
160 return remotefilectx.remotefilectx(self, path, *args, **kwargs)
165 return remotefilectx.remotefilectx(self, path, *args, **kwargs)
161 else:
166 else:
162 return super(shallowrepository, self).filectx(path, *args,
167 return super(shallowrepository, self).filectx(path, *args,
163 **kwargs)
168 **kwargs)
164
169
165 @localrepo.unfilteredmethod
170 @localrepo.unfilteredmethod
166 def commitctx(self, ctx, error=False):
171 def commitctx(self, ctx, error=False):
167 """Add a new revision to current repository.
172 """Add a new revision to current repository.
168 Revision information is passed via the context argument.
173 Revision information is passed via the context argument.
169 """
174 """
170
175
171 # some contexts already have manifest nodes, they don't need any
176 # some contexts already have manifest nodes, they don't need any
172 # prefetching (for example if we're just editing a commit message
177 # prefetching (for example if we're just editing a commit message
173 # we can reuse manifest
178 # we can reuse manifest
174 if not ctx.manifestnode():
179 if not ctx.manifestnode():
175 # prefetch files that will likely be compared
180 # prefetch files that will likely be compared
176 m1 = ctx.p1().manifest()
181 m1 = ctx.p1().manifest()
177 files = []
182 files = []
178 for f in ctx.modified() + ctx.added():
183 for f in ctx.modified() + ctx.added():
179 fparent1 = m1.get(f, nullid)
184 fparent1 = m1.get(f, nullid)
180 if fparent1 != nullid:
185 if fparent1 != nullid:
181 files.append((f, hex(fparent1)))
186 files.append((f, hex(fparent1)))
182 self.fileservice.prefetch(files)
187 self.fileservice.prefetch(files)
183 return super(shallowrepository, self).commitctx(ctx,
188 return super(shallowrepository, self).commitctx(ctx,
184 error=error)
189 error=error)
185
190
186 def backgroundprefetch(self, revs, base=None, repack=False, pats=None,
191 def backgroundprefetch(self, revs, base=None, repack=False, pats=None,
187 opts=None):
192 opts=None):
188 """Runs prefetch in background with optional repack
193 """Runs prefetch in background with optional repack
189 """
194 """
190 cmd = [_hgexecutable(), '-R', repo.origroot, 'prefetch']
195 cmd = [_hgexecutable(), '-R', repo.origroot, 'prefetch']
191 if repack:
196 if repack:
192 cmd.append('--repack')
197 cmd.append('--repack')
193 if revs:
198 if revs:
194 cmd += ['-r', revs]
199 cmd += ['-r', revs]
195 procutil.runbgcommand(cmd, encoding.environ)
200 procutil.runbgcommand(cmd, encoding.environ)
196
201
197 def prefetch(self, revs, base=None, pats=None, opts=None):
202 def prefetch(self, revs, base=None, pats=None, opts=None):
198 """Prefetches all the necessary file revisions for the given revs
203 """Prefetches all the necessary file revisions for the given revs
199 Optionally runs repack in background
204 Optionally runs repack in background
200 """
205 """
201 with repo._lock(repo.svfs, 'prefetchlock', True, None, None,
206 with repo._lock(repo.svfs, 'prefetchlock', True, None, None,
202 _('prefetching in %s') % repo.origroot):
207 _('prefetching in %s') % repo.origroot):
203 self._prefetch(revs, base, pats, opts)
208 self._prefetch(revs, base, pats, opts)
204
209
205 def _prefetch(self, revs, base=None, pats=None, opts=None):
210 def _prefetch(self, revs, base=None, pats=None, opts=None):
206 fallbackpath = self.fallbackpath
211 fallbackpath = self.fallbackpath
207 if fallbackpath:
212 if fallbackpath:
208 # If we know a rev is on the server, we should fetch the server
213 # If we know a rev is on the server, we should fetch the server
209 # version of those files, since our local file versions might
214 # version of those files, since our local file versions might
210 # become obsolete if the local commits are stripped.
215 # become obsolete if the local commits are stripped.
211 localrevs = repo.revs('outgoing(%s)', fallbackpath)
216 localrevs = repo.revs('outgoing(%s)', fallbackpath)
212 if base is not None and base != nullrev:
217 if base is not None and base != nullrev:
213 serverbase = list(repo.revs('first(reverse(::%s) - %ld)',
218 serverbase = list(repo.revs('first(reverse(::%s) - %ld)',
214 base, localrevs))
219 base, localrevs))
215 if serverbase:
220 if serverbase:
216 base = serverbase[0]
221 base = serverbase[0]
217 else:
222 else:
218 localrevs = repo
223 localrevs = repo
219
224
220 mfl = repo.manifestlog
225 mfl = repo.manifestlog
221 mfrevlog = mfl.getstorage('')
226 mfrevlog = mfl.getstorage('')
222 if base is not None:
227 if base is not None:
223 mfdict = mfl[repo[base].manifestnode()].read()
228 mfdict = mfl[repo[base].manifestnode()].read()
224 skip = set(mfdict.iteritems())
229 skip = set(mfdict.iteritems())
225 else:
230 else:
226 skip = set()
231 skip = set()
227
232
228 # Copy the skip set to start large and avoid constant resizing,
233 # Copy the skip set to start large and avoid constant resizing,
229 # and since it's likely to be very similar to the prefetch set.
234 # and since it's likely to be very similar to the prefetch set.
230 files = skip.copy()
235 files = skip.copy()
231 serverfiles = skip.copy()
236 serverfiles = skip.copy()
232 visited = set()
237 visited = set()
233 visited.add(nullrev)
238 visited.add(nullrev)
234 revcount = len(revs)
239 revcount = len(revs)
235 progress = self.ui.makeprogress(_('prefetching'), total=revcount)
240 progress = self.ui.makeprogress(_('prefetching'), total=revcount)
236 progress.update(0)
241 progress.update(0)
237 for rev in sorted(revs):
242 for rev in sorted(revs):
238 ctx = repo[rev]
243 ctx = repo[rev]
239 if pats:
244 if pats:
240 m = scmutil.match(ctx, pats, opts)
245 m = scmutil.match(ctx, pats, opts)
241 sparsematch = repo.maybesparsematch(rev)
246 sparsematch = repo.maybesparsematch(rev)
242
247
243 mfnode = ctx.manifestnode()
248 mfnode = ctx.manifestnode()
244 mfrev = mfrevlog.rev(mfnode)
249 mfrev = mfrevlog.rev(mfnode)
245
250
246 # Decompressing manifests is expensive.
251 # Decompressing manifests is expensive.
247 # When possible, only read the deltas.
252 # When possible, only read the deltas.
248 p1, p2 = mfrevlog.parentrevs(mfrev)
253 p1, p2 = mfrevlog.parentrevs(mfrev)
249 if p1 in visited and p2 in visited:
254 if p1 in visited and p2 in visited:
250 mfdict = mfl[mfnode].readfast()
255 mfdict = mfl[mfnode].readfast()
251 else:
256 else:
252 mfdict = mfl[mfnode].read()
257 mfdict = mfl[mfnode].read()
253
258
254 diff = mfdict.iteritems()
259 diff = mfdict.iteritems()
255 if pats:
260 if pats:
256 diff = (pf for pf in diff if m(pf[0]))
261 diff = (pf for pf in diff if m(pf[0]))
257 if sparsematch:
262 if sparsematch:
258 diff = (pf for pf in diff if sparsematch(pf[0]))
263 diff = (pf for pf in diff if sparsematch(pf[0]))
259 if rev not in localrevs:
264 if rev not in localrevs:
260 serverfiles.update(diff)
265 serverfiles.update(diff)
261 else:
266 else:
262 files.update(diff)
267 files.update(diff)
263
268
264 visited.add(mfrev)
269 visited.add(mfrev)
265 progress.increment()
270 progress.increment()
266
271
267 files.difference_update(skip)
272 files.difference_update(skip)
268 serverfiles.difference_update(skip)
273 serverfiles.difference_update(skip)
269 progress.complete()
274 progress.complete()
270
275
271 # Fetch files known to be on the server
276 # Fetch files known to be on the server
272 if serverfiles:
277 if serverfiles:
273 results = [(path, hex(fnode)) for (path, fnode) in serverfiles]
278 results = [(path, hex(fnode)) for (path, fnode) in serverfiles]
274 repo.fileservice.prefetch(results, force=True)
279 repo.fileservice.prefetch(results, force=True)
275
280
276 # Fetch files that may or may not be on the server
281 # Fetch files that may or may not be on the server
277 if files:
282 if files:
278 results = [(path, hex(fnode)) for (path, fnode) in files]
283 results = [(path, hex(fnode)) for (path, fnode) in files]
279 repo.fileservice.prefetch(results)
284 repo.fileservice.prefetch(results)
280
285
281 def close(self):
286 def close(self):
282 super(shallowrepository, self).close()
287 super(shallowrepository, self).close()
283 self.connectionpool.close()
288 self.connectionpool.close()
284
289
285 repo.__class__ = shallowrepository
290 repo.__class__ = shallowrepository
286
291
287 repo.shallowmatch = match.always(repo.root, '')
292 repo.shallowmatch = match.always(repo.root, '')
288
293
289 makeunionstores(repo)
294 makeunionstores(repo)
290
295
291 repo.includepattern = repo.ui.configlist("remotefilelog", "includepattern",
296 repo.includepattern = repo.ui.configlist("remotefilelog", "includepattern",
292 None)
297 None)
293 repo.excludepattern = repo.ui.configlist("remotefilelog", "excludepattern",
298 repo.excludepattern = repo.ui.configlist("remotefilelog", "excludepattern",
294 None)
299 None)
295 if not util.safehasattr(repo, 'connectionpool'):
300 if not util.safehasattr(repo, 'connectionpool'):
296 repo.connectionpool = connectionpool.connectionpool(repo)
301 repo.connectionpool = connectionpool.connectionpool(repo)
297
302
298 if repo.includepattern or repo.excludepattern:
303 if repo.includepattern or repo.excludepattern:
299 repo.shallowmatch = match.match(repo.root, '', None,
304 repo.shallowmatch = match.match(repo.root, '', None,
300 repo.includepattern, repo.excludepattern)
305 repo.includepattern, repo.excludepattern)
General Comments 0
You need to be logged in to leave comments. Login now