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