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