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