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