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