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