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