##// END OF EJS Templates
lfs: dedent documentation section about .hglfs file...
Denis Laxalde -
r43597:26caf96a default
parent child Browse files
Show More
@@ -1,406 +1,404 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 The extension reads its configuration from a versioned ``.hglfs``
57 configuration file found in the root of the working directory. The
58 ``.hglfs`` file uses the same syntax as all other Mercurial
59 configuration files. It uses a single section, ``[track]``.
57
60
58 The extension reads its configuration from a versioned ``.hglfs``
61 The ``[track]`` section specifies which files are stored as LFS (or
59 configuration file found in the root of the working directory. The
62 not). Each line is keyed by a file pattern, with a predicate value.
60 ``.hglfs`` file uses the same syntax as all other Mercurial
63 The first file pattern match is used, so put more specific patterns
61 configuration files. It uses a single section, ``[track]``.
64 first. The available predicates are ``all()``, ``none()``, and
62
65 ``size()``. See "hg help filesets.size" for the latter.
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.
65 The first file pattern match is used, so put more specific patterns
66 first. The available predicates are ``all()``, ``none()``, and
67 ``size()``. See "hg help filesets.size" for the latter.
68
66
69 Example versioned ``.hglfs`` file::
67 Example versioned ``.hglfs`` file::
70
68
71 [track]
69 [track]
72 # No Makefile or python file, anywhere, will be LFS
70 # No Makefile or python file, anywhere, will be LFS
73 **Makefile = none()
71 **Makefile = none()
74 **.py = none()
72 **.py = none()
75
73
76 **.zip = all()
74 **.zip = all()
77 **.exe = size(">1MB")
75 **.exe = size(">1MB")
78
76
79 # Catchall for everything not matched above
77 # Catchall for everything not matched above
80 ** = size(">10MB")
78 ** = size(">10MB")
81
79
82 Configs::
80 Configs::
83
81
84 [lfs]
82 [lfs]
85 # Remote endpoint. Multiple protocols are supported:
83 # Remote endpoint. Multiple protocols are supported:
86 # - http(s)://user:pass@example.com/path
84 # - http(s)://user:pass@example.com/path
87 # git-lfs endpoint
85 # git-lfs endpoint
88 # - file:///tmp/path
86 # - file:///tmp/path
89 # local filesystem, usually for testing
87 # local filesystem, usually for testing
90 # if unset, lfs will assume the remote repository also handles blob storage
88 # if unset, lfs will assume the remote repository also handles blob storage
91 # for http(s) URLs. Otherwise, lfs will prompt to set this when it must
89 # for http(s) URLs. Otherwise, lfs will prompt to set this when it must
92 # use this value.
90 # use this value.
93 # (default: unset)
91 # (default: unset)
94 url = https://example.com/repo.git/info/lfs
92 url = https://example.com/repo.git/info/lfs
95
93
96 # 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
97 # extensions, and "path:under/some/directory" for path prefix. Both
95 # extensions, and "path:under/some/directory" for path prefix. Both
98 # are relative to the repository root.
96 # are relative to the repository root.
99 # 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
100 # joined with fileset operators. (See "hg help filesets.operators".)
98 # joined with fileset operators. (See "hg help filesets.operators".)
101 #
99 #
102 # Some examples:
100 # Some examples:
103 # - all() # everything
101 # - all() # everything
104 # - none() # nothing
102 # - none() # nothing
105 # - size(">20MB") # larger than 20MB
103 # - size(">20MB") # larger than 20MB
106 # - !**.txt # anything not a *.txt file
104 # - !**.txt # anything not a *.txt file
107 # - **.zip | **.tar.gz | **.7z # some types of compressed files
105 # - **.zip | **.tar.gz | **.7z # some types of compressed files
108 # - path:bin # files under "bin" in the project root
106 # - path:bin # files under "bin" in the project root
109 # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz
107 # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz
110 # | (path:bin & !path:/bin/README) | size(">1GB")
108 # | (path:bin & !path:/bin/README) | size(">1GB")
111 # (default: none())
109 # (default: none())
112 #
110 #
113 # 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
114 # will eventually be deprecated and removed.
112 # will eventually be deprecated and removed.
115 track = size(">10M")
113 track = size(">10M")
116
114
117 # 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
118 retry = 5
116 retry = 5
119
117
120 # 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.
121 # 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.
122 usercache = /path/to/global/cache
120 usercache = /path/to/global/cache
123 """
121 """
124
122
125 from __future__ import absolute_import
123 from __future__ import absolute_import
126
124
127 import sys
125 import sys
128
126
129 from mercurial.i18n import _
127 from mercurial.i18n import _
130
128
131 from mercurial import (
129 from mercurial import (
132 config,
130 config,
133 context,
131 context,
134 error,
132 error,
135 exchange,
133 exchange,
136 extensions,
134 extensions,
137 exthelper,
135 exthelper,
138 filelog,
136 filelog,
139 filesetlang,
137 filesetlang,
140 localrepo,
138 localrepo,
141 minifileset,
139 minifileset,
142 node,
140 node,
143 pycompat,
141 pycompat,
144 revlog,
142 revlog,
145 scmutil,
143 scmutil,
146 templateutil,
144 templateutil,
147 util,
145 util,
148 )
146 )
149
147
150 from mercurial.interfaces import repository
148 from mercurial.interfaces import repository
151
149
152 from . import (
150 from . import (
153 blobstore,
151 blobstore,
154 wireprotolfsserver,
152 wireprotolfsserver,
155 wrapper,
153 wrapper,
156 )
154 )
157
155
158 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
156 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
159 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
160 # be specifying the version(s) of Mercurial they are tested with, or
158 # be specifying the version(s) of Mercurial they are tested with, or
161 # leave the attribute unspecified.
159 # leave the attribute unspecified.
162 testedwith = b'ships-with-hg-core'
160 testedwith = b'ships-with-hg-core'
163
161
164 eh = exthelper.exthelper()
162 eh = exthelper.exthelper()
165 eh.merge(wrapper.eh)
163 eh.merge(wrapper.eh)
166 eh.merge(wireprotolfsserver.eh)
164 eh.merge(wireprotolfsserver.eh)
167
165
168 cmdtable = eh.cmdtable
166 cmdtable = eh.cmdtable
169 configtable = eh.configtable
167 configtable = eh.configtable
170 extsetup = eh.finalextsetup
168 extsetup = eh.finalextsetup
171 uisetup = eh.finaluisetup
169 uisetup = eh.finaluisetup
172 filesetpredicate = eh.filesetpredicate
170 filesetpredicate = eh.filesetpredicate
173 reposetup = eh.finalreposetup
171 reposetup = eh.finalreposetup
174 templatekeyword = eh.templatekeyword
172 templatekeyword = eh.templatekeyword
175
173
176 eh.configitem(
174 eh.configitem(
177 b'experimental', b'lfs.serve', default=True,
175 b'experimental', b'lfs.serve', default=True,
178 )
176 )
179 eh.configitem(
177 eh.configitem(
180 b'experimental', b'lfs.user-agent', default=None,
178 b'experimental', b'lfs.user-agent', default=None,
181 )
179 )
182 eh.configitem(
180 eh.configitem(
183 b'experimental', b'lfs.disableusercache', default=False,
181 b'experimental', b'lfs.disableusercache', default=False,
184 )
182 )
185 eh.configitem(
183 eh.configitem(
186 b'experimental', b'lfs.worker-enable', default=False,
184 b'experimental', b'lfs.worker-enable', default=False,
187 )
185 )
188
186
189 eh.configitem(
187 eh.configitem(
190 b'lfs', b'url', default=None,
188 b'lfs', b'url', default=None,
191 )
189 )
192 eh.configitem(
190 eh.configitem(
193 b'lfs', b'usercache', default=None,
191 b'lfs', b'usercache', default=None,
194 )
192 )
195 # Deprecated
193 # Deprecated
196 eh.configitem(
194 eh.configitem(
197 b'lfs', b'threshold', default=None,
195 b'lfs', b'threshold', default=None,
198 )
196 )
199 eh.configitem(
197 eh.configitem(
200 b'lfs', b'track', default=b'none()',
198 b'lfs', b'track', default=b'none()',
201 )
199 )
202 eh.configitem(
200 eh.configitem(
203 b'lfs', b'retry', default=5,
201 b'lfs', b'retry', default=5,
204 )
202 )
205
203
206 lfsprocessor = (
204 lfsprocessor = (
207 wrapper.readfromstore,
205 wrapper.readfromstore,
208 wrapper.writetostore,
206 wrapper.writetostore,
209 wrapper.bypasscheckhash,
207 wrapper.bypasscheckhash,
210 )
208 )
211
209
212
210
213 def featuresetup(ui, supported):
211 def featuresetup(ui, supported):
214 # don't die on seeing a repo with the lfs requirement
212 # don't die on seeing a repo with the lfs requirement
215 supported |= {b'lfs'}
213 supported |= {b'lfs'}
216
214
217
215
218 @eh.uisetup
216 @eh.uisetup
219 def _uisetup(ui):
217 def _uisetup(ui):
220 localrepo.featuresetupfuncs.add(featuresetup)
218 localrepo.featuresetupfuncs.add(featuresetup)
221
219
222
220
223 @eh.reposetup
221 @eh.reposetup
224 def _reposetup(ui, repo):
222 def _reposetup(ui, repo):
225 # Nothing to do with a remote repo
223 # Nothing to do with a remote repo
226 if not repo.local():
224 if not repo.local():
227 return
225 return
228
226
229 repo.svfs.lfslocalblobstore = blobstore.local(repo)
227 repo.svfs.lfslocalblobstore = blobstore.local(repo)
230 repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
228 repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
231
229
232 class lfsrepo(repo.__class__):
230 class lfsrepo(repo.__class__):
233 @localrepo.unfilteredmethod
231 @localrepo.unfilteredmethod
234 def commitctx(self, ctx, error=False, origctx=None):
232 def commitctx(self, ctx, error=False, origctx=None):
235 repo.svfs.options[b'lfstrack'] = _trackedmatcher(self)
233 repo.svfs.options[b'lfstrack'] = _trackedmatcher(self)
236 return super(lfsrepo, self).commitctx(ctx, error, origctx=origctx)
234 return super(lfsrepo, self).commitctx(ctx, error, origctx=origctx)
237
235
238 repo.__class__ = lfsrepo
236 repo.__class__ = lfsrepo
239
237
240 if b'lfs' not in repo.requirements:
238 if b'lfs' not in repo.requirements:
241
239
242 def checkrequireslfs(ui, repo, **kwargs):
240 def checkrequireslfs(ui, repo, **kwargs):
243 if b'lfs' in repo.requirements:
241 if b'lfs' in repo.requirements:
244 return 0
242 return 0
245
243
246 last = kwargs.get(r'node_last')
244 last = kwargs.get(r'node_last')
247 _bin = node.bin
245 _bin = node.bin
248 if last:
246 if last:
249 s = repo.set(b'%n:%n', _bin(kwargs[r'node']), _bin(last))
247 s = repo.set(b'%n:%n', _bin(kwargs[r'node']), _bin(last))
250 else:
248 else:
251 s = repo.set(b'%n', _bin(kwargs[r'node']))
249 s = repo.set(b'%n', _bin(kwargs[r'node']))
252 match = repo._storenarrowmatch
250 match = repo._storenarrowmatch
253 for ctx in s:
251 for ctx in s:
254 # TODO: is there a way to just walk the files in the commit?
252 # TODO: is there a way to just walk the files in the commit?
255 if any(
253 if any(
256 ctx[f].islfs() for f in ctx.files() if f in ctx and match(f)
254 ctx[f].islfs() for f in ctx.files() if f in ctx and match(f)
257 ):
255 ):
258 repo.requirements.add(b'lfs')
256 repo.requirements.add(b'lfs')
259 repo.features.add(repository.REPO_FEATURE_LFS)
257 repo.features.add(repository.REPO_FEATURE_LFS)
260 repo._writerequirements()
258 repo._writerequirements()
261 repo.prepushoutgoinghooks.add(b'lfs', wrapper.prepush)
259 repo.prepushoutgoinghooks.add(b'lfs', wrapper.prepush)
262 break
260 break
263
261
264 ui.setconfig(b'hooks', b'commit.lfs', checkrequireslfs, b'lfs')
262 ui.setconfig(b'hooks', b'commit.lfs', checkrequireslfs, b'lfs')
265 ui.setconfig(
263 ui.setconfig(
266 b'hooks', b'pretxnchangegroup.lfs', checkrequireslfs, b'lfs'
264 b'hooks', b'pretxnchangegroup.lfs', checkrequireslfs, b'lfs'
267 )
265 )
268 else:
266 else:
269 repo.prepushoutgoinghooks.add(b'lfs', wrapper.prepush)
267 repo.prepushoutgoinghooks.add(b'lfs', wrapper.prepush)
270
268
271
269
272 def _trackedmatcher(repo):
270 def _trackedmatcher(repo):
273 """Return a function (path, size) -> bool indicating whether or not to
271 """Return a function (path, size) -> bool indicating whether or not to
274 track a given file with lfs."""
272 track a given file with lfs."""
275 if not repo.wvfs.exists(b'.hglfs'):
273 if not repo.wvfs.exists(b'.hglfs'):
276 # No '.hglfs' in wdir. Fallback to config for now.
274 # No '.hglfs' in wdir. Fallback to config for now.
277 trackspec = repo.ui.config(b'lfs', b'track')
275 trackspec = repo.ui.config(b'lfs', b'track')
278
276
279 # deprecated config: lfs.threshold
277 # deprecated config: lfs.threshold
280 threshold = repo.ui.configbytes(b'lfs', b'threshold')
278 threshold = repo.ui.configbytes(b'lfs', b'threshold')
281 if threshold:
279 if threshold:
282 filesetlang.parse(trackspec) # make sure syntax errors are confined
280 filesetlang.parse(trackspec) # make sure syntax errors are confined
283 trackspec = b"(%s) | size('>%d')" % (trackspec, threshold)
281 trackspec = b"(%s) | size('>%d')" % (trackspec, threshold)
284
282
285 return minifileset.compile(trackspec)
283 return minifileset.compile(trackspec)
286
284
287 data = repo.wvfs.tryread(b'.hglfs')
285 data = repo.wvfs.tryread(b'.hglfs')
288 if not data:
286 if not data:
289 return lambda p, s: False
287 return lambda p, s: False
290
288
291 # Parse errors here will abort with a message that points to the .hglfs file
289 # Parse errors here will abort with a message that points to the .hglfs file
292 # and line number.
290 # and line number.
293 cfg = config.config()
291 cfg = config.config()
294 cfg.parse(b'.hglfs', data)
292 cfg.parse(b'.hglfs', data)
295
293
296 try:
294 try:
297 rules = [
295 rules = [
298 (minifileset.compile(pattern), minifileset.compile(rule))
296 (minifileset.compile(pattern), minifileset.compile(rule))
299 for pattern, rule in cfg.items(b'track')
297 for pattern, rule in cfg.items(b'track')
300 ]
298 ]
301 except error.ParseError as e:
299 except error.ParseError as e:
302 # The original exception gives no indicator that the error is in the
300 # The original exception gives no indicator that the error is in the
303 # .hglfs file, so add that.
301 # .hglfs file, so add that.
304
302
305 # TODO: See if the line number of the file can be made available.
303 # TODO: See if the line number of the file can be made available.
306 raise error.Abort(_(b'parse error in .hglfs: %s') % e)
304 raise error.Abort(_(b'parse error in .hglfs: %s') % e)
307
305
308 def _match(path, size):
306 def _match(path, size):
309 for pat, rule in rules:
307 for pat, rule in rules:
310 if pat(path, size):
308 if pat(path, size):
311 return rule(path, size)
309 return rule(path, size)
312
310
313 return False
311 return False
314
312
315 return _match
313 return _match
316
314
317
315
318 # Called by remotefilelog
316 # Called by remotefilelog
319 def wrapfilelog(filelog):
317 def wrapfilelog(filelog):
320 wrapfunction = extensions.wrapfunction
318 wrapfunction = extensions.wrapfunction
321
319
322 wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
320 wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
323 wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
321 wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
324 wrapfunction(filelog, 'size', wrapper.filelogsize)
322 wrapfunction(filelog, 'size', wrapper.filelogsize)
325
323
326
324
327 @eh.wrapfunction(localrepo, b'resolverevlogstorevfsoptions')
325 @eh.wrapfunction(localrepo, b'resolverevlogstorevfsoptions')
328 def _resolverevlogstorevfsoptions(orig, ui, requirements, features):
326 def _resolverevlogstorevfsoptions(orig, ui, requirements, features):
329 opts = orig(ui, requirements, features)
327 opts = orig(ui, requirements, features)
330 for name, module in extensions.extensions(ui):
328 for name, module in extensions.extensions(ui):
331 if module is sys.modules[__name__]:
329 if module is sys.modules[__name__]:
332 if revlog.REVIDX_EXTSTORED in opts[b'flagprocessors']:
330 if revlog.REVIDX_EXTSTORED in opts[b'flagprocessors']:
333 msg = (
331 msg = (
334 _(b"cannot register multiple processors on flag '%#x'.")
332 _(b"cannot register multiple processors on flag '%#x'.")
335 % revlog.REVIDX_EXTSTORED
333 % revlog.REVIDX_EXTSTORED
336 )
334 )
337 raise error.Abort(msg)
335 raise error.Abort(msg)
338
336
339 opts[b'flagprocessors'][revlog.REVIDX_EXTSTORED] = lfsprocessor
337 opts[b'flagprocessors'][revlog.REVIDX_EXTSTORED] = lfsprocessor
340 break
338 break
341
339
342 return opts
340 return opts
343
341
344
342
345 @eh.extsetup
343 @eh.extsetup
346 def _extsetup(ui):
344 def _extsetup(ui):
347 wrapfilelog(filelog.filelog)
345 wrapfilelog(filelog.filelog)
348
346
349 context.basefilectx.islfs = wrapper.filectxislfs
347 context.basefilectx.islfs = wrapper.filectxislfs
350
348
351 scmutil.fileprefetchhooks.add(b'lfs', wrapper._prefetchfiles)
349 scmutil.fileprefetchhooks.add(b'lfs', wrapper._prefetchfiles)
352
350
353 # Make bundle choose changegroup3 instead of changegroup2. This affects
351 # Make bundle choose changegroup3 instead of changegroup2. This affects
354 # "hg bundle" command. Note: it does not cover all bundle formats like
352 # "hg bundle" command. Note: it does not cover all bundle formats like
355 # "packed1". Using "packed1" with lfs will likely cause trouble.
353 # "packed1". Using "packed1" with lfs will likely cause trouble.
356 exchange._bundlespeccontentopts[b"v2"][b"cg.version"] = b"03"
354 exchange._bundlespeccontentopts[b"v2"][b"cg.version"] = b"03"
357
355
358
356
359 @eh.filesetpredicate(b'lfs()')
357 @eh.filesetpredicate(b'lfs()')
360 def lfsfileset(mctx, x):
358 def lfsfileset(mctx, x):
361 """File that uses LFS storage."""
359 """File that uses LFS storage."""
362 # i18n: "lfs" is a keyword
360 # i18n: "lfs" is a keyword
363 filesetlang.getargs(x, 0, 0, _(b"lfs takes no arguments"))
361 filesetlang.getargs(x, 0, 0, _(b"lfs takes no arguments"))
364 ctx = mctx.ctx
362 ctx = mctx.ctx
365
363
366 def lfsfilep(f):
364 def lfsfilep(f):
367 return wrapper.pointerfromctx(ctx, f, removed=True) is not None
365 return wrapper.pointerfromctx(ctx, f, removed=True) is not None
368
366
369 return mctx.predicate(lfsfilep, predrepr=b'<lfs>')
367 return mctx.predicate(lfsfilep, predrepr=b'<lfs>')
370
368
371
369
372 @eh.templatekeyword(b'lfs_files', requires={b'ctx'})
370 @eh.templatekeyword(b'lfs_files', requires={b'ctx'})
373 def lfsfiles(context, mapping):
371 def lfsfiles(context, mapping):
374 """List of strings. All files modified, added, or removed by this
372 """List of strings. All files modified, added, or removed by this
375 changeset."""
373 changeset."""
376 ctx = context.resource(mapping, b'ctx')
374 ctx = context.resource(mapping, b'ctx')
377
375
378 pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer}
376 pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer}
379 files = sorted(pointers.keys())
377 files = sorted(pointers.keys())
380
378
381 def pointer(v):
379 def pointer(v):
382 # In the file spec, version is first and the other keys are sorted.
380 # In the file spec, version is first and the other keys are sorted.
383 sortkeyfunc = lambda x: (x[0] != b'version', x)
381 sortkeyfunc = lambda x: (x[0] != b'version', x)
384 items = sorted(pycompat.iteritems(pointers[v]), key=sortkeyfunc)
382 items = sorted(pycompat.iteritems(pointers[v]), key=sortkeyfunc)
385 return util.sortdict(items)
383 return util.sortdict(items)
386
384
387 makemap = lambda v: {
385 makemap = lambda v: {
388 b'file': v,
386 b'file': v,
389 b'lfsoid': pointers[v].oid() if pointers[v] else None,
387 b'lfsoid': pointers[v].oid() if pointers[v] else None,
390 b'lfspointer': templateutil.hybriddict(pointer(v)),
388 b'lfspointer': templateutil.hybriddict(pointer(v)),
391 }
389 }
392
390
393 # TODO: make the separator ', '?
391 # TODO: make the separator ', '?
394 f = templateutil._showcompatlist(context, mapping, b'lfs_file', files)
392 f = templateutil._showcompatlist(context, mapping, b'lfs_file', files)
395 return templateutil.hybrid(f, files, makemap, pycompat.identity)
393 return templateutil.hybrid(f, files, makemap, pycompat.identity)
396
394
397
395
398 @eh.command(
396 @eh.command(
399 b'debuglfsupload',
397 b'debuglfsupload',
400 [(b'r', b'rev', [], _(b'upload large files introduced by REV'))],
398 [(b'r', b'rev', [], _(b'upload large files introduced by REV'))],
401 )
399 )
402 def debuglfsupload(ui, repo, **opts):
400 def debuglfsupload(ui, repo, **opts):
403 """upload lfs blobs added by the working copy parent or given revisions"""
401 """upload lfs blobs added by the working copy parent or given revisions"""
404 revs = opts.get(r'rev', [])
402 revs = opts.get(r'rev', [])
405 pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
403 pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
406 wrapper.uploadblobs(repo, pointers)
404 wrapper.uploadblobs(repo, pointers)
General Comments 0
You need to be logged in to leave comments. Login now