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