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