##// END OF EJS Templates
largefiles: extract 'orig' method in lfilesmanifestdict.__contains__
Martin von Zweigbergk -
r22516:5e27eccb default
parent child Browse files
Show More
@@ -1,495 +1,492 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 import copy
11 11 import os
12 12
13 13 from mercurial import error, manifest, match as match_, util
14 14 from mercurial.i18n import _
15 15 from mercurial import localrepo
16 16
17 17 import lfcommands
18 18 import lfutil
19 19
20 20 def reposetup(ui, repo):
21 21 # wire repositories should be given new wireproto functions
22 22 # by "proto.wirereposetup()" via "hg.wirepeersetupfuncs"
23 23 if not repo.local():
24 24 return
25 25
26 26 class lfilesrepo(repo.__class__):
27 27 lfstatus = False
28 28 def status_nolfiles(self, *args, **kwargs):
29 29 return super(lfilesrepo, self).status(*args, **kwargs)
30 30
31 31 # When lfstatus is set, return a context that gives the names
32 32 # of largefiles instead of their corresponding standins and
33 33 # identifies the largefiles as always binary, regardless of
34 34 # their actual contents.
35 35 def __getitem__(self, changeid):
36 36 ctx = super(lfilesrepo, self).__getitem__(changeid)
37 37 if self.lfstatus:
38 38 class lfilesmanifestdict(manifest.manifestdict):
39 39 def __contains__(self, filename):
40 if super(lfilesmanifestdict,
41 self).__contains__(filename):
42 return True
43 return super(lfilesmanifestdict,
44 self).__contains__(lfutil.standin(filename))
40 orig = super(lfilesmanifestdict, self).__contains__
41 return orig(filename) or orig(lfutil.standin(filename))
45 42 class lfilesctx(ctx.__class__):
46 43 def files(self):
47 44 filenames = super(lfilesctx, self).files()
48 45 return [lfutil.splitstandin(f) or f for f in filenames]
49 46 def manifest(self):
50 47 man1 = super(lfilesctx, self).manifest()
51 48 man1.__class__ = lfilesmanifestdict
52 49 return man1
53 50 def filectx(self, path, fileid=None, filelog=None):
54 51 try:
55 52 if filelog is not None:
56 53 result = super(lfilesctx, self).filectx(
57 54 path, fileid, filelog)
58 55 else:
59 56 result = super(lfilesctx, self).filectx(
60 57 path, fileid)
61 58 except error.LookupError:
62 59 # Adding a null character will cause Mercurial to
63 60 # identify this as a binary file.
64 61 if filelog is not None:
65 62 result = super(lfilesctx, self).filectx(
66 63 lfutil.standin(path), fileid, filelog)
67 64 else:
68 65 result = super(lfilesctx, self).filectx(
69 66 lfutil.standin(path), fileid)
70 67 olddata = result.data
71 68 result.data = lambda: olddata() + '\0'
72 69 return result
73 70 ctx.__class__ = lfilesctx
74 71 return ctx
75 72
76 73 # Figure out the status of big files and insert them into the
77 74 # appropriate list in the result. Also removes standin files
78 75 # from the listing. Revert to the original status if
79 76 # self.lfstatus is False.
80 77 # XXX large file status is buggy when used on repo proxy.
81 78 # XXX this needs to be investigated.
82 79 @localrepo.unfilteredmethod
83 80 def status(self, node1='.', node2=None, match=None, ignored=False,
84 81 clean=False, unknown=False, listsubrepos=False):
85 82 listignored, listclean, listunknown = ignored, clean, unknown
86 83 if not self.lfstatus:
87 84 return super(lfilesrepo, self).status(node1, node2, match,
88 85 listignored, listclean, listunknown, listsubrepos)
89 86
90 87 # some calls in this function rely on the old version of status
91 88 self.lfstatus = False
92 89 ctx1 = self[node1]
93 90 ctx2 = self[node2]
94 91 working = ctx2.rev() is None
95 92 parentworking = working and ctx1 == self['.']
96 93
97 94 def inctx(file, ctx):
98 95 try:
99 96 if ctx.rev() is None:
100 97 return file in ctx.manifest()
101 98 ctx[file]
102 99 return True
103 100 except KeyError:
104 101 return False
105 102
106 103 if match is None:
107 104 match = match_.always(self.root, self.getcwd())
108 105
109 106 wlock = None
110 107 try:
111 108 try:
112 109 # updating the dirstate is optional
113 110 # so we don't wait on the lock
114 111 wlock = self.wlock(False)
115 112 except error.LockError:
116 113 pass
117 114
118 115 # First check if there were files specified on the
119 116 # command line. If there were, and none of them were
120 117 # largefiles, we should just bail here and let super
121 118 # handle it -- thus gaining a big performance boost.
122 119 lfdirstate = lfutil.openlfdirstate(ui, self)
123 120 if match.files() and not match.anypats():
124 121 for f in lfdirstate:
125 122 if match(f):
126 123 break
127 124 else:
128 125 return super(lfilesrepo, self).status(node1, node2,
129 126 match, listignored, listclean,
130 127 listunknown, listsubrepos)
131 128
132 129 # Create a copy of match that matches standins instead
133 130 # of largefiles.
134 131 def tostandins(files):
135 132 if not working:
136 133 return files
137 134 newfiles = []
138 135 dirstate = self.dirstate
139 136 for f in files:
140 137 sf = lfutil.standin(f)
141 138 if sf in dirstate:
142 139 newfiles.append(sf)
143 140 elif sf in dirstate.dirs():
144 141 # Directory entries could be regular or
145 142 # standin, check both
146 143 newfiles.extend((f, sf))
147 144 else:
148 145 newfiles.append(f)
149 146 return newfiles
150 147
151 148 m = copy.copy(match)
152 149 m._files = tostandins(m._files)
153 150
154 151 result = super(lfilesrepo, self).status(node1, node2, m,
155 152 ignored, clean, unknown, listsubrepos)
156 153 if working:
157 154
158 155 def sfindirstate(f):
159 156 sf = lfutil.standin(f)
160 157 dirstate = self.dirstate
161 158 return sf in dirstate or sf in dirstate.dirs()
162 159
163 160 match._files = [f for f in match._files
164 161 if sfindirstate(f)]
165 162 # Don't waste time getting the ignored and unknown
166 163 # files from lfdirstate
167 164 s = lfdirstate.status(match, [], False,
168 165 listclean, False)
169 166 (unsure, modified, added, removed, missing, _unknown,
170 167 _ignored, clean) = s
171 168 if parentworking:
172 169 for lfile in unsure:
173 170 standin = lfutil.standin(lfile)
174 171 if standin not in ctx1:
175 172 # from second parent
176 173 modified.append(lfile)
177 174 elif ctx1[standin].data().strip() \
178 175 != lfutil.hashfile(self.wjoin(lfile)):
179 176 modified.append(lfile)
180 177 else:
181 178 clean.append(lfile)
182 179 lfdirstate.normal(lfile)
183 180 else:
184 181 tocheck = unsure + modified + added + clean
185 182 modified, added, clean = [], [], []
186 183
187 184 for lfile in tocheck:
188 185 standin = lfutil.standin(lfile)
189 186 if inctx(standin, ctx1):
190 187 if ctx1[standin].data().strip() != \
191 188 lfutil.hashfile(self.wjoin(lfile)):
192 189 modified.append(lfile)
193 190 else:
194 191 clean.append(lfile)
195 192 else:
196 193 added.append(lfile)
197 194
198 195 # Standins no longer found in lfdirstate has been
199 196 # removed
200 197 for standin in ctx1.manifest():
201 198 if not lfutil.isstandin(standin):
202 199 continue
203 200 lfile = lfutil.splitstandin(standin)
204 201 if not match(lfile):
205 202 continue
206 203 if lfile not in lfdirstate:
207 204 removed.append(lfile)
208 205
209 206 # Filter result lists
210 207 result = list(result)
211 208
212 209 # Largefiles are not really removed when they're
213 210 # still in the normal dirstate. Likewise, normal
214 211 # files are not really removed if they are still in
215 212 # lfdirstate. This happens in merges where files
216 213 # change type.
217 214 removed = [f for f in removed
218 215 if f not in self.dirstate]
219 216 result[2] = [f for f in result[2]
220 217 if f not in lfdirstate]
221 218
222 219 lfiles = set(lfdirstate._map)
223 220 # Unknown files
224 221 result[4] = set(result[4]).difference(lfiles)
225 222 # Ignored files
226 223 result[5] = set(result[5]).difference(lfiles)
227 224 # combine normal files and largefiles
228 225 normals = [[fn for fn in filelist
229 226 if not lfutil.isstandin(fn)]
230 227 for filelist in result]
231 228 lfiles = (modified, added, removed, missing, [], [], clean)
232 229 result = [sorted(list1 + list2)
233 230 for (list1, list2) in zip(normals, lfiles)]
234 231 else:
235 232 def toname(f):
236 233 if lfutil.isstandin(f):
237 234 return lfutil.splitstandin(f)
238 235 return f
239 236 result = [[toname(f) for f in items]
240 237 for items in result]
241 238
242 239 if wlock:
243 240 lfdirstate.write()
244 241
245 242 finally:
246 243 if wlock:
247 244 wlock.release()
248 245
249 246 if not listunknown:
250 247 result[4] = []
251 248 if not listignored:
252 249 result[5] = []
253 250 if not listclean:
254 251 result[6] = []
255 252 self.lfstatus = True
256 253 return result
257 254
258 255 # As part of committing, copy all of the largefiles into the
259 256 # cache.
260 257 def commitctx(self, *args, **kwargs):
261 258 node = super(lfilesrepo, self).commitctx(*args, **kwargs)
262 259 lfutil.copyalltostore(self, node)
263 260 return node
264 261
265 262 # Before commit, largefile standins have not had their
266 263 # contents updated to reflect the hash of their largefile.
267 264 # Do that here.
268 265 def commit(self, text="", user=None, date=None, match=None,
269 266 force=False, editor=False, extra={}):
270 267 orig = super(lfilesrepo, self).commit
271 268
272 269 wlock = self.wlock()
273 270 try:
274 271 # Case 0: Automated committing
275 272 #
276 273 # While automated committing (like rebase, transplant
277 274 # and so on), this code path is used to avoid:
278 275 # (1) updating standins, because standins should
279 276 # be already updated at this point
280 277 # (2) aborting when stadnins are matched by "match",
281 278 # because automated committing may specify them directly
282 279 #
283 280 if getattr(self, "_isrebasing", False) or \
284 281 getattr(self, "_istransplanting", False):
285 282 result = orig(text=text, user=user, date=date, match=match,
286 283 force=force, editor=editor, extra=extra)
287 284
288 285 if result:
289 286 lfdirstate = lfutil.openlfdirstate(ui, self)
290 287 for f in self[result].files():
291 288 if lfutil.isstandin(f):
292 289 lfile = lfutil.splitstandin(f)
293 290 lfutil.synclfdirstate(self, lfdirstate, lfile,
294 291 False)
295 292 lfdirstate.write()
296 293
297 294 return result
298 295 # Case 1: user calls commit with no specific files or
299 296 # include/exclude patterns: refresh and commit all files that
300 297 # are "dirty".
301 298 if ((match is None) or
302 299 (not match.anypats() and not match.files())):
303 300 # Spend a bit of time here to get a list of files we know
304 301 # are modified so we can compare only against those.
305 302 # It can cost a lot of time (several seconds)
306 303 # otherwise to update all standins if the largefiles are
307 304 # large.
308 305 lfdirstate = lfutil.openlfdirstate(ui, self)
309 306 dirtymatch = match_.always(self.root, self.getcwd())
310 307 s = lfdirstate.status(dirtymatch, [], False, False, False)
311 308 (unsure, modified, added, removed, _missing, _unknown,
312 309 _ignored, _clean) = s
313 310 modifiedfiles = unsure + modified + added + removed
314 311 lfiles = lfutil.listlfiles(self)
315 312 # this only loops through largefiles that exist (not
316 313 # removed/renamed)
317 314 for lfile in lfiles:
318 315 if lfile in modifiedfiles:
319 316 if os.path.exists(
320 317 self.wjoin(lfutil.standin(lfile))):
321 318 # this handles the case where a rebase is being
322 319 # performed and the working copy is not updated
323 320 # yet.
324 321 if os.path.exists(self.wjoin(lfile)):
325 322 lfutil.updatestandin(self,
326 323 lfutil.standin(lfile))
327 324 lfdirstate.normal(lfile)
328 325
329 326 result = orig(text=text, user=user, date=date, match=match,
330 327 force=force, editor=editor, extra=extra)
331 328
332 329 if result is not None:
333 330 for lfile in lfdirstate:
334 331 if lfile in modifiedfiles:
335 332 if (not os.path.exists(self.wjoin(
336 333 lfutil.standin(lfile)))) or \
337 334 (not os.path.exists(self.wjoin(lfile))):
338 335 lfdirstate.drop(lfile)
339 336
340 337 # This needs to be after commit; otherwise precommit hooks
341 338 # get the wrong status
342 339 lfdirstate.write()
343 340 return result
344 341
345 342 lfiles = lfutil.listlfiles(self)
346 343 match._files = self._subdirlfs(match.files(), lfiles)
347 344
348 345 # Case 2: user calls commit with specified patterns: refresh
349 346 # any matching big files.
350 347 smatcher = lfutil.composestandinmatcher(self, match)
351 348 standins = self.dirstate.walk(smatcher, [], False, False)
352 349
353 350 # No matching big files: get out of the way and pass control to
354 351 # the usual commit() method.
355 352 if not standins:
356 353 return orig(text=text, user=user, date=date, match=match,
357 354 force=force, editor=editor, extra=extra)
358 355
359 356 # Refresh all matching big files. It's possible that the
360 357 # commit will end up failing, in which case the big files will
361 358 # stay refreshed. No harm done: the user modified them and
362 359 # asked to commit them, so sooner or later we're going to
363 360 # refresh the standins. Might as well leave them refreshed.
364 361 lfdirstate = lfutil.openlfdirstate(ui, self)
365 362 for standin in standins:
366 363 lfile = lfutil.splitstandin(standin)
367 364 if lfdirstate[lfile] != 'r':
368 365 lfutil.updatestandin(self, standin)
369 366 lfdirstate.normal(lfile)
370 367 else:
371 368 lfdirstate.drop(lfile)
372 369
373 370 # Cook up a new matcher that only matches regular files or
374 371 # standins corresponding to the big files requested by the
375 372 # user. Have to modify _files to prevent commit() from
376 373 # complaining "not tracked" for big files.
377 374 match = copy.copy(match)
378 375 origmatchfn = match.matchfn
379 376
380 377 # Check both the list of largefiles and the list of
381 378 # standins because if a largefile was removed, it
382 379 # won't be in the list of largefiles at this point
383 380 match._files += sorted(standins)
384 381
385 382 actualfiles = []
386 383 for f in match._files:
387 384 fstandin = lfutil.standin(f)
388 385
389 386 # ignore known largefiles and standins
390 387 if f in lfiles or fstandin in standins:
391 388 continue
392 389
393 390 actualfiles.append(f)
394 391 match._files = actualfiles
395 392
396 393 def matchfn(f):
397 394 if origmatchfn(f):
398 395 return f not in lfiles
399 396 else:
400 397 return f in standins
401 398
402 399 match.matchfn = matchfn
403 400 result = orig(text=text, user=user, date=date, match=match,
404 401 force=force, editor=editor, extra=extra)
405 402 # This needs to be after commit; otherwise precommit hooks
406 403 # get the wrong status
407 404 lfdirstate.write()
408 405 return result
409 406 finally:
410 407 wlock.release()
411 408
412 409 def push(self, remote, force=False, revs=None, newbranch=False):
413 410 if remote.local():
414 411 missing = set(self.requirements) - remote.local().supported
415 412 if missing:
416 413 msg = _("required features are not"
417 414 " supported in the destination:"
418 415 " %s") % (', '.join(sorted(missing)))
419 416 raise util.Abort(msg)
420 417 return super(lfilesrepo, self).push(remote, force=force, revs=revs,
421 418 newbranch=newbranch)
422 419
423 420 def _subdirlfs(self, files, lfiles):
424 421 '''
425 422 Adjust matched file list
426 423 If we pass a directory to commit whose only commitable files
427 424 are largefiles, the core commit code aborts before finding
428 425 the largefiles.
429 426 So we do the following:
430 427 For directories that only have largefiles as matches,
431 428 we explicitly add the largefiles to the match list and remove
432 429 the directory.
433 430 In other cases, we leave the match list unmodified.
434 431 '''
435 432 actualfiles = []
436 433 dirs = []
437 434 regulars = []
438 435
439 436 for f in files:
440 437 if lfutil.isstandin(f + '/'):
441 438 raise util.Abort(
442 439 _('file "%s" is a largefile standin') % f,
443 440 hint=('commit the largefile itself instead'))
444 441 # Scan directories
445 442 if os.path.isdir(self.wjoin(f)):
446 443 dirs.append(f)
447 444 else:
448 445 regulars.append(f)
449 446
450 447 for f in dirs:
451 448 matcheddir = False
452 449 d = self.dirstate.normalize(f) + '/'
453 450 # Check for matched normal files
454 451 for mf in regulars:
455 452 if self.dirstate.normalize(mf).startswith(d):
456 453 actualfiles.append(f)
457 454 matcheddir = True
458 455 break
459 456 if not matcheddir:
460 457 # If no normal match, manually append
461 458 # any matching largefiles
462 459 for lf in lfiles:
463 460 if self.dirstate.normalize(lf).startswith(d):
464 461 actualfiles.append(lf)
465 462 if not matcheddir:
466 463 actualfiles.append(lfutil.standin(f))
467 464 matcheddir = True
468 465 # Nothing in dir, so readd it
469 466 # and let commit reject it
470 467 if not matcheddir:
471 468 actualfiles.append(f)
472 469
473 470 # Always add normal files
474 471 actualfiles += regulars
475 472 return actualfiles
476 473
477 474 repo.__class__ = lfilesrepo
478 475
479 476 def prepushoutgoinghook(local, remote, outgoing):
480 477 if outgoing.missing:
481 478 toupload = set()
482 479 addfunc = lambda fn, lfhash: toupload.add(lfhash)
483 480 lfutil.getlfilestoupload(local, outgoing.missing, addfunc)
484 481 lfcommands.uploadlfiles(ui, local, remote, toupload)
485 482 repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook)
486 483
487 484 def checkrequireslfiles(ui, repo, **kwargs):
488 485 if 'largefiles' not in repo.requirements and util.any(
489 486 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
490 487 repo.requirements.add('largefiles')
491 488 repo._writerequirements()
492 489
493 490 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles,
494 491 'largefiles')
495 492 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles')
General Comments 0
You need to be logged in to leave comments. Login now