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