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