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