##// END OF EJS Templates
largefiles: factor out a copyalltostore() function
Dan Villiom Podlaski Christiansen -
r15796:3e5b6045 default
parent child Browse files
Show More
@@ -1,450 +1,460 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 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import os
12 12 import errno
13 13 import platform
14 14 import shutil
15 15 import stat
16 16 import tempfile
17 17
18 18 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
19 19 from mercurial.i18n import _
20 20
21 21 shortname = '.hglf'
22 22 longname = 'largefiles'
23 23
24 24
25 25 # -- Portability wrappers ----------------------------------------------
26 26
27 27 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
28 28 return dirstate.walk(matcher, [], unknown, ignored)
29 29
30 30 def repo_add(repo, list):
31 31 add = repo[None].add
32 32 return add(list)
33 33
34 34 def repo_remove(repo, list, unlink=False):
35 35 def remove(list, unlink):
36 36 wlock = repo.wlock()
37 37 try:
38 38 if unlink:
39 39 for f in list:
40 40 try:
41 41 util.unlinkpath(repo.wjoin(f))
42 42 except OSError, inst:
43 43 if inst.errno != errno.ENOENT:
44 44 raise
45 45 repo[None].forget(list)
46 46 finally:
47 47 wlock.release()
48 48 return remove(list, unlink=unlink)
49 49
50 50 def repo_forget(repo, list):
51 51 forget = repo[None].forget
52 52 return forget(list)
53 53
54 54 def findoutgoing(repo, remote, force):
55 55 from mercurial import discovery
56 56 common, _anyinc, _heads = discovery.findcommonincoming(repo,
57 57 remote, force=force)
58 58 return repo.changelog.findmissing(common)
59 59
60 60 # -- Private worker functions ------------------------------------------
61 61
62 62 def getminsize(ui, assumelfiles, opt, default=10):
63 63 lfsize = opt
64 64 if not lfsize and assumelfiles:
65 65 lfsize = ui.config(longname, 'minsize', default=default)
66 66 if lfsize:
67 67 try:
68 68 lfsize = float(lfsize)
69 69 except ValueError:
70 70 raise util.Abort(_('largefiles: size must be number (not %s)\n')
71 71 % lfsize)
72 72 if lfsize is None:
73 73 raise util.Abort(_('minimum size for largefiles must be specified'))
74 74 return lfsize
75 75
76 76 def link(src, dest):
77 77 try:
78 78 util.oslink(src, dest)
79 79 except OSError:
80 80 # if hardlinks fail, fallback on atomic copy
81 81 dst = util.atomictempfile(dest)
82 82 for chunk in util.filechunkiter(open(src, 'rb')):
83 83 dst.write(chunk)
84 84 dst.close()
85 85 os.chmod(dest, os.stat(src).st_mode)
86 86
87 87 def usercachepath(ui, hash):
88 88 path = ui.configpath(longname, 'usercache', None)
89 89 if path:
90 90 path = os.path.join(path, hash)
91 91 else:
92 92 if os.name == 'nt':
93 93 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
94 94 if appdata:
95 95 path = os.path.join(appdata, longname, hash)
96 96 elif platform.system() == 'Darwin':
97 97 home = os.getenv('HOME')
98 98 if home:
99 99 path = os.path.join(home, 'Library', 'Caches',
100 100 longname, hash)
101 101 elif os.name == 'posix':
102 102 path = os.getenv('XDG_CACHE_HOME')
103 103 if path:
104 104 path = os.path.join(path, longname, hash)
105 105 else:
106 106 home = os.getenv('HOME')
107 107 if home:
108 108 path = os.path.join(home, '.cache', longname, hash)
109 109 else:
110 110 raise util.Abort(_('unknown operating system: %s\n') % os.name)
111 111 return path
112 112
113 113 def inusercache(ui, hash):
114 114 path = usercachepath(ui, hash)
115 115 return path and os.path.exists(path)
116 116
117 117 def findfile(repo, hash):
118 118 if instore(repo, hash):
119 119 repo.ui.note(_('Found %s in store\n') % hash)
120 120 elif inusercache(repo.ui, hash):
121 121 repo.ui.note(_('Found %s in system cache\n') % hash)
122 122 path = storepath(repo, hash)
123 123 util.makedirs(os.path.dirname(path))
124 124 link(usercachepath(repo.ui, hash), path)
125 125 else:
126 126 return None
127 127 return storepath(repo, hash)
128 128
129 129 class largefiles_dirstate(dirstate.dirstate):
130 130 def __getitem__(self, key):
131 131 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
132 132 def normal(self, f):
133 133 return super(largefiles_dirstate, self).normal(unixpath(f))
134 134 def remove(self, f):
135 135 return super(largefiles_dirstate, self).remove(unixpath(f))
136 136 def add(self, f):
137 137 return super(largefiles_dirstate, self).add(unixpath(f))
138 138 def drop(self, f):
139 139 return super(largefiles_dirstate, self).drop(unixpath(f))
140 140 def forget(self, f):
141 141 return super(largefiles_dirstate, self).forget(unixpath(f))
142 142 def normallookup(self, f):
143 143 return super(largefiles_dirstate, self).normallookup(unixpath(f))
144 144
145 145 def openlfdirstate(ui, repo):
146 146 '''
147 147 Return a dirstate object that tracks largefiles: i.e. its root is
148 148 the repo root, but it is saved in .hg/largefiles/dirstate.
149 149 '''
150 150 admin = repo.join(longname)
151 151 opener = scmutil.opener(admin)
152 152 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
153 153 repo.dirstate._validate)
154 154
155 155 # If the largefiles dirstate does not exist, populate and create
156 156 # it. This ensures that we create it on the first meaningful
157 157 # largefiles operation in a new clone.
158 158 if not os.path.exists(os.path.join(admin, 'dirstate')):
159 159 util.makedirs(admin)
160 160 matcher = getstandinmatcher(repo)
161 161 for standin in dirstate_walk(repo.dirstate, matcher):
162 162 lfile = splitstandin(standin)
163 163 hash = readstandin(repo, lfile)
164 164 lfdirstate.normallookup(lfile)
165 165 try:
166 166 if hash == hashfile(repo.wjoin(lfile)):
167 167 lfdirstate.normal(lfile)
168 168 except OSError, err:
169 169 if err.errno != errno.ENOENT:
170 170 raise
171 171 return lfdirstate
172 172
173 173 def lfdirstate_status(lfdirstate, repo, rev):
174 174 match = match_.always(repo.root, repo.getcwd())
175 175 s = lfdirstate.status(match, [], False, False, False)
176 176 unsure, modified, added, removed, missing, unknown, ignored, clean = s
177 177 for lfile in unsure:
178 178 if repo[rev][standin(lfile)].data().strip() != \
179 179 hashfile(repo.wjoin(lfile)):
180 180 modified.append(lfile)
181 181 else:
182 182 clean.append(lfile)
183 183 lfdirstate.normal(lfile)
184 184 return (modified, added, removed, missing, unknown, ignored, clean)
185 185
186 186 def listlfiles(repo, rev=None, matcher=None):
187 187 '''return a list of largefiles in the working copy or the
188 188 specified changeset'''
189 189
190 190 if matcher is None:
191 191 matcher = getstandinmatcher(repo)
192 192
193 193 # ignore unknown files in working directory
194 194 return [splitstandin(f)
195 195 for f in repo[rev].walk(matcher)
196 196 if rev is not None or repo.dirstate[f] != '?']
197 197
198 198 def instore(repo, hash):
199 199 return os.path.exists(storepath(repo, hash))
200 200
201 201 def storepath(repo, hash):
202 202 return repo.join(os.path.join(longname, hash))
203 203
204 204 def copyfromcache(repo, hash, filename):
205 205 '''Copy the specified largefile from the repo or system cache to
206 206 filename in the repository. Return true on success or false if the
207 207 file was not found in either cache (which should not happened:
208 208 this is meant to be called only after ensuring that the needed
209 209 largefile exists in the cache).'''
210 210 path = findfile(repo, hash)
211 211 if path is None:
212 212 return False
213 213 util.makedirs(os.path.dirname(repo.wjoin(filename)))
214 214 # The write may fail before the file is fully written, but we
215 215 # don't use atomic writes in the working copy.
216 216 shutil.copy(path, repo.wjoin(filename))
217 217 return True
218 218
219 219 def copytostore(repo, rev, file, uploaded=False):
220 220 hash = readstandin(repo, file)
221 221 if instore(repo, hash):
222 222 return
223 223 copytostoreabsolute(repo, repo.wjoin(file), hash)
224 224
225 def copyalltostore(repo, node):
226 '''Copy all largefiles in a given revision to the store'''
227
228 ctx = repo[node]
229 for filename in ctx.files():
230 if isstandin(filename) and filename in ctx.manifest():
231 realfile = splitstandin(filename)
232 copytostore(repo, ctx.node(), realfile)
233
234
225 235 def copytostoreabsolute(repo, file, hash):
226 236 util.makedirs(os.path.dirname(storepath(repo, hash)))
227 237 if inusercache(repo.ui, hash):
228 238 link(usercachepath(repo.ui, hash), storepath(repo, hash))
229 239 else:
230 240 dst = util.atomictempfile(storepath(repo, hash))
231 241 for chunk in util.filechunkiter(open(file, 'rb')):
232 242 dst.write(chunk)
233 243 dst.close()
234 244 util.copymode(file, storepath(repo, hash))
235 245 linktousercache(repo, hash)
236 246
237 247 def linktousercache(repo, hash):
238 248 path = usercachepath(repo.ui, hash)
239 249 if path:
240 250 util.makedirs(os.path.dirname(path))
241 251 link(storepath(repo, hash), path)
242 252
243 253 def getstandinmatcher(repo, pats=[], opts={}):
244 254 '''Return a match object that applies pats to the standin directory'''
245 255 standindir = repo.pathto(shortname)
246 256 if pats:
247 257 # patterns supplied: search standin directory relative to current dir
248 258 cwd = repo.getcwd()
249 259 if os.path.isabs(cwd):
250 260 # cwd is an absolute path for hg -R <reponame>
251 261 # work relative to the repository root in this case
252 262 cwd = ''
253 263 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
254 264 elif os.path.isdir(standindir):
255 265 # no patterns: relative to repo root
256 266 pats = [standindir]
257 267 else:
258 268 # no patterns and no standin dir: return matcher that matches nothing
259 269 match = match_.match(repo.root, None, [], exact=True)
260 270 match.matchfn = lambda f: False
261 271 return match
262 272 return getmatcher(repo, pats, opts, showbad=False)
263 273
264 274 def getmatcher(repo, pats=[], opts={}, showbad=True):
265 275 '''Wrapper around scmutil.match() that adds showbad: if false,
266 276 neuter the match object's bad() method so it does not print any
267 277 warnings about missing files or directories.'''
268 278 match = scmutil.match(repo[None], pats, opts)
269 279
270 280 if not showbad:
271 281 match.bad = lambda f, msg: None
272 282 return match
273 283
274 284 def composestandinmatcher(repo, rmatcher):
275 285 '''Return a matcher that accepts standins corresponding to the
276 286 files accepted by rmatcher. Pass the list of files in the matcher
277 287 as the paths specified by the user.'''
278 288 smatcher = getstandinmatcher(repo, rmatcher.files())
279 289 isstandin = smatcher.matchfn
280 290 def composed_matchfn(f):
281 291 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
282 292 smatcher.matchfn = composed_matchfn
283 293
284 294 return smatcher
285 295
286 296 def standin(filename):
287 297 '''Return the repo-relative path to the standin for the specified big
288 298 file.'''
289 299 # Notes:
290 300 # 1) Most callers want an absolute path, but _create_standin() needs
291 301 # it repo-relative so lfadd() can pass it to repo_add(). So leave
292 302 # it up to the caller to use repo.wjoin() to get an absolute path.
293 303 # 2) Join with '/' because that's what dirstate always uses, even on
294 304 # Windows. Change existing separator to '/' first in case we are
295 305 # passed filenames from an external source (like the command line).
296 306 return shortname + '/' + filename.replace(os.sep, '/')
297 307
298 308 def isstandin(filename):
299 309 '''Return true if filename is a big file standin. filename must be
300 310 in Mercurial's internal form (slash-separated).'''
301 311 return filename.startswith(shortname + '/')
302 312
303 313 def splitstandin(filename):
304 314 # Split on / because that's what dirstate always uses, even on Windows.
305 315 # Change local separator to / first just in case we are passed filenames
306 316 # from an external source (like the command line).
307 317 bits = filename.replace(os.sep, '/').split('/', 1)
308 318 if len(bits) == 2 and bits[0] == shortname:
309 319 return bits[1]
310 320 else:
311 321 return None
312 322
313 323 def updatestandin(repo, standin):
314 324 file = repo.wjoin(splitstandin(standin))
315 325 if os.path.exists(file):
316 326 hash = hashfile(file)
317 327 executable = getexecutable(file)
318 328 writestandin(repo, standin, hash, executable)
319 329
320 330 def readstandin(repo, filename, node=None):
321 331 '''read hex hash from standin for filename at given node, or working
322 332 directory if no node is given'''
323 333 return repo[node][standin(filename)].data().strip()
324 334
325 335 def writestandin(repo, standin, hash, executable):
326 336 '''write hash to <repo.root>/<standin>'''
327 337 writehash(hash, repo.wjoin(standin), executable)
328 338
329 339 def copyandhash(instream, outfile):
330 340 '''Read bytes from instream (iterable) and write them to outfile,
331 341 computing the SHA-1 hash of the data along the way. Close outfile
332 342 when done and return the binary hash.'''
333 343 hasher = util.sha1('')
334 344 for data in instream:
335 345 hasher.update(data)
336 346 outfile.write(data)
337 347
338 348 # Blecch: closing a file that somebody else opened is rude and
339 349 # wrong. But it's so darn convenient and practical! After all,
340 350 # outfile was opened just to copy and hash.
341 351 outfile.close()
342 352
343 353 return hasher.digest()
344 354
345 355 def hashrepofile(repo, file):
346 356 return hashfile(repo.wjoin(file))
347 357
348 358 def hashfile(file):
349 359 if not os.path.exists(file):
350 360 return ''
351 361 hasher = util.sha1('')
352 362 fd = open(file, 'rb')
353 363 for data in blockstream(fd):
354 364 hasher.update(data)
355 365 fd.close()
356 366 return hasher.hexdigest()
357 367
358 368 class limitreader(object):
359 369 def __init__(self, f, limit):
360 370 self.f = f
361 371 self.limit = limit
362 372
363 373 def read(self, length):
364 374 if self.limit == 0:
365 375 return ''
366 376 length = length > self.limit and self.limit or length
367 377 self.limit -= length
368 378 return self.f.read(length)
369 379
370 380 def close(self):
371 381 pass
372 382
373 383 def blockstream(infile, blocksize=128 * 1024):
374 384 """Generator that yields blocks of data from infile and closes infile."""
375 385 while True:
376 386 data = infile.read(blocksize)
377 387 if not data:
378 388 break
379 389 yield data
380 390 # same blecch as copyandhash() above
381 391 infile.close()
382 392
383 393 def readhash(filename):
384 394 rfile = open(filename, 'rb')
385 395 hash = rfile.read(40)
386 396 rfile.close()
387 397 if len(hash) < 40:
388 398 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
389 399 % (filename, len(hash)))
390 400 return hash
391 401
392 402 def writehash(hash, filename, executable):
393 403 util.makedirs(os.path.dirname(filename))
394 404 util.writefile(filename, hash + '\n')
395 405 os.chmod(filename, getmode(executable))
396 406
397 407 def getexecutable(filename):
398 408 mode = os.stat(filename).st_mode
399 409 return ((mode & stat.S_IXUSR) and
400 410 (mode & stat.S_IXGRP) and
401 411 (mode & stat.S_IXOTH))
402 412
403 413 def getmode(executable):
404 414 if executable:
405 415 return 0755
406 416 else:
407 417 return 0644
408 418
409 419 def urljoin(first, second, *arg):
410 420 def join(left, right):
411 421 if not left.endswith('/'):
412 422 left += '/'
413 423 if right.startswith('/'):
414 424 right = right[1:]
415 425 return left + right
416 426
417 427 url = join(first, second)
418 428 for a in arg:
419 429 url = join(url, a)
420 430 return url
421 431
422 432 def hexsha1(data):
423 433 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
424 434 object data"""
425 435 h = util.sha1()
426 436 for chunk in util.filechunkiter(data):
427 437 h.update(chunk)
428 438 return h.hexdigest()
429 439
430 440 def httpsendfile(ui, filename):
431 441 return httpconnection.httpsendfile(ui, filename, 'rb')
432 442
433 443 def unixpath(path):
434 444 '''Return a version of path normalized for use with the lfdirstate.'''
435 445 return os.path.normpath(path).replace(os.sep, '/')
436 446
437 447 def islfilesrepo(repo):
438 448 return ('largefiles' in repo.requirements and
439 449 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
440 450
441 451 def mkstemp(repo, prefix):
442 452 '''Returns a file descriptor and a filename corresponding to a temporary
443 453 file in the repo's largefiles store.'''
444 454 path = repo.join(longname)
445 455 util.makedirs(path)
446 456 return tempfile.mkstemp(prefix=prefix, dir=path)
447 457
448 458 class storeprotonotcapable(Exception):
449 459 def __init__(self, storetypes):
450 460 self.storetypes = storetypes
@@ -1,451 +1,446 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 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 lfiles_repo(repo.__class__):
38 38 lfstatus = False
39 39 def status_nolfiles(self, *args, **kwargs):
40 40 return super(lfiles_repo, 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(lfiles_repo, self).__getitem__(changeid)
48 48 if self.lfstatus:
49 49 class lfiles_manifestdict(manifest.manifestdict):
50 50 def __contains__(self, filename):
51 51 if super(lfiles_manifestdict,
52 52 self).__contains__(filename):
53 53 return True
54 54 return super(lfiles_manifestdict,
55 55 self).__contains__(lfutil.standin(filename))
56 56 class lfiles_ctx(ctx.__class__):
57 57 def files(self):
58 58 filenames = super(lfiles_ctx, self).files()
59 59 return [lfutil.splitstandin(f) or f for f in filenames]
60 60 def manifest(self):
61 61 man1 = super(lfiles_ctx, self).manifest()
62 62 man1.__class__ = lfiles_manifestdict
63 63 return man1
64 64 def filectx(self, path, fileid=None, filelog=None):
65 65 try:
66 66 result = super(lfiles_ctx, self).filectx(path,
67 67 fileid, filelog)
68 68 except error.LookupError:
69 69 # Adding a null character will cause Mercurial to
70 70 # identify this as a binary file.
71 71 result = super(lfiles_ctx, self).filectx(
72 72 lfutil.standin(path), fileid, filelog)
73 73 olddata = result.data
74 74 result.data = lambda: olddata() + '\0'
75 75 return result
76 76 ctx.__class__ = lfiles_ctx
77 77 return ctx
78 78
79 79 # Figure out the status of big files and insert them into the
80 80 # appropriate list in the result. Also removes standin files
81 81 # from the listing. Revert to the original status if
82 82 # self.lfstatus is False.
83 83 def status(self, node1='.', node2=None, match=None, ignored=False,
84 84 clean=False, unknown=False, listsubrepos=False):
85 85 listignored, listclean, listunknown = ignored, clean, unknown
86 86 if not self.lfstatus:
87 87 return super(lfiles_repo, self).status(node1, node2, match,
88 88 listignored, listclean, listunknown, listsubrepos)
89 89 else:
90 90 # some calls in this function rely on the old version of status
91 91 self.lfstatus = False
92 92 if isinstance(node1, context.changectx):
93 93 ctx1 = node1
94 94 else:
95 95 ctx1 = repo[node1]
96 96 if isinstance(node2, context.changectx):
97 97 ctx2 = node2
98 98 else:
99 99 ctx2 = repo[node2]
100 100 working = ctx2.rev() is None
101 101 parentworking = working and ctx1 == self['.']
102 102
103 103 def inctx(file, ctx):
104 104 try:
105 105 if ctx.rev() is None:
106 106 return file in ctx.manifest()
107 107 ctx[file]
108 108 return True
109 109 except KeyError:
110 110 return False
111 111
112 112 if match is None:
113 113 match = match_.always(self.root, self.getcwd())
114 114
115 115 # First check if there were files specified on the
116 116 # command line. If there were, and none of them were
117 117 # largefiles, we should just bail here and let super
118 118 # handle it -- thus gaining a big performance boost.
119 119 lfdirstate = lfutil.openlfdirstate(ui, self)
120 120 if match.files() and not match.anypats():
121 121 matchedfiles = [f for f in match.files() if f in lfdirstate]
122 122 if not matchedfiles:
123 123 return super(lfiles_repo, self).status(node1, node2,
124 124 match, listignored, listclean,
125 125 listunknown, listsubrepos)
126 126
127 127 # Create a copy of match that matches standins instead
128 128 # of largefiles.
129 129 def tostandin(file):
130 130 if inctx(lfutil.standin(file), ctx2):
131 131 return lfutil.standin(file)
132 132 return file
133 133
134 134 # Create a function that we can use to override what is
135 135 # normally the ignore matcher. We've already checked
136 136 # for ignored files on the first dirstate walk, and
137 137 # unecessarily re-checking here causes a huge performance
138 138 # hit because lfdirstate only knows about largefiles
139 139 def _ignoreoverride(self):
140 140 return False
141 141
142 142 m = copy.copy(match)
143 143 m._files = [tostandin(f) for f in m._files]
144 144
145 145 # Get ignored files here even if we weren't asked for them; we
146 146 # must use the result here for filtering later
147 147 result = super(lfiles_repo, self).status(node1, node2, m,
148 148 True, clean, unknown, listsubrepos)
149 149 if working:
150 150 try:
151 151 # Any non-largefiles that were explicitly listed must be
152 152 # taken out or lfdirstate.status will report an error.
153 153 # The status of these files was already computed using
154 154 # super's status.
155 155 # Override lfdirstate's ignore matcher to not do
156 156 # anything
157 157 orig_ignore = lfdirstate._ignore
158 158 lfdirstate._ignore = _ignoreoverride
159 159
160 160 match._files = [f for f in match._files if f in
161 161 lfdirstate]
162 162 # Don't waste time getting the ignored and unknown
163 163 # files again; we already have them
164 164 s = lfdirstate.status(match, [], False,
165 165 listclean, False)
166 166 (unsure, modified, added, removed, missing, unknown,
167 167 ignored, clean) = s
168 168 # Replace the list of ignored and unknown files with
169 169 # the previously caclulated lists, and strip out the
170 170 # largefiles
171 171 lfiles = set(lfdirstate._map)
172 172 ignored = set(result[5]).difference(lfiles)
173 173 unknown = set(result[4]).difference(lfiles)
174 174 if parentworking:
175 175 for lfile in unsure:
176 176 standin = lfutil.standin(lfile)
177 177 if standin not in ctx1:
178 178 # from second parent
179 179 modified.append(lfile)
180 180 elif ctx1[standin].data().strip() \
181 181 != lfutil.hashfile(self.wjoin(lfile)):
182 182 modified.append(lfile)
183 183 else:
184 184 clean.append(lfile)
185 185 lfdirstate.normal(lfile)
186 186 else:
187 187 tocheck = unsure + modified + added + clean
188 188 modified, added, clean = [], [], []
189 189
190 190 for lfile in tocheck:
191 191 standin = lfutil.standin(lfile)
192 192 if inctx(standin, ctx1):
193 193 if ctx1[standin].data().strip() != \
194 194 lfutil.hashfile(self.wjoin(lfile)):
195 195 modified.append(lfile)
196 196 else:
197 197 clean.append(lfile)
198 198 else:
199 199 added.append(lfile)
200 200 finally:
201 201 # Replace the original ignore function
202 202 lfdirstate._ignore = orig_ignore
203 203
204 204 for standin in ctx1.manifest():
205 205 if not lfutil.isstandin(standin):
206 206 continue
207 207 lfile = lfutil.splitstandin(standin)
208 208 if not match(lfile):
209 209 continue
210 210 if lfile not in lfdirstate:
211 211 removed.append(lfile)
212 212
213 213 # Filter result lists
214 214 result = list(result)
215 215
216 216 # Largefiles are not really removed when they're
217 217 # still in the normal dirstate. Likewise, normal
218 218 # files are not really removed if it's still in
219 219 # lfdirstate. This happens in merges where files
220 220 # change type.
221 221 removed = [f for f in removed if f not in repo.dirstate]
222 222 result[2] = [f for f in result[2] if f not in lfdirstate]
223 223
224 224 # Unknown files
225 225 unknown = set(unknown).difference(ignored)
226 226 result[4] = [f for f in unknown
227 227 if (repo.dirstate[f] == '?' and
228 228 not lfutil.isstandin(f))]
229 229 # Ignored files were calculated earlier by the dirstate,
230 230 # and we already stripped out the largefiles from the list
231 231 result[5] = ignored
232 232 # combine normal files and largefiles
233 233 normals = [[fn for fn in filelist
234 234 if not lfutil.isstandin(fn)]
235 235 for filelist in result]
236 236 lfiles = (modified, added, removed, missing, [], [], clean)
237 237 result = [sorted(list1 + list2)
238 238 for (list1, list2) in zip(normals, lfiles)]
239 239 else:
240 240 def toname(f):
241 241 if lfutil.isstandin(f):
242 242 return lfutil.splitstandin(f)
243 243 return f
244 244 result = [[toname(f) for f in items] for items in result]
245 245
246 246 if not listunknown:
247 247 result[4] = []
248 248 if not listignored:
249 249 result[5] = []
250 250 if not listclean:
251 251 result[6] = []
252 252 self.lfstatus = True
253 253 return result
254 254
255 255 # As part of committing, copy all of the largefiles into the
256 256 # cache.
257 257 def commitctx(self, *args, **kwargs):
258 258 node = super(lfiles_repo, self).commitctx(*args, **kwargs)
259 ctx = self[node]
260 for filename in ctx.files():
261 if lfutil.isstandin(filename) and filename in ctx.manifest():
262 realfile = lfutil.splitstandin(filename)
263 lfutil.copytostore(self, ctx.node(), realfile)
264
259 lfutil.copyalltostore(self, node)
265 260 return node
266 261
267 262 # Before commit, largefile standins have not had their
268 263 # contents updated to reflect the hash of their largefile.
269 264 # Do that here.
270 265 def commit(self, text="", user=None, date=None, match=None,
271 266 force=False, editor=False, extra={}):
272 267 orig = super(lfiles_repo, self).commit
273 268
274 269 wlock = repo.wlock()
275 270 try:
276 271 # Case 0: Rebase
277 272 # We have to take the time to pull down the new largefiles now.
278 273 # Otherwise if we are rebasing, any largefiles that were
279 274 # modified in the destination changesets get overwritten, either
280 275 # by the rebase or in the first commit after the rebase.
281 276 # updatelfiles will update the dirstate to mark any pulled
282 277 # largefiles as modified
283 278 if getattr(repo, "_isrebasing", False):
284 279 lfcommands.updatelfiles(repo.ui, repo)
285 280 result = orig(text=text, user=user, date=date, match=match,
286 281 force=force, editor=editor, extra=extra)
287 282 return result
288 283 # Case 1: user calls commit with no specific files or
289 284 # include/exclude patterns: refresh and commit all files that
290 285 # are "dirty".
291 286 if ((match is None) or
292 287 (not match.anypats() and not match.files())):
293 288 # Spend a bit of time here to get a list of files we know
294 289 # are modified so we can compare only against those.
295 290 # It can cost a lot of time (several seconds)
296 291 # otherwise to update all standins if the largefiles are
297 292 # large.
298 293 lfdirstate = lfutil.openlfdirstate(ui, self)
299 294 dirtymatch = match_.always(repo.root, repo.getcwd())
300 295 s = lfdirstate.status(dirtymatch, [], False, False, False)
301 296 modifiedfiles = []
302 297 for i in s:
303 298 modifiedfiles.extend(i)
304 299 lfiles = lfutil.listlfiles(self)
305 300 # this only loops through largefiles that exist (not
306 301 # removed/renamed)
307 302 for lfile in lfiles:
308 303 if lfile in modifiedfiles:
309 304 if os.path.exists(self.wjoin(lfutil.standin(lfile))):
310 305 # this handles the case where a rebase is being
311 306 # performed and the working copy is not updated
312 307 # yet.
313 308 if os.path.exists(self.wjoin(lfile)):
314 309 lfutil.updatestandin(self,
315 310 lfutil.standin(lfile))
316 311 lfdirstate.normal(lfile)
317 312 for lfile in lfdirstate:
318 313 if lfile in modifiedfiles:
319 314 if not os.path.exists(
320 315 repo.wjoin(lfutil.standin(lfile))):
321 316 lfdirstate.drop(lfile)
322 317
323 318 result = orig(text=text, user=user, date=date, match=match,
324 319 force=force, editor=editor, extra=extra)
325 320 # This needs to be after commit; otherwise precommit hooks
326 321 # get the wrong status
327 322 lfdirstate.write()
328 323 return result
329 324
330 325 for f in match.files():
331 326 if lfutil.isstandin(f):
332 327 raise util.Abort(
333 328 _('file "%s" is a largefile standin') % f,
334 329 hint=('commit the largefile itself instead'))
335 330
336 331 # Case 2: user calls commit with specified patterns: refresh
337 332 # any matching big files.
338 333 smatcher = lfutil.composestandinmatcher(self, match)
339 334 standins = lfutil.dirstate_walk(self.dirstate, smatcher)
340 335
341 336 # No matching big files: get out of the way and pass control to
342 337 # the usual commit() method.
343 338 if not standins:
344 339 return orig(text=text, user=user, date=date, match=match,
345 340 force=force, editor=editor, extra=extra)
346 341
347 342 # Refresh all matching big files. It's possible that the
348 343 # commit will end up failing, in which case the big files will
349 344 # stay refreshed. No harm done: the user modified them and
350 345 # asked to commit them, so sooner or later we're going to
351 346 # refresh the standins. Might as well leave them refreshed.
352 347 lfdirstate = lfutil.openlfdirstate(ui, self)
353 348 for standin in standins:
354 349 lfile = lfutil.splitstandin(standin)
355 350 if lfdirstate[lfile] <> 'r':
356 351 lfutil.updatestandin(self, standin)
357 352 lfdirstate.normal(lfile)
358 353 else:
359 354 lfdirstate.drop(lfile)
360 355
361 356 # Cook up a new matcher that only matches regular files or
362 357 # standins corresponding to the big files requested by the
363 358 # user. Have to modify _files to prevent commit() from
364 359 # complaining "not tracked" for big files.
365 360 lfiles = lfutil.listlfiles(repo)
366 361 match = copy.copy(match)
367 362 orig_matchfn = match.matchfn
368 363
369 364 # Check both the list of largefiles and the list of
370 365 # standins because if a largefile was removed, it
371 366 # won't be in the list of largefiles at this point
372 367 match._files += sorted(standins)
373 368
374 369 actualfiles = []
375 370 for f in match._files:
376 371 fstandin = lfutil.standin(f)
377 372
378 373 # ignore known largefiles and standins
379 374 if f in lfiles or fstandin in standins:
380 375 continue
381 376
382 377 # append directory separator to avoid collisions
383 378 if not fstandin.endswith(os.sep):
384 379 fstandin += os.sep
385 380
386 381 # prevalidate matching standin directories
387 382 if util.any(st for st in match._files
388 383 if st.startswith(fstandin)):
389 384 continue
390 385 actualfiles.append(f)
391 386 match._files = actualfiles
392 387
393 388 def matchfn(f):
394 389 if orig_matchfn(f):
395 390 return f not in lfiles
396 391 else:
397 392 return f in standins
398 393
399 394 match.matchfn = matchfn
400 395 result = orig(text=text, user=user, date=date, match=match,
401 396 force=force, editor=editor, extra=extra)
402 397 # This needs to be after commit; otherwise precommit hooks
403 398 # get the wrong status
404 399 lfdirstate.write()
405 400 return result
406 401 finally:
407 402 wlock.release()
408 403
409 404 def push(self, remote, force=False, revs=None, newbranch=False):
410 405 o = lfutil.findoutgoing(repo, remote, force)
411 406 if o:
412 407 toupload = set()
413 408 o = repo.changelog.nodesbetween(o, revs)[0]
414 409 for n in o:
415 410 parents = [p for p in repo.changelog.parents(n)
416 411 if p != node_.nullid]
417 412 ctx = repo[n]
418 413 files = set(ctx.files())
419 414 if len(parents) == 2:
420 415 mc = ctx.manifest()
421 416 mp1 = ctx.parents()[0].manifest()
422 417 mp2 = ctx.parents()[1].manifest()
423 418 for f in mp1:
424 419 if f not in mc:
425 420 files.add(f)
426 421 for f in mp2:
427 422 if f not in mc:
428 423 files.add(f)
429 424 for f in mc:
430 425 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f,
431 426 None):
432 427 files.add(f)
433 428
434 429 toupload = toupload.union(
435 430 set([ctx[f].data().strip()
436 431 for f in files
437 432 if lfutil.isstandin(f) and f in ctx]))
438 433 lfcommands.uploadlfiles(ui, self, remote, toupload)
439 434 return super(lfiles_repo, self).push(remote, force, revs,
440 435 newbranch)
441 436
442 437 repo.__class__ = lfiles_repo
443 438
444 439 def checkrequireslfiles(ui, repo, **kwargs):
445 440 if 'largefiles' not in repo.requirements and util.any(
446 441 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
447 442 repo.requirements.add('largefiles')
448 443 repo._writerequirements()
449 444
450 445 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles)
451 446 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)
General Comments 0
You need to be logged in to leave comments. Login now