##// END OF EJS Templates
configitems: register the 'largefiles.usercache' config
Boris Feld -
r34758:8cf0a6cd default
parent child Browse files
Show More
@@ -1,151 +1,155
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 '''track large binary files
10 10
11 11 Large binary files tend to be not very compressible, not very
12 12 diffable, and not at all mergeable. Such files are not handled
13 13 efficiently by Mercurial's storage format (revlog), which is based on
14 14 compressed binary deltas; storing large binary files as regular
15 15 Mercurial files wastes bandwidth and disk space and increases
16 16 Mercurial's memory usage. The largefiles extension addresses these
17 17 problems by adding a centralized client-server layer on top of
18 18 Mercurial: largefiles live in a *central store* out on the network
19 19 somewhere, and you only fetch the revisions that you need when you
20 20 need them.
21 21
22 22 largefiles works by maintaining a "standin file" in .hglf/ for each
23 23 largefile. The standins are small (41 bytes: an SHA-1 hash plus
24 24 newline) and are tracked by Mercurial. Largefile revisions are
25 25 identified by the SHA-1 hash of their contents, which is written to
26 26 the standin. largefiles uses that revision ID to get/put largefile
27 27 revisions from/to the central store. This saves both disk space and
28 28 bandwidth, since you don't need to retrieve all historical revisions
29 29 of large files when you clone or pull.
30 30
31 31 To start a new repository or add new large binary files, just add
32 32 --large to your :hg:`add` command. For example::
33 33
34 34 $ dd if=/dev/urandom of=randomdata count=2000
35 35 $ hg add --large randomdata
36 36 $ hg commit -m "add randomdata as a largefile"
37 37
38 38 When you push a changeset that adds/modifies largefiles to a remote
39 39 repository, its largefile revisions will be uploaded along with it.
40 40 Note that the remote Mercurial must also have the largefiles extension
41 41 enabled for this to work.
42 42
43 43 When you pull a changeset that affects largefiles from a remote
44 44 repository, the largefiles for the changeset will by default not be
45 45 pulled down. However, when you update to such a revision, any
46 46 largefiles needed by that revision are downloaded and cached (if
47 47 they have never been downloaded before). One way to pull largefiles
48 48 when pulling is thus to use --update, which will update your working
49 49 copy to the latest pulled revision (and thereby downloading any new
50 50 largefiles).
51 51
52 52 If you want to pull largefiles you don't need for update yet, then
53 53 you can use pull with the `--lfrev` option or the :hg:`lfpull` command.
54 54
55 55 If you know you are pulling from a non-default location and want to
56 56 download all the largefiles that correspond to the new changesets at
57 57 the same time, then you can pull with `--lfrev "pulled()"`.
58 58
59 59 If you just want to ensure that you will have the largefiles needed to
60 60 merge or rebase with new heads that you are pulling, then you can pull
61 61 with `--lfrev "head(pulled())"` flag to pre-emptively download any largefiles
62 62 that are new in the heads you are pulling.
63 63
64 64 Keep in mind that network access may now be required to update to
65 65 changesets that you have not previously updated to. The nature of the
66 66 largefiles extension means that updating is no longer guaranteed to
67 67 be a local-only operation.
68 68
69 69 If you already have large files tracked by Mercurial without the
70 70 largefiles extension, you will need to convert your repository in
71 71 order to benefit from largefiles. This is done with the
72 72 :hg:`lfconvert` command::
73 73
74 74 $ hg lfconvert --size 10 oldrepo newrepo
75 75
76 76 In repositories that already have largefiles in them, any new file
77 77 over 10MB will automatically be added as a largefile. To change this
78 78 threshold, set ``largefiles.minsize`` in your Mercurial config file
79 79 to the minimum size in megabytes to track as a largefile, or use the
80 80 --lfsize option to the add command (also in megabytes)::
81 81
82 82 [largefiles]
83 83 minsize = 2
84 84
85 85 $ hg add --lfsize 2
86 86
87 87 The ``largefiles.patterns`` config option allows you to specify a list
88 88 of filename patterns (see :hg:`help patterns`) that should always be
89 89 tracked as largefiles::
90 90
91 91 [largefiles]
92 92 patterns =
93 93 *.jpg
94 94 re:.*\\.(png|bmp)$
95 95 library.zip
96 96 content/audio/*
97 97
98 98 Files that match one of these patterns will be added as largefiles
99 99 regardless of their size.
100 100
101 101 The ``largefiles.minsize`` and ``largefiles.patterns`` config options
102 102 will be ignored for any repositories not already containing a
103 103 largefile. To add the first largefile to a repository, you must
104 104 explicitly do so with the --large flag passed to the :hg:`add`
105 105 command.
106 106 '''
107 107 from __future__ import absolute_import
108 108
109 109 from mercurial import (
110 110 configitems,
111 111 hg,
112 112 localrepo,
113 113 registrar,
114 114 )
115 115
116 116 from . import (
117 117 lfcommands,
118 118 overrides,
119 119 proto,
120 120 reposetup,
121 121 uisetup as uisetupmod,
122 122 )
123 123
124 124 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
125 125 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
126 126 # be specifying the version(s) of Mercurial they are tested with, or
127 127 # leave the attribute unspecified.
128 128 testedwith = 'ships-with-hg-core'
129 129
130 130 configtable = {}
131 131 configitem = registrar.configitem(configtable)
132 132
133 133 configitem('largefiles', 'minsize',
134 134 default=configitems.dynamicdefault,
135 135 )
136 136 configitem('largefiles', 'patterns',
137 137 default=list,
138 138 )
139 configitem('largefiles', 'usercache',
140 default=None,
141 )
142
139 143 reposetup = reposetup.reposetup
140 144
141 145 def featuresetup(ui, supported):
142 146 # don't die on seeing a repo with the largefiles requirement
143 147 supported |= {'largefiles'}
144 148
145 149 def uisetup(ui):
146 150 localrepo.localrepository.featuresetupfuncs.add(featuresetup)
147 151 hg.wirepeersetupfuncs.append(proto.wirereposetup)
148 152 uisetupmod.uisetup(ui)
149 153
150 154 cmdtable = lfcommands.cmdtable
151 155 revsetpredicate = overrides.revsetpredicate
@@ -1,673 +1,673
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 from __future__ import absolute_import
11 11
12 12 import copy
13 13 import hashlib
14 14 import os
15 15 import stat
16 16
17 17 from mercurial.i18n import _
18 18
19 19 from mercurial import (
20 20 dirstate,
21 21 encoding,
22 22 error,
23 23 httpconnection,
24 24 match as matchmod,
25 25 node,
26 26 pycompat,
27 27 scmutil,
28 28 sparse,
29 29 util,
30 30 vfs as vfsmod,
31 31 )
32 32
33 33 shortname = '.hglf'
34 34 shortnameslash = shortname + '/'
35 35 longname = 'largefiles'
36 36
37 37 # -- Private worker functions ------------------------------------------
38 38
39 39 def getminsize(ui, assumelfiles, opt, default=10):
40 40 lfsize = opt
41 41 if not lfsize and assumelfiles:
42 42 lfsize = ui.config(longname, 'minsize', default=default)
43 43 if lfsize:
44 44 try:
45 45 lfsize = float(lfsize)
46 46 except ValueError:
47 47 raise error.Abort(_('largefiles: size must be number (not %s)\n')
48 48 % lfsize)
49 49 if lfsize is None:
50 50 raise error.Abort(_('minimum size for largefiles must be specified'))
51 51 return lfsize
52 52
53 53 def link(src, dest):
54 54 """Try to create hardlink - if that fails, efficiently make a copy."""
55 55 util.makedirs(os.path.dirname(dest))
56 56 try:
57 57 util.oslink(src, dest)
58 58 except OSError:
59 59 # if hardlinks fail, fallback on atomic copy
60 60 with open(src, 'rb') as srcf, util.atomictempfile(dest) as dstf:
61 61 for chunk in util.filechunkiter(srcf):
62 62 dstf.write(chunk)
63 63 os.chmod(dest, os.stat(src).st_mode)
64 64
65 65 def usercachepath(ui, hash):
66 66 '''Return the correct location in the "global" largefiles cache for a file
67 67 with the given hash.
68 68 This cache is used for sharing of largefiles across repositories - both
69 69 to preserve download bandwidth and storage space.'''
70 70 return os.path.join(_usercachedir(ui), hash)
71 71
72 72 def _usercachedir(ui):
73 73 '''Return the location of the "global" largefiles cache.'''
74 path = ui.configpath(longname, 'usercache', None)
74 path = ui.configpath(longname, 'usercache')
75 75 if path:
76 76 return path
77 77 if pycompat.iswindows:
78 78 appdata = encoding.environ.get('LOCALAPPDATA',\
79 79 encoding.environ.get('APPDATA'))
80 80 if appdata:
81 81 return os.path.join(appdata, longname)
82 82 elif pycompat.isdarwin:
83 83 home = encoding.environ.get('HOME')
84 84 if home:
85 85 return os.path.join(home, 'Library', 'Caches', longname)
86 86 elif pycompat.isposix:
87 87 path = encoding.environ.get('XDG_CACHE_HOME')
88 88 if path:
89 89 return os.path.join(path, longname)
90 90 home = encoding.environ.get('HOME')
91 91 if home:
92 92 return os.path.join(home, '.cache', longname)
93 93 else:
94 94 raise error.Abort(_('unknown operating system: %s\n')
95 95 % pycompat.osname)
96 96 raise error.Abort(_('unknown %s usercache location') % longname)
97 97
98 98 def inusercache(ui, hash):
99 99 path = usercachepath(ui, hash)
100 100 return os.path.exists(path)
101 101
102 102 def findfile(repo, hash):
103 103 '''Return store path of the largefile with the specified hash.
104 104 As a side effect, the file might be linked from user cache.
105 105 Return None if the file can't be found locally.'''
106 106 path, exists = findstorepath(repo, hash)
107 107 if exists:
108 108 repo.ui.note(_('found %s in store\n') % hash)
109 109 return path
110 110 elif inusercache(repo.ui, hash):
111 111 repo.ui.note(_('found %s in system cache\n') % hash)
112 112 path = storepath(repo, hash)
113 113 link(usercachepath(repo.ui, hash), path)
114 114 return path
115 115 return None
116 116
117 117 class largefilesdirstate(dirstate.dirstate):
118 118 def __getitem__(self, key):
119 119 return super(largefilesdirstate, self).__getitem__(unixpath(key))
120 120 def normal(self, f):
121 121 return super(largefilesdirstate, self).normal(unixpath(f))
122 122 def remove(self, f):
123 123 return super(largefilesdirstate, self).remove(unixpath(f))
124 124 def add(self, f):
125 125 return super(largefilesdirstate, self).add(unixpath(f))
126 126 def drop(self, f):
127 127 return super(largefilesdirstate, self).drop(unixpath(f))
128 128 def forget(self, f):
129 129 return super(largefilesdirstate, self).forget(unixpath(f))
130 130 def normallookup(self, f):
131 131 return super(largefilesdirstate, self).normallookup(unixpath(f))
132 132 def _ignore(self, f):
133 133 return False
134 134 def write(self, tr=False):
135 135 # (1) disable PENDING mode always
136 136 # (lfdirstate isn't yet managed as a part of the transaction)
137 137 # (2) avoid develwarn 'use dirstate.write with ....'
138 138 super(largefilesdirstate, self).write(None)
139 139
140 140 def openlfdirstate(ui, repo, create=True):
141 141 '''
142 142 Return a dirstate object that tracks largefiles: i.e. its root is
143 143 the repo root, but it is saved in .hg/largefiles/dirstate.
144 144 '''
145 145 vfs = repo.vfs
146 146 lfstoredir = longname
147 147 opener = vfsmod.vfs(vfs.join(lfstoredir))
148 148 lfdirstate = largefilesdirstate(opener, ui, repo.root,
149 149 repo.dirstate._validate,
150 150 lambda: sparse.matcher(repo))
151 151
152 152 # If the largefiles dirstate does not exist, populate and create
153 153 # it. This ensures that we create it on the first meaningful
154 154 # largefiles operation in a new clone.
155 155 if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
156 156 matcher = getstandinmatcher(repo)
157 157 standins = repo.dirstate.walk(matcher, subrepos=[], unknown=False,
158 158 ignored=False)
159 159
160 160 if len(standins) > 0:
161 161 vfs.makedirs(lfstoredir)
162 162
163 163 for standin in standins:
164 164 lfile = splitstandin(standin)
165 165 lfdirstate.normallookup(lfile)
166 166 return lfdirstate
167 167
168 168 def lfdirstatestatus(lfdirstate, repo):
169 169 pctx = repo['.']
170 170 match = matchmod.always(repo.root, repo.getcwd())
171 171 unsure, s = lfdirstate.status(match, subrepos=[], ignored=False,
172 172 clean=False, unknown=False)
173 173 modified, clean = s.modified, s.clean
174 174 for lfile in unsure:
175 175 try:
176 176 fctx = pctx[standin(lfile)]
177 177 except LookupError:
178 178 fctx = None
179 179 if not fctx or readasstandin(fctx) != 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 s
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, forcelocal=False):
199 199 '''Return true if a largefile with the given hash exists in the store'''
200 200 return os.path.exists(storepath(repo, hash, forcelocal))
201 201
202 202 def storepath(repo, hash, forcelocal=False):
203 203 '''Return the correct location in the repository largefiles store for a
204 204 file with the given hash.'''
205 205 if not forcelocal and repo.shared():
206 206 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
207 207 return repo.vfs.join(longname, hash)
208 208
209 209 def findstorepath(repo, hash):
210 210 '''Search through the local store path(s) to find the file for the given
211 211 hash. If the file is not found, its path in the primary store is returned.
212 212 The return value is a tuple of (path, exists(path)).
213 213 '''
214 214 # For shared repos, the primary store is in the share source. But for
215 215 # backward compatibility, force a lookup in the local store if it wasn't
216 216 # found in the share source.
217 217 path = storepath(repo, hash, False)
218 218
219 219 if instore(repo, hash):
220 220 return (path, True)
221 221 elif repo.shared() and instore(repo, hash, True):
222 222 return storepath(repo, hash, True), True
223 223
224 224 return (path, False)
225 225
226 226 def copyfromcache(repo, hash, filename):
227 227 '''Copy the specified largefile from the repo or system cache to
228 228 filename in the repository. Return true on success or false if the
229 229 file was not found in either cache (which should not happened:
230 230 this is meant to be called only after ensuring that the needed
231 231 largefile exists in the cache).'''
232 232 wvfs = repo.wvfs
233 233 path = findfile(repo, hash)
234 234 if path is None:
235 235 return False
236 236 wvfs.makedirs(wvfs.dirname(wvfs.join(filename)))
237 237 # The write may fail before the file is fully written, but we
238 238 # don't use atomic writes in the working copy.
239 239 with open(path, 'rb') as srcfd, wvfs(filename, 'wb') as destfd:
240 240 gothash = copyandhash(
241 241 util.filechunkiter(srcfd), destfd)
242 242 if gothash != hash:
243 243 repo.ui.warn(_('%s: data corruption in %s with hash %s\n')
244 244 % (filename, path, gothash))
245 245 wvfs.unlink(filename)
246 246 return False
247 247 return True
248 248
249 249 def copytostore(repo, ctx, file, fstandin):
250 250 wvfs = repo.wvfs
251 251 hash = readasstandin(ctx[fstandin])
252 252 if instore(repo, hash):
253 253 return
254 254 if wvfs.exists(file):
255 255 copytostoreabsolute(repo, wvfs.join(file), hash)
256 256 else:
257 257 repo.ui.warn(_("%s: largefile %s not available from local store\n") %
258 258 (file, hash))
259 259
260 260 def copyalltostore(repo, node):
261 261 '''Copy all largefiles in a given revision to the store'''
262 262
263 263 ctx = repo[node]
264 264 for filename in ctx.files():
265 265 realfile = splitstandin(filename)
266 266 if realfile is not None and filename in ctx.manifest():
267 267 copytostore(repo, ctx, realfile, filename)
268 268
269 269 def copytostoreabsolute(repo, file, hash):
270 270 if inusercache(repo.ui, hash):
271 271 link(usercachepath(repo.ui, hash), storepath(repo, hash))
272 272 else:
273 273 util.makedirs(os.path.dirname(storepath(repo, hash)))
274 274 with open(file, 'rb') as srcf:
275 275 with util.atomictempfile(storepath(repo, hash),
276 276 createmode=repo.store.createmode) as dstf:
277 277 for chunk in util.filechunkiter(srcf):
278 278 dstf.write(chunk)
279 279 linktousercache(repo, hash)
280 280
281 281 def linktousercache(repo, hash):
282 282 '''Link / copy the largefile with the specified hash from the store
283 283 to the cache.'''
284 284 path = usercachepath(repo.ui, hash)
285 285 link(storepath(repo, hash), path)
286 286
287 287 def getstandinmatcher(repo, rmatcher=None):
288 288 '''Return a match object that applies rmatcher to the standin directory'''
289 289 wvfs = repo.wvfs
290 290 standindir = shortname
291 291
292 292 # no warnings about missing files or directories
293 293 badfn = lambda f, msg: None
294 294
295 295 if rmatcher and not rmatcher.always():
296 296 pats = [wvfs.join(standindir, pat) for pat in rmatcher.files()]
297 297 if not pats:
298 298 pats = [wvfs.join(standindir)]
299 299 match = scmutil.match(repo[None], pats, badfn=badfn)
300 300 else:
301 301 # no patterns: relative to repo root
302 302 match = scmutil.match(repo[None], [wvfs.join(standindir)], badfn=badfn)
303 303 return match
304 304
305 305 def composestandinmatcher(repo, rmatcher):
306 306 '''Return a matcher that accepts standins corresponding to the
307 307 files accepted by rmatcher. Pass the list of files in the matcher
308 308 as the paths specified by the user.'''
309 309 smatcher = getstandinmatcher(repo, rmatcher)
310 310 isstandin = smatcher.matchfn
311 311 def composedmatchfn(f):
312 312 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
313 313 smatcher.matchfn = composedmatchfn
314 314
315 315 return smatcher
316 316
317 317 def standin(filename):
318 318 '''Return the repo-relative path to the standin for the specified big
319 319 file.'''
320 320 # Notes:
321 321 # 1) Some callers want an absolute path, but for instance addlargefiles
322 322 # needs it repo-relative so it can be passed to repo[None].add(). So
323 323 # leave it up to the caller to use repo.wjoin() to get an absolute path.
324 324 # 2) Join with '/' because that's what dirstate always uses, even on
325 325 # Windows. Change existing separator to '/' first in case we are
326 326 # passed filenames from an external source (like the command line).
327 327 return shortnameslash + util.pconvert(filename)
328 328
329 329 def isstandin(filename):
330 330 '''Return true if filename is a big file standin. filename must be
331 331 in Mercurial's internal form (slash-separated).'''
332 332 return filename.startswith(shortnameslash)
333 333
334 334 def splitstandin(filename):
335 335 # Split on / because that's what dirstate always uses, even on Windows.
336 336 # Change local separator to / first just in case we are passed filenames
337 337 # from an external source (like the command line).
338 338 bits = util.pconvert(filename).split('/', 1)
339 339 if len(bits) == 2 and bits[0] == shortname:
340 340 return bits[1]
341 341 else:
342 342 return None
343 343
344 344 def updatestandin(repo, lfile, standin):
345 345 """Re-calculate hash value of lfile and write it into standin
346 346
347 347 This assumes that "lfutil.standin(lfile) == standin", for efficiency.
348 348 """
349 349 file = repo.wjoin(lfile)
350 350 if repo.wvfs.exists(lfile):
351 351 hash = hashfile(file)
352 352 executable = getexecutable(file)
353 353 writestandin(repo, standin, hash, executable)
354 354 else:
355 355 raise error.Abort(_('%s: file not found!') % lfile)
356 356
357 357 def readasstandin(fctx):
358 358 '''read hex hash from given filectx of standin file
359 359
360 360 This encapsulates how "standin" data is stored into storage layer.'''
361 361 return fctx.data().strip()
362 362
363 363 def writestandin(repo, standin, hash, executable):
364 364 '''write hash to <repo.root>/<standin>'''
365 365 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
366 366
367 367 def copyandhash(instream, outfile):
368 368 '''Read bytes from instream (iterable) and write them to outfile,
369 369 computing the SHA-1 hash of the data along the way. Return the hash.'''
370 370 hasher = hashlib.sha1('')
371 371 for data in instream:
372 372 hasher.update(data)
373 373 outfile.write(data)
374 374 return hasher.hexdigest()
375 375
376 376 def hashfile(file):
377 377 if not os.path.exists(file):
378 378 return ''
379 379 with open(file, 'rb') as fd:
380 380 return hexsha1(fd)
381 381
382 382 def getexecutable(filename):
383 383 mode = os.stat(filename).st_mode
384 384 return ((mode & stat.S_IXUSR) and
385 385 (mode & stat.S_IXGRP) and
386 386 (mode & stat.S_IXOTH))
387 387
388 388 def urljoin(first, second, *arg):
389 389 def join(left, right):
390 390 if not left.endswith('/'):
391 391 left += '/'
392 392 if right.startswith('/'):
393 393 right = right[1:]
394 394 return left + right
395 395
396 396 url = join(first, second)
397 397 for a in arg:
398 398 url = join(url, a)
399 399 return url
400 400
401 401 def hexsha1(fileobj):
402 402 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
403 403 object data"""
404 404 h = hashlib.sha1()
405 405 for chunk in util.filechunkiter(fileobj):
406 406 h.update(chunk)
407 407 return h.hexdigest()
408 408
409 409 def httpsendfile(ui, filename):
410 410 return httpconnection.httpsendfile(ui, filename, 'rb')
411 411
412 412 def unixpath(path):
413 413 '''Return a version of path normalized for use with the lfdirstate.'''
414 414 return util.pconvert(os.path.normpath(path))
415 415
416 416 def islfilesrepo(repo):
417 417 '''Return true if the repo is a largefile repo.'''
418 418 if ('largefiles' in repo.requirements and
419 419 any(shortnameslash in f[0] for f in repo.store.datafiles())):
420 420 return True
421 421
422 422 return any(openlfdirstate(repo.ui, repo, False))
423 423
424 424 class storeprotonotcapable(Exception):
425 425 def __init__(self, storetypes):
426 426 self.storetypes = storetypes
427 427
428 428 def getstandinsstate(repo):
429 429 standins = []
430 430 matcher = getstandinmatcher(repo)
431 431 wctx = repo[None]
432 432 for standin in repo.dirstate.walk(matcher, subrepos=[], unknown=False,
433 433 ignored=False):
434 434 lfile = splitstandin(standin)
435 435 try:
436 436 hash = readasstandin(wctx[standin])
437 437 except IOError:
438 438 hash = None
439 439 standins.append((lfile, hash))
440 440 return standins
441 441
442 442 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
443 443 lfstandin = standin(lfile)
444 444 if lfstandin in repo.dirstate:
445 445 stat = repo.dirstate._map[lfstandin]
446 446 state, mtime = stat[0], stat[3]
447 447 else:
448 448 state, mtime = '?', -1
449 449 if state == 'n':
450 450 if (normallookup or mtime < 0 or
451 451 not repo.wvfs.exists(lfile)):
452 452 # state 'n' doesn't ensure 'clean' in this case
453 453 lfdirstate.normallookup(lfile)
454 454 else:
455 455 lfdirstate.normal(lfile)
456 456 elif state == 'm':
457 457 lfdirstate.normallookup(lfile)
458 458 elif state == 'r':
459 459 lfdirstate.remove(lfile)
460 460 elif state == 'a':
461 461 lfdirstate.add(lfile)
462 462 elif state == '?':
463 463 lfdirstate.drop(lfile)
464 464
465 465 def markcommitted(orig, ctx, node):
466 466 repo = ctx.repo()
467 467
468 468 orig(node)
469 469
470 470 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
471 471 # because files coming from the 2nd parent are omitted in the latter.
472 472 #
473 473 # The former should be used to get targets of "synclfdirstate",
474 474 # because such files:
475 475 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
476 476 # - have to be marked as "n" after commit, but
477 477 # - aren't listed in "repo[node].files()"
478 478
479 479 lfdirstate = openlfdirstate(repo.ui, repo)
480 480 for f in ctx.files():
481 481 lfile = splitstandin(f)
482 482 if lfile is not None:
483 483 synclfdirstate(repo, lfdirstate, lfile, False)
484 484 lfdirstate.write()
485 485
486 486 # As part of committing, copy all of the largefiles into the cache.
487 487 #
488 488 # Using "node" instead of "ctx" implies additional "repo[node]"
489 489 # lookup while copyalltostore(), but can omit redundant check for
490 490 # files comming from the 2nd parent, which should exist in store
491 491 # at merging.
492 492 copyalltostore(repo, node)
493 493
494 494 def getlfilestoupdate(oldstandins, newstandins):
495 495 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
496 496 filelist = []
497 497 for f in changedstandins:
498 498 if f[0] not in filelist:
499 499 filelist.append(f[0])
500 500 return filelist
501 501
502 502 def getlfilestoupload(repo, missing, addfunc):
503 503 for i, n in enumerate(missing):
504 504 repo.ui.progress(_('finding outgoing largefiles'), i,
505 505 unit=_('revisions'), total=len(missing))
506 506 parents = [p for p in repo[n].parents() if p != node.nullid]
507 507
508 508 oldlfstatus = repo.lfstatus
509 509 repo.lfstatus = False
510 510 try:
511 511 ctx = repo[n]
512 512 finally:
513 513 repo.lfstatus = oldlfstatus
514 514
515 515 files = set(ctx.files())
516 516 if len(parents) == 2:
517 517 mc = ctx.manifest()
518 518 mp1 = ctx.parents()[0].manifest()
519 519 mp2 = ctx.parents()[1].manifest()
520 520 for f in mp1:
521 521 if f not in mc:
522 522 files.add(f)
523 523 for f in mp2:
524 524 if f not in mc:
525 525 files.add(f)
526 526 for f in mc:
527 527 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
528 528 files.add(f)
529 529 for fn in files:
530 530 if isstandin(fn) and fn in ctx:
531 531 addfunc(fn, readasstandin(ctx[fn]))
532 532 repo.ui.progress(_('finding outgoing largefiles'), None)
533 533
534 534 def updatestandinsbymatch(repo, match):
535 535 '''Update standins in the working directory according to specified match
536 536
537 537 This returns (possibly modified) ``match`` object to be used for
538 538 subsequent commit process.
539 539 '''
540 540
541 541 ui = repo.ui
542 542
543 543 # Case 1: user calls commit with no specific files or
544 544 # include/exclude patterns: refresh and commit all files that
545 545 # are "dirty".
546 546 if match is None or match.always():
547 547 # Spend a bit of time here to get a list of files we know
548 548 # are modified so we can compare only against those.
549 549 # It can cost a lot of time (several seconds)
550 550 # otherwise to update all standins if the largefiles are
551 551 # large.
552 552 lfdirstate = openlfdirstate(ui, repo)
553 553 dirtymatch = matchmod.always(repo.root, repo.getcwd())
554 554 unsure, s = lfdirstate.status(dirtymatch, subrepos=[], ignored=False,
555 555 clean=False, unknown=False)
556 556 modifiedfiles = unsure + s.modified + s.added + s.removed
557 557 lfiles = listlfiles(repo)
558 558 # this only loops through largefiles that exist (not
559 559 # removed/renamed)
560 560 for lfile in lfiles:
561 561 if lfile in modifiedfiles:
562 562 fstandin = standin(lfile)
563 563 if repo.wvfs.exists(fstandin):
564 564 # this handles the case where a rebase is being
565 565 # performed and the working copy is not updated
566 566 # yet.
567 567 if repo.wvfs.exists(lfile):
568 568 updatestandin(repo, lfile, fstandin)
569 569
570 570 return match
571 571
572 572 lfiles = listlfiles(repo)
573 573 match._files = repo._subdirlfs(match.files(), lfiles)
574 574
575 575 # Case 2: user calls commit with specified patterns: refresh
576 576 # any matching big files.
577 577 smatcher = composestandinmatcher(repo, match)
578 578 standins = repo.dirstate.walk(smatcher, subrepos=[], unknown=False,
579 579 ignored=False)
580 580
581 581 # No matching big files: get out of the way and pass control to
582 582 # the usual commit() method.
583 583 if not standins:
584 584 return match
585 585
586 586 # Refresh all matching big files. It's possible that the
587 587 # commit will end up failing, in which case the big files will
588 588 # stay refreshed. No harm done: the user modified them and
589 589 # asked to commit them, so sooner or later we're going to
590 590 # refresh the standins. Might as well leave them refreshed.
591 591 lfdirstate = openlfdirstate(ui, repo)
592 592 for fstandin in standins:
593 593 lfile = splitstandin(fstandin)
594 594 if lfdirstate[lfile] != 'r':
595 595 updatestandin(repo, lfile, fstandin)
596 596
597 597 # Cook up a new matcher that only matches regular files or
598 598 # standins corresponding to the big files requested by the
599 599 # user. Have to modify _files to prevent commit() from
600 600 # complaining "not tracked" for big files.
601 601 match = copy.copy(match)
602 602 origmatchfn = match.matchfn
603 603
604 604 # Check both the list of largefiles and the list of
605 605 # standins because if a largefile was removed, it
606 606 # won't be in the list of largefiles at this point
607 607 match._files += sorted(standins)
608 608
609 609 actualfiles = []
610 610 for f in match._files:
611 611 fstandin = standin(f)
612 612
613 613 # For largefiles, only one of the normal and standin should be
614 614 # committed (except if one of them is a remove). In the case of a
615 615 # standin removal, drop the normal file if it is unknown to dirstate.
616 616 # Thus, skip plain largefile names but keep the standin.
617 617 if f in lfiles or fstandin in standins:
618 618 if repo.dirstate[fstandin] != 'r':
619 619 if repo.dirstate[f] != 'r':
620 620 continue
621 621 elif repo.dirstate[f] == '?':
622 622 continue
623 623
624 624 actualfiles.append(f)
625 625 match._files = actualfiles
626 626
627 627 def matchfn(f):
628 628 if origmatchfn(f):
629 629 return f not in lfiles
630 630 else:
631 631 return f in standins
632 632
633 633 match.matchfn = matchfn
634 634
635 635 return match
636 636
637 637 class automatedcommithook(object):
638 638 '''Stateful hook to update standins at the 1st commit of resuming
639 639
640 640 For efficiency, updating standins in the working directory should
641 641 be avoided while automated committing (like rebase, transplant and
642 642 so on), because they should be updated before committing.
643 643
644 644 But the 1st commit of resuming automated committing (e.g. ``rebase
645 645 --continue``) should update them, because largefiles may be
646 646 modified manually.
647 647 '''
648 648 def __init__(self, resuming):
649 649 self.resuming = resuming
650 650
651 651 def __call__(self, repo, match):
652 652 if self.resuming:
653 653 self.resuming = False # avoids updating at subsequent commits
654 654 return updatestandinsbymatch(repo, match)
655 655 else:
656 656 return match
657 657
658 658 def getstatuswriter(ui, repo, forcibly=None):
659 659 '''Return the function to write largefiles specific status out
660 660
661 661 If ``forcibly`` is ``None``, this returns the last element of
662 662 ``repo._lfstatuswriters`` as "default" writer function.
663 663
664 664 Otherwise, this returns the function to always write out (or
665 665 ignore if ``not forcibly``) status.
666 666 '''
667 667 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
668 668 return repo._lfstatuswriters[-1]
669 669 else:
670 670 if forcibly:
671 671 return ui.status # forcibly WRITE OUT
672 672 else:
673 673 return lambda *msg, **opts: None # forcibly IGNORE
General Comments 0
You need to be logged in to leave comments. Login now