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