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