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