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