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