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