##// END OF EJS Templates
lfs: respect narrowmatcher when testing to add 'lfs' requirement (issue5794)...
Matt Harbison -
r37156:4d63f3bc default
parent child Browse files
Show More
@@ -1,387 +1,389 b''
1 # lfs - hash-preserving large file support using Git-LFS protocol
1 # lfs - hash-preserving large file support using Git-LFS protocol
2 #
2 #
3 # Copyright 2017 Facebook, Inc.
3 # Copyright 2017 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
7
8 """lfs - large file support (EXPERIMENTAL)
8 """lfs - large file support (EXPERIMENTAL)
9
9
10 This extension allows large files to be tracked outside of the normal
10 This extension allows large files to be tracked outside of the normal
11 repository storage and stored on a centralized server, similar to the
11 repository storage and stored on a centralized server, similar to the
12 ``largefiles`` extension. The ``git-lfs`` protocol is used when
12 ``largefiles`` extension. The ``git-lfs`` protocol is used when
13 communicating with the server, so existing git infrastructure can be
13 communicating with the server, so existing git infrastructure can be
14 harnessed. Even though the files are stored outside of the repository,
14 harnessed. Even though the files are stored outside of the repository,
15 they are still integrity checked in the same manner as normal files.
15 they are still integrity checked in the same manner as normal files.
16
16
17 The files stored outside of the repository are downloaded on demand,
17 The files stored outside of the repository are downloaded on demand,
18 which reduces the time to clone, and possibly the local disk usage.
18 which reduces the time to clone, and possibly the local disk usage.
19 This changes fundamental workflows in a DVCS, so careful thought
19 This changes fundamental workflows in a DVCS, so careful thought
20 should be given before deploying it. :hg:`convert` can be used to
20 should be given before deploying it. :hg:`convert` can be used to
21 convert LFS repositories to normal repositories that no longer
21 convert LFS repositories to normal repositories that no longer
22 require this extension, and do so without changing the commit hashes.
22 require this extension, and do so without changing the commit hashes.
23 This allows the extension to be disabled if the centralized workflow
23 This allows the extension to be disabled if the centralized workflow
24 becomes burdensome. However, the pre and post convert clones will
24 becomes burdensome. However, the pre and post convert clones will
25 not be able to communicate with each other unless the extension is
25 not be able to communicate with each other unless the extension is
26 enabled on both.
26 enabled on both.
27
27
28 To start a new repository, or to add LFS files to an existing one, just
28 To start a new repository, or to add LFS files to an existing one, just
29 create an ``.hglfs`` file as described below in the root directory of
29 create an ``.hglfs`` file as described below in the root directory of
30 the repository. Typically, this file should be put under version
30 the repository. Typically, this file should be put under version
31 control, so that the settings will propagate to other repositories with
31 control, so that the settings will propagate to other repositories with
32 push and pull. During any commit, Mercurial will consult this file to
32 push and pull. During any commit, Mercurial will consult this file to
33 determine if an added or modified file should be stored externally. The
33 determine if an added or modified file should be stored externally. The
34 type of storage depends on the characteristics of the file at each
34 type of storage depends on the characteristics of the file at each
35 commit. A file that is near a size threshold may switch back and forth
35 commit. A file that is near a size threshold may switch back and forth
36 between LFS and normal storage, as needed.
36 between LFS and normal storage, as needed.
37
37
38 Alternately, both normal repositories and largefile controlled
38 Alternately, both normal repositories and largefile controlled
39 repositories can be converted to LFS by using :hg:`convert` and the
39 repositories can be converted to LFS by using :hg:`convert` and the
40 ``lfs.track`` config option described below. The ``.hglfs`` file
40 ``lfs.track`` config option described below. The ``.hglfs`` file
41 should then be created and added, to control subsequent LFS selection.
41 should then be created and added, to control subsequent LFS selection.
42 The hashes are also unchanged in this case. The LFS and non-LFS
42 The hashes are also unchanged in this case. The LFS and non-LFS
43 repositories can be distinguished because the LFS repository will
43 repositories can be distinguished because the LFS repository will
44 abort any command if this extension is disabled.
44 abort any command if this extension is disabled.
45
45
46 Committed LFS files are held locally, until the repository is pushed.
46 Committed LFS files are held locally, until the repository is pushed.
47 Prior to pushing the normal repository data, the LFS files that are
47 Prior to pushing the normal repository data, the LFS files that are
48 tracked by the outgoing commits are automatically uploaded to the
48 tracked by the outgoing commits are automatically uploaded to the
49 configured central server. No LFS files are transferred on
49 configured central server. No LFS files are transferred on
50 :hg:`pull` or :hg:`clone`. Instead, the files are downloaded on
50 :hg:`pull` or :hg:`clone`. Instead, the files are downloaded on
51 demand as they need to be read, if a cached copy cannot be found
51 demand as they need to be read, if a cached copy cannot be found
52 locally. Both committing and downloading an LFS file will link the
52 locally. Both committing and downloading an LFS file will link the
53 file to a usercache, to speed up future access. See the `usercache`
53 file to a usercache, to speed up future access. See the `usercache`
54 config setting described below.
54 config setting described below.
55
55
56 .hglfs::
56 .hglfs::
57
57
58 The extension reads its configuration from a versioned ``.hglfs``
58 The extension reads its configuration from a versioned ``.hglfs``
59 configuration file found in the root of the working directory. The
59 configuration file found in the root of the working directory. The
60 ``.hglfs`` file uses the same syntax as all other Mercurial
60 ``.hglfs`` file uses the same syntax as all other Mercurial
61 configuration files. It uses a single section, ``[track]``.
61 configuration files. It uses a single section, ``[track]``.
62
62
63 The ``[track]`` section specifies which files are stored as LFS (or
63 The ``[track]`` section specifies which files are stored as LFS (or
64 not). Each line is keyed by a file pattern, with a predicate value.
64 not). Each line is keyed by a file pattern, with a predicate value.
65 The first file pattern match is used, so put more specific patterns
65 The first file pattern match is used, so put more specific patterns
66 first. The available predicates are ``all()``, ``none()``, and
66 first. The available predicates are ``all()``, ``none()``, and
67 ``size()``. See "hg help filesets.size" for the latter.
67 ``size()``. See "hg help filesets.size" for the latter.
68
68
69 Example versioned ``.hglfs`` file::
69 Example versioned ``.hglfs`` file::
70
70
71 [track]
71 [track]
72 # No Makefile or python file, anywhere, will be LFS
72 # No Makefile or python file, anywhere, will be LFS
73 **Makefile = none()
73 **Makefile = none()
74 **.py = none()
74 **.py = none()
75
75
76 **.zip = all()
76 **.zip = all()
77 **.exe = size(">1MB")
77 **.exe = size(">1MB")
78
78
79 # Catchall for everything not matched above
79 # Catchall for everything not matched above
80 ** = size(">10MB")
80 ** = size(">10MB")
81
81
82 Configs::
82 Configs::
83
83
84 [lfs]
84 [lfs]
85 # Remote endpoint. Multiple protocols are supported:
85 # Remote endpoint. Multiple protocols are supported:
86 # - http(s)://user:pass@example.com/path
86 # - http(s)://user:pass@example.com/path
87 # git-lfs endpoint
87 # git-lfs endpoint
88 # - file:///tmp/path
88 # - file:///tmp/path
89 # local filesystem, usually for testing
89 # local filesystem, usually for testing
90 # if unset, lfs will prompt setting this when it must use this value.
90 # if unset, lfs will prompt setting this when it must use this value.
91 # (default: unset)
91 # (default: unset)
92 url = https://example.com/repo.git/info/lfs
92 url = https://example.com/repo.git/info/lfs
93
93
94 # Which files to track in LFS. Path tests are "**.extname" for file
94 # Which files to track in LFS. Path tests are "**.extname" for file
95 # extensions, and "path:under/some/directory" for path prefix. Both
95 # extensions, and "path:under/some/directory" for path prefix. Both
96 # are relative to the repository root.
96 # are relative to the repository root.
97 # File size can be tested with the "size()" fileset, and tests can be
97 # File size can be tested with the "size()" fileset, and tests can be
98 # joined with fileset operators. (See "hg help filesets.operators".)
98 # joined with fileset operators. (See "hg help filesets.operators".)
99 #
99 #
100 # Some examples:
100 # Some examples:
101 # - all() # everything
101 # - all() # everything
102 # - none() # nothing
102 # - none() # nothing
103 # - size(">20MB") # larger than 20MB
103 # - size(">20MB") # larger than 20MB
104 # - !**.txt # anything not a *.txt file
104 # - !**.txt # anything not a *.txt file
105 # - **.zip | **.tar.gz | **.7z # some types of compressed files
105 # - **.zip | **.tar.gz | **.7z # some types of compressed files
106 # - path:bin # files under "bin" in the project root
106 # - path:bin # files under "bin" in the project root
107 # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz
107 # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz
108 # | (path:bin & !path:/bin/README) | size(">1GB")
108 # | (path:bin & !path:/bin/README) | size(">1GB")
109 # (default: none())
109 # (default: none())
110 #
110 #
111 # This is ignored if there is a tracked '.hglfs' file, and this setting
111 # This is ignored if there is a tracked '.hglfs' file, and this setting
112 # will eventually be deprecated and removed.
112 # will eventually be deprecated and removed.
113 track = size(">10M")
113 track = size(">10M")
114
114
115 # how many times to retry before giving up on transferring an object
115 # how many times to retry before giving up on transferring an object
116 retry = 5
116 retry = 5
117
117
118 # the local directory to store lfs files for sharing across local clones.
118 # the local directory to store lfs files for sharing across local clones.
119 # If not set, the cache is located in an OS specific cache location.
119 # If not set, the cache is located in an OS specific cache location.
120 usercache = /path/to/global/cache
120 usercache = /path/to/global/cache
121 """
121 """
122
122
123 from __future__ import absolute_import
123 from __future__ import absolute_import
124
124
125 from mercurial.i18n import _
125 from mercurial.i18n import _
126
126
127 from mercurial import (
127 from mercurial import (
128 bundle2,
128 bundle2,
129 changegroup,
129 changegroup,
130 cmdutil,
130 cmdutil,
131 config,
131 config,
132 context,
132 context,
133 error,
133 error,
134 exchange,
134 exchange,
135 extensions,
135 extensions,
136 filelog,
136 filelog,
137 fileset,
137 fileset,
138 hg,
138 hg,
139 localrepo,
139 localrepo,
140 minifileset,
140 minifileset,
141 node,
141 node,
142 pycompat,
142 pycompat,
143 registrar,
143 registrar,
144 revlog,
144 revlog,
145 scmutil,
145 scmutil,
146 templateutil,
146 templateutil,
147 upgrade,
147 upgrade,
148 util,
148 util,
149 vfs as vfsmod,
149 vfs as vfsmod,
150 wireproto,
150 wireproto,
151 )
151 )
152
152
153 from . import (
153 from . import (
154 blobstore,
154 blobstore,
155 wrapper,
155 wrapper,
156 )
156 )
157
157
158 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
158 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
159 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
159 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
160 # be specifying the version(s) of Mercurial they are tested with, or
160 # be specifying the version(s) of Mercurial they are tested with, or
161 # leave the attribute unspecified.
161 # leave the attribute unspecified.
162 testedwith = 'ships-with-hg-core'
162 testedwith = 'ships-with-hg-core'
163
163
164 configtable = {}
164 configtable = {}
165 configitem = registrar.configitem(configtable)
165 configitem = registrar.configitem(configtable)
166
166
167 configitem('experimental', 'lfs.user-agent',
167 configitem('experimental', 'lfs.user-agent',
168 default=None,
168 default=None,
169 )
169 )
170 configitem('experimental', 'lfs.worker-enable',
170 configitem('experimental', 'lfs.worker-enable',
171 default=False,
171 default=False,
172 )
172 )
173
173
174 configitem('lfs', 'url',
174 configitem('lfs', 'url',
175 default=None,
175 default=None,
176 )
176 )
177 configitem('lfs', 'usercache',
177 configitem('lfs', 'usercache',
178 default=None,
178 default=None,
179 )
179 )
180 # Deprecated
180 # Deprecated
181 configitem('lfs', 'threshold',
181 configitem('lfs', 'threshold',
182 default=None,
182 default=None,
183 )
183 )
184 configitem('lfs', 'track',
184 configitem('lfs', 'track',
185 default='none()',
185 default='none()',
186 )
186 )
187 configitem('lfs', 'retry',
187 configitem('lfs', 'retry',
188 default=5,
188 default=5,
189 )
189 )
190
190
191 cmdtable = {}
191 cmdtable = {}
192 command = registrar.command(cmdtable)
192 command = registrar.command(cmdtable)
193
193
194 templatekeyword = registrar.templatekeyword()
194 templatekeyword = registrar.templatekeyword()
195 filesetpredicate = registrar.filesetpredicate()
195 filesetpredicate = registrar.filesetpredicate()
196
196
197 def featuresetup(ui, supported):
197 def featuresetup(ui, supported):
198 # don't die on seeing a repo with the lfs requirement
198 # don't die on seeing a repo with the lfs requirement
199 supported |= {'lfs'}
199 supported |= {'lfs'}
200
200
201 def uisetup(ui):
201 def uisetup(ui):
202 localrepo.featuresetupfuncs.add(featuresetup)
202 localrepo.featuresetupfuncs.add(featuresetup)
203
203
204 def reposetup(ui, repo):
204 def reposetup(ui, repo):
205 # Nothing to do with a remote repo
205 # Nothing to do with a remote repo
206 if not repo.local():
206 if not repo.local():
207 return
207 return
208
208
209 repo.svfs.lfslocalblobstore = blobstore.local(repo)
209 repo.svfs.lfslocalblobstore = blobstore.local(repo)
210 repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
210 repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
211
211
212 class lfsrepo(repo.__class__):
212 class lfsrepo(repo.__class__):
213 @localrepo.unfilteredmethod
213 @localrepo.unfilteredmethod
214 def commitctx(self, ctx, error=False):
214 def commitctx(self, ctx, error=False):
215 repo.svfs.options['lfstrack'] = _trackedmatcher(self)
215 repo.svfs.options['lfstrack'] = _trackedmatcher(self)
216 return super(lfsrepo, self).commitctx(ctx, error)
216 return super(lfsrepo, self).commitctx(ctx, error)
217
217
218 repo.__class__ = lfsrepo
218 repo.__class__ = lfsrepo
219
219
220 if 'lfs' not in repo.requirements:
220 if 'lfs' not in repo.requirements:
221 def checkrequireslfs(ui, repo, **kwargs):
221 def checkrequireslfs(ui, repo, **kwargs):
222 if 'lfs' not in repo.requirements:
222 if 'lfs' not in repo.requirements:
223 last = kwargs.get(r'node_last')
223 last = kwargs.get(r'node_last')
224 _bin = node.bin
224 _bin = node.bin
225 if last:
225 if last:
226 s = repo.set('%n:%n', _bin(kwargs[r'node']), _bin(last))
226 s = repo.set('%n:%n', _bin(kwargs[r'node']), _bin(last))
227 else:
227 else:
228 s = repo.set('%n', _bin(kwargs[r'node']))
228 s = repo.set('%n', _bin(kwargs[r'node']))
229 match = repo.narrowmatch()
229 for ctx in s:
230 for ctx in s:
230 # TODO: is there a way to just walk the files in the commit?
231 # TODO: is there a way to just walk the files in the commit?
231 if any(ctx[f].islfs() for f in ctx.files() if f in ctx):
232 if any(ctx[f].islfs() for f in ctx.files()
233 if f in ctx and match(f)):
232 repo.requirements.add('lfs')
234 repo.requirements.add('lfs')
233 repo._writerequirements()
235 repo._writerequirements()
234 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
236 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
235 break
237 break
236
238
237 ui.setconfig('hooks', 'commit.lfs', checkrequireslfs, 'lfs')
239 ui.setconfig('hooks', 'commit.lfs', checkrequireslfs, 'lfs')
238 ui.setconfig('hooks', 'pretxnchangegroup.lfs', checkrequireslfs, 'lfs')
240 ui.setconfig('hooks', 'pretxnchangegroup.lfs', checkrequireslfs, 'lfs')
239 else:
241 else:
240 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
242 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
241
243
242 def _trackedmatcher(repo):
244 def _trackedmatcher(repo):
243 """Return a function (path, size) -> bool indicating whether or not to
245 """Return a function (path, size) -> bool indicating whether or not to
244 track a given file with lfs."""
246 track a given file with lfs."""
245 if not repo.wvfs.exists('.hglfs'):
247 if not repo.wvfs.exists('.hglfs'):
246 # No '.hglfs' in wdir. Fallback to config for now.
248 # No '.hglfs' in wdir. Fallback to config for now.
247 trackspec = repo.ui.config('lfs', 'track')
249 trackspec = repo.ui.config('lfs', 'track')
248
250
249 # deprecated config: lfs.threshold
251 # deprecated config: lfs.threshold
250 threshold = repo.ui.configbytes('lfs', 'threshold')
252 threshold = repo.ui.configbytes('lfs', 'threshold')
251 if threshold:
253 if threshold:
252 fileset.parse(trackspec) # make sure syntax errors are confined
254 fileset.parse(trackspec) # make sure syntax errors are confined
253 trackspec = "(%s) | size('>%d')" % (trackspec, threshold)
255 trackspec = "(%s) | size('>%d')" % (trackspec, threshold)
254
256
255 return minifileset.compile(trackspec)
257 return minifileset.compile(trackspec)
256
258
257 data = repo.wvfs.tryread('.hglfs')
259 data = repo.wvfs.tryread('.hglfs')
258 if not data:
260 if not data:
259 return lambda p, s: False
261 return lambda p, s: False
260
262
261 # Parse errors here will abort with a message that points to the .hglfs file
263 # Parse errors here will abort with a message that points to the .hglfs file
262 # and line number.
264 # and line number.
263 cfg = config.config()
265 cfg = config.config()
264 cfg.parse('.hglfs', data)
266 cfg.parse('.hglfs', data)
265
267
266 try:
268 try:
267 rules = [(minifileset.compile(pattern), minifileset.compile(rule))
269 rules = [(minifileset.compile(pattern), minifileset.compile(rule))
268 for pattern, rule in cfg.items('track')]
270 for pattern, rule in cfg.items('track')]
269 except error.ParseError as e:
271 except error.ParseError as e:
270 # The original exception gives no indicator that the error is in the
272 # The original exception gives no indicator that the error is in the
271 # .hglfs file, so add that.
273 # .hglfs file, so add that.
272
274
273 # TODO: See if the line number of the file can be made available.
275 # TODO: See if the line number of the file can be made available.
274 raise error.Abort(_('parse error in .hglfs: %s') % e)
276 raise error.Abort(_('parse error in .hglfs: %s') % e)
275
277
276 def _match(path, size):
278 def _match(path, size):
277 for pat, rule in rules:
279 for pat, rule in rules:
278 if pat(path, size):
280 if pat(path, size):
279 return rule(path, size)
281 return rule(path, size)
280
282
281 return False
283 return False
282
284
283 return _match
285 return _match
284
286
285 def wrapfilelog(filelog):
287 def wrapfilelog(filelog):
286 wrapfunction = extensions.wrapfunction
288 wrapfunction = extensions.wrapfunction
287
289
288 wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
290 wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
289 wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
291 wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
290 wrapfunction(filelog, 'size', wrapper.filelogsize)
292 wrapfunction(filelog, 'size', wrapper.filelogsize)
291
293
292 def extsetup(ui):
294 def extsetup(ui):
293 wrapfilelog(filelog.filelog)
295 wrapfilelog(filelog.filelog)
294
296
295 wrapfunction = extensions.wrapfunction
297 wrapfunction = extensions.wrapfunction
296
298
297 wrapfunction(cmdutil, '_updatecatformatter', wrapper._updatecatformatter)
299 wrapfunction(cmdutil, '_updatecatformatter', wrapper._updatecatformatter)
298 wrapfunction(scmutil, 'wrapconvertsink', wrapper.convertsink)
300 wrapfunction(scmutil, 'wrapconvertsink', wrapper.convertsink)
299
301
300 wrapfunction(upgrade, '_finishdatamigration',
302 wrapfunction(upgrade, '_finishdatamigration',
301 wrapper.upgradefinishdatamigration)
303 wrapper.upgradefinishdatamigration)
302
304
303 wrapfunction(upgrade, 'preservedrequirements',
305 wrapfunction(upgrade, 'preservedrequirements',
304 wrapper.upgraderequirements)
306 wrapper.upgraderequirements)
305
307
306 wrapfunction(upgrade, 'supporteddestrequirements',
308 wrapfunction(upgrade, 'supporteddestrequirements',
307 wrapper.upgraderequirements)
309 wrapper.upgraderequirements)
308
310
309 wrapfunction(changegroup,
311 wrapfunction(changegroup,
310 'allsupportedversions',
312 'allsupportedversions',
311 wrapper.allsupportedversions)
313 wrapper.allsupportedversions)
312
314
313 wrapfunction(exchange, 'push', wrapper.push)
315 wrapfunction(exchange, 'push', wrapper.push)
314 wrapfunction(wireproto, '_capabilities', wrapper._capabilities)
316 wrapfunction(wireproto, '_capabilities', wrapper._capabilities)
315
317
316 wrapfunction(context.basefilectx, 'cmp', wrapper.filectxcmp)
318 wrapfunction(context.basefilectx, 'cmp', wrapper.filectxcmp)
317 wrapfunction(context.basefilectx, 'isbinary', wrapper.filectxisbinary)
319 wrapfunction(context.basefilectx, 'isbinary', wrapper.filectxisbinary)
318 context.basefilectx.islfs = wrapper.filectxislfs
320 context.basefilectx.islfs = wrapper.filectxislfs
319
321
320 revlog.addflagprocessor(
322 revlog.addflagprocessor(
321 revlog.REVIDX_EXTSTORED,
323 revlog.REVIDX_EXTSTORED,
322 (
324 (
323 wrapper.readfromstore,
325 wrapper.readfromstore,
324 wrapper.writetostore,
326 wrapper.writetostore,
325 wrapper.bypasscheckhash,
327 wrapper.bypasscheckhash,
326 ),
328 ),
327 )
329 )
328
330
329 wrapfunction(hg, 'clone', wrapper.hgclone)
331 wrapfunction(hg, 'clone', wrapper.hgclone)
330 wrapfunction(hg, 'postshare', wrapper.hgpostshare)
332 wrapfunction(hg, 'postshare', wrapper.hgpostshare)
331
333
332 scmutil.fileprefetchhooks.add('lfs', wrapper._prefetchfiles)
334 scmutil.fileprefetchhooks.add('lfs', wrapper._prefetchfiles)
333
335
334 # Make bundle choose changegroup3 instead of changegroup2. This affects
336 # Make bundle choose changegroup3 instead of changegroup2. This affects
335 # "hg bundle" command. Note: it does not cover all bundle formats like
337 # "hg bundle" command. Note: it does not cover all bundle formats like
336 # "packed1". Using "packed1" with lfs will likely cause trouble.
338 # "packed1". Using "packed1" with lfs will likely cause trouble.
337 names = [k for k, v in exchange._bundlespeccgversions.items() if v == '02']
339 names = [k for k, v in exchange._bundlespeccgversions.items() if v == '02']
338 for k in names:
340 for k in names:
339 exchange._bundlespeccgversions[k] = '03'
341 exchange._bundlespeccgversions[k] = '03'
340
342
341 # bundlerepo uses "vfsmod.readonlyvfs(othervfs)", we need to make sure lfs
343 # bundlerepo uses "vfsmod.readonlyvfs(othervfs)", we need to make sure lfs
342 # options and blob stores are passed from othervfs to the new readonlyvfs.
344 # options and blob stores are passed from othervfs to the new readonlyvfs.
343 wrapfunction(vfsmod.readonlyvfs, '__init__', wrapper.vfsinit)
345 wrapfunction(vfsmod.readonlyvfs, '__init__', wrapper.vfsinit)
344
346
345 # when writing a bundle via "hg bundle" command, upload related LFS blobs
347 # when writing a bundle via "hg bundle" command, upload related LFS blobs
346 wrapfunction(bundle2, 'writenewbundle', wrapper.writenewbundle)
348 wrapfunction(bundle2, 'writenewbundle', wrapper.writenewbundle)
347
349
348 @filesetpredicate('lfs()', callstatus=True)
350 @filesetpredicate('lfs()', callstatus=True)
349 def lfsfileset(mctx, x):
351 def lfsfileset(mctx, x):
350 """File that uses LFS storage."""
352 """File that uses LFS storage."""
351 # i18n: "lfs" is a keyword
353 # i18n: "lfs" is a keyword
352 fileset.getargs(x, 0, 0, _("lfs takes no arguments"))
354 fileset.getargs(x, 0, 0, _("lfs takes no arguments"))
353 return [f for f in mctx.subset
355 return [f for f in mctx.subset
354 if wrapper.pointerfromctx(mctx.ctx, f, removed=True) is not None]
356 if wrapper.pointerfromctx(mctx.ctx, f, removed=True) is not None]
355
357
356 @templatekeyword('lfs_files', requires={'ctx'})
358 @templatekeyword('lfs_files', requires={'ctx'})
357 def lfsfiles(context, mapping):
359 def lfsfiles(context, mapping):
358 """List of strings. All files modified, added, or removed by this
360 """List of strings. All files modified, added, or removed by this
359 changeset."""
361 changeset."""
360 ctx = context.resource(mapping, 'ctx')
362 ctx = context.resource(mapping, 'ctx')
361
363
362 pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer}
364 pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer}
363 files = sorted(pointers.keys())
365 files = sorted(pointers.keys())
364
366
365 def pointer(v):
367 def pointer(v):
366 # In the file spec, version is first and the other keys are sorted.
368 # In the file spec, version is first and the other keys are sorted.
367 sortkeyfunc = lambda x: (x[0] != 'version', x)
369 sortkeyfunc = lambda x: (x[0] != 'version', x)
368 items = sorted(pointers[v].iteritems(), key=sortkeyfunc)
370 items = sorted(pointers[v].iteritems(), key=sortkeyfunc)
369 return util.sortdict(items)
371 return util.sortdict(items)
370
372
371 makemap = lambda v: {
373 makemap = lambda v: {
372 'file': v,
374 'file': v,
373 'lfsoid': pointers[v].oid() if pointers[v] else None,
375 'lfsoid': pointers[v].oid() if pointers[v] else None,
374 'lfspointer': templateutil.hybriddict(pointer(v)),
376 'lfspointer': templateutil.hybriddict(pointer(v)),
375 }
377 }
376
378
377 # TODO: make the separator ', '?
379 # TODO: make the separator ', '?
378 f = templateutil._showcompatlist(context, mapping, 'lfs_file', files)
380 f = templateutil._showcompatlist(context, mapping, 'lfs_file', files)
379 return templateutil.hybrid(f, files, makemap, pycompat.identity)
381 return templateutil.hybrid(f, files, makemap, pycompat.identity)
380
382
381 @command('debuglfsupload',
383 @command('debuglfsupload',
382 [('r', 'rev', [], _('upload large files introduced by REV'))])
384 [('r', 'rev', [], _('upload large files introduced by REV'))])
383 def debuglfsupload(ui, repo, **opts):
385 def debuglfsupload(ui, repo, **opts):
384 """upload lfs blobs added by the working copy parent or given revisions"""
386 """upload lfs blobs added by the working copy parent or given revisions"""
385 revs = opts.get(r'rev', [])
387 revs = opts.get(r'rev', [])
386 pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
388 pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
387 wrapper.uploadblobs(repo, pointers)
389 wrapper.uploadblobs(repo, pointers)
@@ -1,102 +1,106 b''
1 #testcases flat tree
1 #testcases flat tree
2
2
3 $ . "$TESTDIR/narrow-library.sh"
3 $ . "$TESTDIR/narrow-library.sh"
4
4
5 #if tree
5 #if tree
6 $ cat << EOF >> $HGRCPATH
6 $ cat << EOF >> $HGRCPATH
7 > [experimental]
7 > [experimental]
8 > treemanifest = 1
8 > treemanifest = 1
9 > EOF
9 > EOF
10 #endif
10 #endif
11
11
12 create full repo
12 create full repo
13
13
14 $ hg init master
14 $ hg init master
15 $ cd master
15 $ cd master
16
16
17 $ mkdir inside
17 $ mkdir inside
18 $ echo inside > inside/f1
18 $ echo inside > inside/f1
19 $ mkdir outside
19 $ mkdir outside
20 $ echo outside > outside/f1
20 $ echo outside > outside/f1
21 $ hg ci -Aqm 'initial'
21 $ hg ci -Aqm 'initial'
22
22
23 $ echo modified > inside/f1
23 $ echo modified > inside/f1
24 $ hg ci -qm 'modify inside'
24 $ hg ci -qm 'modify inside'
25
25
26 $ echo modified > outside/f1
26 $ echo modified > outside/f1
27 $ hg ci -qm 'modify outside'
27 $ hg ci -qm 'modify outside'
28
28
29 $ cd ..
29 $ cd ..
30
30
31 $ hg clone --narrow ssh://user@dummy/master narrow --include inside
31 (The lfs extension does nothing here, but this test ensures that its hook that
32 determines whether to add the lfs requirement, respects the narrow boundaries.)
33
34 $ hg --config extensions.lfs= clone --narrow ssh://user@dummy/master narrow \
35 > --include inside
32 requesting all changes
36 requesting all changes
33 adding changesets
37 adding changesets
34 adding manifests
38 adding manifests
35 adding file changes
39 adding file changes
36 added 3 changesets with 2 changes to 1 files
40 added 3 changesets with 2 changes to 1 files
37 new changesets *:* (glob)
41 new changesets *:* (glob)
38 updating to branch default
42 updating to branch default
39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 $ cd narrow
44 $ cd narrow
41
45
42 $ hg update -q 0
46 $ hg update -q 0
43
47
44 Can not modify dirstate outside
48 Can not modify dirstate outside
45
49
46 $ mkdir outside
50 $ mkdir outside
47 $ touch outside/f1
51 $ touch outside/f1
48 $ hg debugwalk -I 'relglob:f1'
52 $ hg debugwalk -I 'relglob:f1'
49 matcher: <includematcher includes='(?:(?:|.*/)f1(?:/|$))'>
53 matcher: <includematcher includes='(?:(?:|.*/)f1(?:/|$))'>
50 f inside/f1 inside/f1
54 f inside/f1 inside/f1
51 $ hg add outside/f1
55 $ hg add outside/f1
52 abort: cannot track 'outside/f1' - it is outside the narrow clone
56 abort: cannot track 'outside/f1' - it is outside the narrow clone
53 [255]
57 [255]
54 $ touch outside/f3
58 $ touch outside/f3
55 $ hg add outside/f3
59 $ hg add outside/f3
56 abort: cannot track 'outside/f3' - it is outside the narrow clone
60 abort: cannot track 'outside/f3' - it is outside the narrow clone
57 [255]
61 [255]
58
62
59 But adding a truly excluded file shouldn't count
63 But adding a truly excluded file shouldn't count
60
64
61 $ hg add outside/f3 -X outside/f3
65 $ hg add outside/f3 -X outside/f3
62
66
63 $ rm -r outside
67 $ rm -r outside
64
68
65 Can modify dirstate inside
69 Can modify dirstate inside
66
70
67 $ echo modified > inside/f1
71 $ echo modified > inside/f1
68 $ touch inside/f3
72 $ touch inside/f3
69 $ hg add inside/f3
73 $ hg add inside/f3
70 $ hg status
74 $ hg status
71 M inside/f1
75 M inside/f1
72 A inside/f3
76 A inside/f3
73 $ hg revert -qC .
77 $ hg revert -qC .
74 $ rm inside/f3
78 $ rm inside/f3
75
79
76 Can commit changes inside. Leaves outside unchanged.
80 Can commit changes inside. Leaves outside unchanged.
77
81
78 $ hg update -q 'desc("initial")'
82 $ hg update -q 'desc("initial")'
79 $ echo modified2 > inside/f1
83 $ echo modified2 > inside/f1
80 $ hg manifest --debug
84 $ hg manifest --debug
81 4d6a634d5ba06331a60c29ee0db8412490a54fcd 644 inside/f1
85 4d6a634d5ba06331a60c29ee0db8412490a54fcd 644 inside/f1
82 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
86 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
83 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
87 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
84 $ hg commit -m 'modify inside/f1'
88 $ hg commit -m 'modify inside/f1'
85 created new head
89 created new head
86 $ hg files -r .
90 $ hg files -r .
87 inside/f1
91 inside/f1
88 outside/f1 (flat !)
92 outside/f1 (flat !)
89 outside/ (tree !)
93 outside/ (tree !)
90 $ hg manifest --debug
94 $ hg manifest --debug
91 3f4197b4a11b9016e77ebc47fe566944885fd11b 644 inside/f1
95 3f4197b4a11b9016e77ebc47fe566944885fd11b 644 inside/f1
92 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
96 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
93 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
97 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
94 Some filesystems (notably FAT/exFAT only store timestamps with 2
98 Some filesystems (notably FAT/exFAT only store timestamps with 2
95 seconds of precision, so by sleeping for 3 seconds, we can ensure that
99 seconds of precision, so by sleeping for 3 seconds, we can ensure that
96 the timestamps of files stored by dirstate will appear older than the
100 the timestamps of files stored by dirstate will appear older than the
97 dirstate file, and therefore we'll be able to get stable output from
101 dirstate file, and therefore we'll be able to get stable output from
98 debugdirstate. If we don't do this, the test can be slightly flaky.
102 debugdirstate. If we don't do this, the test can be slightly flaky.
99 $ sleep 3
103 $ sleep 3
100 $ hg status
104 $ hg status
101 $ hg debugdirstate --nodates
105 $ hg debugdirstate --nodates
102 n 644 10 set inside/f1
106 n 644 10 set inside/f1
General Comments 0
You need to be logged in to leave comments. Login now