##// END OF EJS Templates
largefiles: take lock before writing requirements...
marmoute -
r49510:497c2655 default
parent child Browse files
Show More
@@ -1,467 +1,468 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''setup for largefiles repositories: reposetup'''
10 10 from __future__ import absolute_import
11 11
12 12 import copy
13 13
14 14 from mercurial.i18n import _
15 15
16 16 from mercurial import (
17 17 error,
18 18 extensions,
19 19 localrepo,
20 20 match as matchmod,
21 21 scmutil,
22 22 util,
23 23 )
24 24
25 25 from mercurial.dirstateutils import timestamp
26 26
27 27 from . import (
28 28 lfcommands,
29 29 lfutil,
30 30 )
31 31
32 32
33 33 def reposetup(ui, repo):
34 34 # wire repositories should be given new wireproto functions
35 35 # by "proto.wirereposetup()" via "hg.wirepeersetupfuncs"
36 36 if not repo.local():
37 37 return
38 38
39 39 class lfilesrepo(repo.__class__):
40 40 # the mark to examine whether "repo" object enables largefiles or not
41 41 _largefilesenabled = True
42 42
43 43 lfstatus = False
44 44
45 45 # When lfstatus is set, return a context that gives the names
46 46 # of largefiles instead of their corresponding standins and
47 47 # identifies the largefiles as always binary, regardless of
48 48 # their actual contents.
49 49 def __getitem__(self, changeid):
50 50 ctx = super(lfilesrepo, self).__getitem__(changeid)
51 51 if self.lfstatus:
52 52
53 53 def files(orig):
54 54 filenames = orig()
55 55 return [lfutil.splitstandin(f) or f for f in filenames]
56 56
57 57 extensions.wrapfunction(ctx, 'files', files)
58 58
59 59 def manifest(orig):
60 60 man1 = orig()
61 61
62 62 class lfilesmanifest(man1.__class__):
63 63 def __contains__(self, filename):
64 64 orig = super(lfilesmanifest, self).__contains__
65 65 return orig(filename) or orig(
66 66 lfutil.standin(filename)
67 67 )
68 68
69 69 man1.__class__ = lfilesmanifest
70 70 return man1
71 71
72 72 extensions.wrapfunction(ctx, 'manifest', manifest)
73 73
74 74 def filectx(orig, path, fileid=None, filelog=None):
75 75 try:
76 76 if filelog is not None:
77 77 result = orig(path, fileid, filelog)
78 78 else:
79 79 result = orig(path, fileid)
80 80 except error.LookupError:
81 81 # Adding a null character will cause Mercurial to
82 82 # identify this as a binary file.
83 83 if filelog is not None:
84 84 result = orig(lfutil.standin(path), fileid, filelog)
85 85 else:
86 86 result = orig(lfutil.standin(path), fileid)
87 87 olddata = result.data
88 88 result.data = lambda: olddata() + b'\0'
89 89 return result
90 90
91 91 extensions.wrapfunction(ctx, 'filectx', filectx)
92 92
93 93 return ctx
94 94
95 95 # Figure out the status of big files and insert them into the
96 96 # appropriate list in the result. Also removes standin files
97 97 # from the listing. Revert to the original status if
98 98 # self.lfstatus is False.
99 99 # XXX large file status is buggy when used on repo proxy.
100 100 # XXX this needs to be investigated.
101 101 @localrepo.unfilteredmethod
102 102 def status(
103 103 self,
104 104 node1=b'.',
105 105 node2=None,
106 106 match=None,
107 107 ignored=False,
108 108 clean=False,
109 109 unknown=False,
110 110 listsubrepos=False,
111 111 ):
112 112 listignored, listclean, listunknown = ignored, clean, unknown
113 113 orig = super(lfilesrepo, self).status
114 114 if not self.lfstatus:
115 115 return orig(
116 116 node1,
117 117 node2,
118 118 match,
119 119 listignored,
120 120 listclean,
121 121 listunknown,
122 122 listsubrepos,
123 123 )
124 124
125 125 # some calls in this function rely on the old version of status
126 126 self.lfstatus = False
127 127 ctx1 = self[node1]
128 128 ctx2 = self[node2]
129 129 working = ctx2.rev() is None
130 130 parentworking = working and ctx1 == self[b'.']
131 131
132 132 if match is None:
133 133 match = matchmod.always()
134 134
135 135 try:
136 136 # updating the dirstate is optional
137 137 # so we don't wait on the lock
138 138 wlock = self.wlock(False)
139 139 gotlock = True
140 140 except error.LockError:
141 141 wlock = util.nullcontextmanager()
142 142 gotlock = False
143 143 with wlock:
144 144
145 145 # First check if paths or patterns were specified on the
146 146 # command line. If there were, and they don't match any
147 147 # largefiles, we should just bail here and let super
148 148 # handle it -- thus gaining a big performance boost.
149 149 lfdirstate = lfutil.openlfdirstate(ui, self)
150 150 if not match.always():
151 151 for f in lfdirstate:
152 152 if match(f):
153 153 break
154 154 else:
155 155 return orig(
156 156 node1,
157 157 node2,
158 158 match,
159 159 listignored,
160 160 listclean,
161 161 listunknown,
162 162 listsubrepos,
163 163 )
164 164
165 165 # Create a copy of match that matches standins instead
166 166 # of largefiles.
167 167 def tostandins(files):
168 168 if not working:
169 169 return files
170 170 newfiles = []
171 171 dirstate = self.dirstate
172 172 for f in files:
173 173 sf = lfutil.standin(f)
174 174 if sf in dirstate:
175 175 newfiles.append(sf)
176 176 elif dirstate.hasdir(sf):
177 177 # Directory entries could be regular or
178 178 # standin, check both
179 179 newfiles.extend((f, sf))
180 180 else:
181 181 newfiles.append(f)
182 182 return newfiles
183 183
184 184 m = copy.copy(match)
185 185 m._files = tostandins(m._files)
186 186
187 187 result = orig(
188 188 node1, node2, m, ignored, clean, unknown, listsubrepos
189 189 )
190 190 if working:
191 191
192 192 def sfindirstate(f):
193 193 sf = lfutil.standin(f)
194 194 dirstate = self.dirstate
195 195 return sf in dirstate or dirstate.hasdir(sf)
196 196
197 197 match._files = [f for f in match._files if sfindirstate(f)]
198 198 # Don't waste time getting the ignored and unknown
199 199 # files from lfdirstate
200 200 unsure, s, mtime_boundary = lfdirstate.status(
201 201 match,
202 202 subrepos=[],
203 203 ignored=False,
204 204 clean=listclean,
205 205 unknown=False,
206 206 )
207 207 (modified, added, removed, deleted, clean) = (
208 208 s.modified,
209 209 s.added,
210 210 s.removed,
211 211 s.deleted,
212 212 s.clean,
213 213 )
214 214 if parentworking:
215 215 wctx = repo[None]
216 216 for lfile in unsure:
217 217 standin = lfutil.standin(lfile)
218 218 if standin not in ctx1:
219 219 # from second parent
220 220 modified.append(lfile)
221 221 elif lfutil.readasstandin(
222 222 ctx1[standin]
223 223 ) != lfutil.hashfile(self.wjoin(lfile)):
224 224 modified.append(lfile)
225 225 else:
226 226 if listclean:
227 227 clean.append(lfile)
228 228 s = wctx[lfile].lstat()
229 229 mode = s.st_mode
230 230 size = s.st_size
231 231 mtime = timestamp.reliable_mtime_of(
232 232 s, mtime_boundary
233 233 )
234 234 if mtime is not None:
235 235 cache_data = (mode, size, mtime)
236 236 lfdirstate.set_clean(lfile, cache_data)
237 237 else:
238 238 tocheck = unsure + modified + added + clean
239 239 modified, added, clean = [], [], []
240 240 checkexec = self.dirstate._checkexec
241 241
242 242 for lfile in tocheck:
243 243 standin = lfutil.standin(lfile)
244 244 if standin in ctx1:
245 245 abslfile = self.wjoin(lfile)
246 246 if (
247 247 lfutil.readasstandin(ctx1[standin])
248 248 != lfutil.hashfile(abslfile)
249 249 ) or (
250 250 checkexec
251 251 and (b'x' in ctx1.flags(standin))
252 252 != bool(lfutil.getexecutable(abslfile))
253 253 ):
254 254 modified.append(lfile)
255 255 elif listclean:
256 256 clean.append(lfile)
257 257 else:
258 258 added.append(lfile)
259 259
260 260 # at this point, 'removed' contains largefiles
261 261 # marked as 'R' in the working context.
262 262 # then, largefiles not managed also in the target
263 263 # context should be excluded from 'removed'.
264 264 removed = [
265 265 lfile
266 266 for lfile in removed
267 267 if lfutil.standin(lfile) in ctx1
268 268 ]
269 269
270 270 # Standins no longer found in lfdirstate have been deleted
271 271 for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
272 272 lfile = lfutil.splitstandin(standin)
273 273 if not match(lfile):
274 274 continue
275 275 if lfile not in lfdirstate:
276 276 deleted.append(lfile)
277 277 # Sync "largefile has been removed" back to the
278 278 # standin. Removing a file as a side effect of
279 279 # running status is gross, but the alternatives (if
280 280 # any) are worse.
281 281 self.wvfs.unlinkpath(standin, ignoremissing=True)
282 282
283 283 # Filter result lists
284 284 result = list(result)
285 285
286 286 # Largefiles are not really removed when they're
287 287 # still in the normal dirstate. Likewise, normal
288 288 # files are not really removed if they are still in
289 289 # lfdirstate. This happens in merges where files
290 290 # change type.
291 291 removed = [f for f in removed if f not in self.dirstate]
292 292 result[2] = [f for f in result[2] if f not in lfdirstate]
293 293
294 294 lfiles = set(lfdirstate)
295 295 # Unknown files
296 296 result[4] = set(result[4]).difference(lfiles)
297 297 # Ignored files
298 298 result[5] = set(result[5]).difference(lfiles)
299 299 # combine normal files and largefiles
300 300 normals = [
301 301 [fn for fn in filelist if not lfutil.isstandin(fn)]
302 302 for filelist in result
303 303 ]
304 304 lfstatus = (
305 305 modified,
306 306 added,
307 307 removed,
308 308 deleted,
309 309 [],
310 310 [],
311 311 clean,
312 312 )
313 313 result = [
314 314 sorted(list1 + list2)
315 315 for (list1, list2) in zip(normals, lfstatus)
316 316 ]
317 317 else: # not against working directory
318 318 result = [
319 319 [lfutil.splitstandin(f) or f for f in items]
320 320 for items in result
321 321 ]
322 322
323 323 if gotlock:
324 324 lfdirstate.write(self.currenttransaction())
325 325
326 326 self.lfstatus = True
327 327 return scmutil.status(*result)
328 328
329 329 def commitctx(self, ctx, *args, **kwargs):
330 330 node = super(lfilesrepo, self).commitctx(ctx, *args, **kwargs)
331 331
332 332 class lfilesctx(ctx.__class__):
333 333 def markcommitted(self, node):
334 334 orig = super(lfilesctx, self).markcommitted
335 335 return lfutil.markcommitted(orig, self, node)
336 336
337 337 ctx.__class__ = lfilesctx
338 338 return node
339 339
340 340 # Before commit, largefile standins have not had their
341 341 # contents updated to reflect the hash of their largefile.
342 342 # Do that here.
343 343 def commit(
344 344 self,
345 345 text=b"",
346 346 user=None,
347 347 date=None,
348 348 match=None,
349 349 force=False,
350 350 editor=False,
351 351 extra=None,
352 352 ):
353 353 if extra is None:
354 354 extra = {}
355 355 orig = super(lfilesrepo, self).commit
356 356
357 357 with self.wlock():
358 358 lfcommithook = self._lfcommithooks[-1]
359 359 match = lfcommithook(self, match)
360 360 result = orig(
361 361 text=text,
362 362 user=user,
363 363 date=date,
364 364 match=match,
365 365 force=force,
366 366 editor=editor,
367 367 extra=extra,
368 368 )
369 369 return result
370 370
371 371 # TODO: _subdirlfs should be moved into "lfutil.py", because
372 372 # it is referred only from "lfutil.updatestandinsbymatch"
373 373 def _subdirlfs(self, files, lfiles):
374 374 """
375 375 Adjust matched file list
376 376 If we pass a directory to commit whose only committable files
377 377 are largefiles, the core commit code aborts before finding
378 378 the largefiles.
379 379 So we do the following:
380 380 For directories that only have largefiles as matches,
381 381 we explicitly add the largefiles to the match list and remove
382 382 the directory.
383 383 In other cases, we leave the match list unmodified.
384 384 """
385 385 actualfiles = []
386 386 dirs = []
387 387 regulars = []
388 388
389 389 for f in files:
390 390 if lfutil.isstandin(f + b'/'):
391 391 raise error.Abort(
392 392 _(b'file "%s" is a largefile standin') % f,
393 393 hint=b'commit the largefile itself instead',
394 394 )
395 395 # Scan directories
396 396 if self.wvfs.isdir(f):
397 397 dirs.append(f)
398 398 else:
399 399 regulars.append(f)
400 400
401 401 for f in dirs:
402 402 matcheddir = False
403 403 d = self.dirstate.normalize(f) + b'/'
404 404 # Check for matched normal files
405 405 for mf in regulars:
406 406 if self.dirstate.normalize(mf).startswith(d):
407 407 actualfiles.append(f)
408 408 matcheddir = True
409 409 break
410 410 if not matcheddir:
411 411 # If no normal match, manually append
412 412 # any matching largefiles
413 413 for lf in lfiles:
414 414 if self.dirstate.normalize(lf).startswith(d):
415 415 actualfiles.append(lf)
416 416 if not matcheddir:
417 417 # There may still be normal files in the dir, so
418 418 # add a directory to the list, which
419 419 # forces status/dirstate to walk all files and
420 420 # call the match function on the matcher, even
421 421 # on case sensitive filesystems.
422 422 actualfiles.append(b'.')
423 423 matcheddir = True
424 424 # Nothing in dir, so readd it
425 425 # and let commit reject it
426 426 if not matcheddir:
427 427 actualfiles.append(f)
428 428
429 429 # Always add normal files
430 430 actualfiles += regulars
431 431 return actualfiles
432 432
433 433 repo.__class__ = lfilesrepo
434 434
435 435 # stack of hooks being executed before committing.
436 436 # only last element ("_lfcommithooks[-1]") is used for each committing.
437 437 repo._lfcommithooks = [lfutil.updatestandinsbymatch]
438 438
439 439 # Stack of status writer functions taking "*msg, **opts" arguments
440 440 # like "ui.status()". Only last element ("_lfstatuswriters[-1]")
441 441 # is used to write status out.
442 442 repo._lfstatuswriters = [ui.status]
443 443
444 444 def prepushoutgoinghook(pushop):
445 445 """Push largefiles for pushop before pushing revisions."""
446 446 lfrevs = pushop.lfrevs
447 447 if lfrevs is None:
448 448 lfrevs = pushop.outgoing.missing
449 449 if lfrevs:
450 450 toupload = set()
451 451 addfunc = lambda fn, lfhash: toupload.add(lfhash)
452 452 lfutil.getlfilestoupload(pushop.repo, lfrevs, addfunc)
453 453 lfcommands.uploadlfiles(ui, pushop.repo, pushop.remote, toupload)
454 454
455 455 repo.prepushoutgoinghooks.add(b"largefiles", prepushoutgoinghook)
456 456
457 457 def checkrequireslfiles(ui, repo, **kwargs):
458 if b'largefiles' not in repo.requirements and any(
459 lfutil.shortname + b'/' in f[1] for f in repo.store.datafiles()
460 ):
461 repo.requirements.add(b'largefiles')
462 scmutil.writereporequirements(repo)
458 with repo.lock():
459 if b'largefiles' not in repo.requirements and any(
460 lfutil.shortname + b'/' in f[1] for f in repo.store.datafiles()
461 ):
462 repo.requirements.add(b'largefiles')
463 scmutil.writereporequirements(repo)
463 464
464 465 ui.setconfig(
465 466 b'hooks', b'changegroup.lfiles', checkrequireslfiles, b'largefiles'
466 467 )
467 468 ui.setconfig(b'hooks', b'commit.lfiles', checkrequireslfiles, b'largefiles')
General Comments 0
You need to be logged in to leave comments. Login now