##// END OF EJS Templates
largefiles: factor out a copyalltostore() function
Dan Villiom Podlaski Christiansen -
r15796:3e5b6045 default
parent child Browse files
Show More
@@ -1,450 +1,460 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 errno
12 import errno
13 import platform
13 import platform
14 import shutil
14 import shutil
15 import stat
15 import stat
16 import tempfile
16 import tempfile
17
17
18 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
19 from mercurial.i18n import _
19 from mercurial.i18n import _
20
20
21 shortname = '.hglf'
21 shortname = '.hglf'
22 longname = 'largefiles'
22 longname = 'largefiles'
23
23
24
24
25 # -- Portability wrappers ----------------------------------------------
25 # -- Portability wrappers ----------------------------------------------
26
26
27 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
27 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
28 return dirstate.walk(matcher, [], unknown, ignored)
28 return dirstate.walk(matcher, [], unknown, ignored)
29
29
30 def repo_add(repo, list):
30 def repo_add(repo, list):
31 add = repo[None].add
31 add = repo[None].add
32 return add(list)
32 return add(list)
33
33
34 def repo_remove(repo, list, unlink=False):
34 def repo_remove(repo, list, unlink=False):
35 def remove(list, unlink):
35 def remove(list, unlink):
36 wlock = repo.wlock()
36 wlock = repo.wlock()
37 try:
37 try:
38 if unlink:
38 if unlink:
39 for f in list:
39 for f in list:
40 try:
40 try:
41 util.unlinkpath(repo.wjoin(f))
41 util.unlinkpath(repo.wjoin(f))
42 except OSError, inst:
42 except OSError, inst:
43 if inst.errno != errno.ENOENT:
43 if inst.errno != errno.ENOENT:
44 raise
44 raise
45 repo[None].forget(list)
45 repo[None].forget(list)
46 finally:
46 finally:
47 wlock.release()
47 wlock.release()
48 return remove(list, unlink=unlink)
48 return remove(list, unlink=unlink)
49
49
50 def repo_forget(repo, list):
50 def repo_forget(repo, list):
51 forget = repo[None].forget
51 forget = repo[None].forget
52 return forget(list)
52 return forget(list)
53
53
54 def findoutgoing(repo, remote, force):
54 def findoutgoing(repo, remote, force):
55 from mercurial import discovery
55 from mercurial import discovery
56 common, _anyinc, _heads = discovery.findcommonincoming(repo,
56 common, _anyinc, _heads = discovery.findcommonincoming(repo,
57 remote, force=force)
57 remote, force=force)
58 return repo.changelog.findmissing(common)
58 return repo.changelog.findmissing(common)
59
59
60 # -- Private worker functions ------------------------------------------
60 # -- Private worker functions ------------------------------------------
61
61
62 def getminsize(ui, assumelfiles, opt, default=10):
62 def getminsize(ui, assumelfiles, opt, default=10):
63 lfsize = opt
63 lfsize = opt
64 if not lfsize and assumelfiles:
64 if not lfsize and assumelfiles:
65 lfsize = ui.config(longname, 'minsize', default=default)
65 lfsize = ui.config(longname, 'minsize', default=default)
66 if lfsize:
66 if lfsize:
67 try:
67 try:
68 lfsize = float(lfsize)
68 lfsize = float(lfsize)
69 except ValueError:
69 except ValueError:
70 raise util.Abort(_('largefiles: size must be number (not %s)\n')
70 raise util.Abort(_('largefiles: size must be number (not %s)\n')
71 % lfsize)
71 % lfsize)
72 if lfsize is None:
72 if lfsize is None:
73 raise util.Abort(_('minimum size for largefiles must be specified'))
73 raise util.Abort(_('minimum size for largefiles must be specified'))
74 return lfsize
74 return lfsize
75
75
76 def link(src, dest):
76 def link(src, dest):
77 try:
77 try:
78 util.oslink(src, dest)
78 util.oslink(src, dest)
79 except OSError:
79 except OSError:
80 # if hardlinks fail, fallback on atomic copy
80 # if hardlinks fail, fallback on atomic copy
81 dst = util.atomictempfile(dest)
81 dst = util.atomictempfile(dest)
82 for chunk in util.filechunkiter(open(src, 'rb')):
82 for chunk in util.filechunkiter(open(src, 'rb')):
83 dst.write(chunk)
83 dst.write(chunk)
84 dst.close()
84 dst.close()
85 os.chmod(dest, os.stat(src).st_mode)
85 os.chmod(dest, os.stat(src).st_mode)
86
86
87 def usercachepath(ui, hash):
87 def usercachepath(ui, hash):
88 path = ui.configpath(longname, 'usercache', None)
88 path = ui.configpath(longname, 'usercache', None)
89 if path:
89 if path:
90 path = os.path.join(path, hash)
90 path = os.path.join(path, hash)
91 else:
91 else:
92 if os.name == 'nt':
92 if os.name == 'nt':
93 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
93 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
94 if appdata:
94 if appdata:
95 path = os.path.join(appdata, longname, hash)
95 path = os.path.join(appdata, longname, hash)
96 elif platform.system() == 'Darwin':
96 elif platform.system() == 'Darwin':
97 home = os.getenv('HOME')
97 home = os.getenv('HOME')
98 if home:
98 if home:
99 path = os.path.join(home, 'Library', 'Caches',
99 path = os.path.join(home, 'Library', 'Caches',
100 longname, hash)
100 longname, hash)
101 elif os.name == 'posix':
101 elif os.name == 'posix':
102 path = os.getenv('XDG_CACHE_HOME')
102 path = os.getenv('XDG_CACHE_HOME')
103 if path:
103 if path:
104 path = os.path.join(path, longname, hash)
104 path = os.path.join(path, longname, hash)
105 else:
105 else:
106 home = os.getenv('HOME')
106 home = os.getenv('HOME')
107 if home:
107 if home:
108 path = os.path.join(home, '.cache', longname, hash)
108 path = os.path.join(home, '.cache', longname, hash)
109 else:
109 else:
110 raise util.Abort(_('unknown operating system: %s\n') % os.name)
110 raise util.Abort(_('unknown operating system: %s\n') % os.name)
111 return path
111 return path
112
112
113 def inusercache(ui, hash):
113 def inusercache(ui, hash):
114 path = usercachepath(ui, hash)
114 path = usercachepath(ui, hash)
115 return path and os.path.exists(path)
115 return path and os.path.exists(path)
116
116
117 def findfile(repo, hash):
117 def findfile(repo, hash):
118 if instore(repo, hash):
118 if instore(repo, hash):
119 repo.ui.note(_('Found %s in store\n') % hash)
119 repo.ui.note(_('Found %s in store\n') % hash)
120 elif inusercache(repo.ui, hash):
120 elif inusercache(repo.ui, hash):
121 repo.ui.note(_('Found %s in system cache\n') % hash)
121 repo.ui.note(_('Found %s in system cache\n') % hash)
122 path = storepath(repo, hash)
122 path = storepath(repo, hash)
123 util.makedirs(os.path.dirname(path))
123 util.makedirs(os.path.dirname(path))
124 link(usercachepath(repo.ui, hash), path)
124 link(usercachepath(repo.ui, hash), path)
125 else:
125 else:
126 return None
126 return None
127 return storepath(repo, hash)
127 return storepath(repo, hash)
128
128
129 class largefiles_dirstate(dirstate.dirstate):
129 class largefiles_dirstate(dirstate.dirstate):
130 def __getitem__(self, key):
130 def __getitem__(self, key):
131 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
131 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
132 def normal(self, f):
132 def normal(self, f):
133 return super(largefiles_dirstate, self).normal(unixpath(f))
133 return super(largefiles_dirstate, self).normal(unixpath(f))
134 def remove(self, f):
134 def remove(self, f):
135 return super(largefiles_dirstate, self).remove(unixpath(f))
135 return super(largefiles_dirstate, self).remove(unixpath(f))
136 def add(self, f):
136 def add(self, f):
137 return super(largefiles_dirstate, self).add(unixpath(f))
137 return super(largefiles_dirstate, self).add(unixpath(f))
138 def drop(self, f):
138 def drop(self, f):
139 return super(largefiles_dirstate, self).drop(unixpath(f))
139 return super(largefiles_dirstate, self).drop(unixpath(f))
140 def forget(self, f):
140 def forget(self, f):
141 return super(largefiles_dirstate, self).forget(unixpath(f))
141 return super(largefiles_dirstate, self).forget(unixpath(f))
142 def normallookup(self, f):
142 def normallookup(self, f):
143 return super(largefiles_dirstate, self).normallookup(unixpath(f))
143 return super(largefiles_dirstate, self).normallookup(unixpath(f))
144
144
145 def openlfdirstate(ui, repo):
145 def openlfdirstate(ui, repo):
146 '''
146 '''
147 Return a dirstate object that tracks largefiles: i.e. its root is
147 Return a dirstate object that tracks largefiles: i.e. its root is
148 the repo root, but it is saved in .hg/largefiles/dirstate.
148 the repo root, but it is saved in .hg/largefiles/dirstate.
149 '''
149 '''
150 admin = repo.join(longname)
150 admin = repo.join(longname)
151 opener = scmutil.opener(admin)
151 opener = scmutil.opener(admin)
152 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
152 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
153 repo.dirstate._validate)
153 repo.dirstate._validate)
154
154
155 # If the largefiles dirstate does not exist, populate and create
155 # If the largefiles dirstate does not exist, populate and create
156 # it. This ensures that we create it on the first meaningful
156 # it. This ensures that we create it on the first meaningful
157 # largefiles operation in a new clone.
157 # largefiles operation in a new clone.
158 if not os.path.exists(os.path.join(admin, 'dirstate')):
158 if not os.path.exists(os.path.join(admin, 'dirstate')):
159 util.makedirs(admin)
159 util.makedirs(admin)
160 matcher = getstandinmatcher(repo)
160 matcher = getstandinmatcher(repo)
161 for standin in dirstate_walk(repo.dirstate, matcher):
161 for standin in dirstate_walk(repo.dirstate, matcher):
162 lfile = splitstandin(standin)
162 lfile = splitstandin(standin)
163 hash = readstandin(repo, lfile)
163 hash = readstandin(repo, lfile)
164 lfdirstate.normallookup(lfile)
164 lfdirstate.normallookup(lfile)
165 try:
165 try:
166 if hash == hashfile(repo.wjoin(lfile)):
166 if hash == hashfile(repo.wjoin(lfile)):
167 lfdirstate.normal(lfile)
167 lfdirstate.normal(lfile)
168 except OSError, err:
168 except OSError, err:
169 if err.errno != errno.ENOENT:
169 if err.errno != errno.ENOENT:
170 raise
170 raise
171 return lfdirstate
171 return lfdirstate
172
172
173 def lfdirstate_status(lfdirstate, repo, rev):
173 def lfdirstate_status(lfdirstate, repo, rev):
174 match = match_.always(repo.root, repo.getcwd())
174 match = match_.always(repo.root, repo.getcwd())
175 s = lfdirstate.status(match, [], False, False, False)
175 s = lfdirstate.status(match, [], False, False, False)
176 unsure, modified, added, removed, missing, unknown, ignored, clean = s
176 unsure, modified, added, removed, missing, unknown, ignored, clean = s
177 for lfile in unsure:
177 for lfile in unsure:
178 if repo[rev][standin(lfile)].data().strip() != \
178 if repo[rev][standin(lfile)].data().strip() != \
179 hashfile(repo.wjoin(lfile)):
179 hashfile(repo.wjoin(lfile)):
180 modified.append(lfile)
180 modified.append(lfile)
181 else:
181 else:
182 clean.append(lfile)
182 clean.append(lfile)
183 lfdirstate.normal(lfile)
183 lfdirstate.normal(lfile)
184 return (modified, added, removed, missing, unknown, ignored, clean)
184 return (modified, added, removed, missing, unknown, ignored, clean)
185
185
186 def listlfiles(repo, rev=None, matcher=None):
186 def listlfiles(repo, rev=None, matcher=None):
187 '''return a list of largefiles in the working copy or the
187 '''return a list of largefiles in the working copy or the
188 specified changeset'''
188 specified changeset'''
189
189
190 if matcher is None:
190 if matcher is None:
191 matcher = getstandinmatcher(repo)
191 matcher = getstandinmatcher(repo)
192
192
193 # ignore unknown files in working directory
193 # ignore unknown files in working directory
194 return [splitstandin(f)
194 return [splitstandin(f)
195 for f in repo[rev].walk(matcher)
195 for f in repo[rev].walk(matcher)
196 if rev is not None or repo.dirstate[f] != '?']
196 if rev is not None or repo.dirstate[f] != '?']
197
197
198 def instore(repo, hash):
198 def instore(repo, hash):
199 return os.path.exists(storepath(repo, hash))
199 return os.path.exists(storepath(repo, hash))
200
200
201 def storepath(repo, hash):
201 def storepath(repo, hash):
202 return repo.join(os.path.join(longname, hash))
202 return repo.join(os.path.join(longname, hash))
203
203
204 def copyfromcache(repo, hash, filename):
204 def copyfromcache(repo, hash, filename):
205 '''Copy the specified largefile from the repo or system cache to
205 '''Copy the specified largefile from the repo or system cache to
206 filename in the repository. Return true on success or false if the
206 filename in the repository. Return true on success or false if the
207 file was not found in either cache (which should not happened:
207 file was not found in either cache (which should not happened:
208 this is meant to be called only after ensuring that the needed
208 this is meant to be called only after ensuring that the needed
209 largefile exists in the cache).'''
209 largefile exists in the cache).'''
210 path = findfile(repo, hash)
210 path = findfile(repo, hash)
211 if path is None:
211 if path is None:
212 return False
212 return False
213 util.makedirs(os.path.dirname(repo.wjoin(filename)))
213 util.makedirs(os.path.dirname(repo.wjoin(filename)))
214 # The write may fail before the file is fully written, but we
214 # The write may fail before the file is fully written, but we
215 # don't use atomic writes in the working copy.
215 # don't use atomic writes in the working copy.
216 shutil.copy(path, repo.wjoin(filename))
216 shutil.copy(path, repo.wjoin(filename))
217 return True
217 return True
218
218
219 def copytostore(repo, rev, file, uploaded=False):
219 def copytostore(repo, rev, file, uploaded=False):
220 hash = readstandin(repo, file)
220 hash = readstandin(repo, file)
221 if instore(repo, hash):
221 if instore(repo, hash):
222 return
222 return
223 copytostoreabsolute(repo, repo.wjoin(file), hash)
223 copytostoreabsolute(repo, repo.wjoin(file), hash)
224
224
225 def copyalltostore(repo, node):
226 '''Copy all largefiles in a given revision to the store'''
227
228 ctx = repo[node]
229 for filename in ctx.files():
230 if isstandin(filename) and filename in ctx.manifest():
231 realfile = splitstandin(filename)
232 copytostore(repo, ctx.node(), realfile)
233
234
225 def copytostoreabsolute(repo, file, hash):
235 def copytostoreabsolute(repo, file, hash):
226 util.makedirs(os.path.dirname(storepath(repo, hash)))
236 util.makedirs(os.path.dirname(storepath(repo, hash)))
227 if inusercache(repo.ui, hash):
237 if inusercache(repo.ui, hash):
228 link(usercachepath(repo.ui, hash), storepath(repo, hash))
238 link(usercachepath(repo.ui, hash), storepath(repo, hash))
229 else:
239 else:
230 dst = util.atomictempfile(storepath(repo, hash))
240 dst = util.atomictempfile(storepath(repo, hash))
231 for chunk in util.filechunkiter(open(file, 'rb')):
241 for chunk in util.filechunkiter(open(file, 'rb')):
232 dst.write(chunk)
242 dst.write(chunk)
233 dst.close()
243 dst.close()
234 util.copymode(file, storepath(repo, hash))
244 util.copymode(file, storepath(repo, hash))
235 linktousercache(repo, hash)
245 linktousercache(repo, hash)
236
246
237 def linktousercache(repo, hash):
247 def linktousercache(repo, hash):
238 path = usercachepath(repo.ui, hash)
248 path = usercachepath(repo.ui, hash)
239 if path:
249 if path:
240 util.makedirs(os.path.dirname(path))
250 util.makedirs(os.path.dirname(path))
241 link(storepath(repo, hash), path)
251 link(storepath(repo, hash), path)
242
252
243 def getstandinmatcher(repo, pats=[], opts={}):
253 def getstandinmatcher(repo, pats=[], opts={}):
244 '''Return a match object that applies pats to the standin directory'''
254 '''Return a match object that applies pats to the standin directory'''
245 standindir = repo.pathto(shortname)
255 standindir = repo.pathto(shortname)
246 if pats:
256 if pats:
247 # patterns supplied: search standin directory relative to current dir
257 # patterns supplied: search standin directory relative to current dir
248 cwd = repo.getcwd()
258 cwd = repo.getcwd()
249 if os.path.isabs(cwd):
259 if os.path.isabs(cwd):
250 # cwd is an absolute path for hg -R <reponame>
260 # cwd is an absolute path for hg -R <reponame>
251 # work relative to the repository root in this case
261 # work relative to the repository root in this case
252 cwd = ''
262 cwd = ''
253 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
263 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
254 elif os.path.isdir(standindir):
264 elif os.path.isdir(standindir):
255 # no patterns: relative to repo root
265 # no patterns: relative to repo root
256 pats = [standindir]
266 pats = [standindir]
257 else:
267 else:
258 # no patterns and no standin dir: return matcher that matches nothing
268 # no patterns and no standin dir: return matcher that matches nothing
259 match = match_.match(repo.root, None, [], exact=True)
269 match = match_.match(repo.root, None, [], exact=True)
260 match.matchfn = lambda f: False
270 match.matchfn = lambda f: False
261 return match
271 return match
262 return getmatcher(repo, pats, opts, showbad=False)
272 return getmatcher(repo, pats, opts, showbad=False)
263
273
264 def getmatcher(repo, pats=[], opts={}, showbad=True):
274 def getmatcher(repo, pats=[], opts={}, showbad=True):
265 '''Wrapper around scmutil.match() that adds showbad: if false,
275 '''Wrapper around scmutil.match() that adds showbad: if false,
266 neuter the match object's bad() method so it does not print any
276 neuter the match object's bad() method so it does not print any
267 warnings about missing files or directories.'''
277 warnings about missing files or directories.'''
268 match = scmutil.match(repo[None], pats, opts)
278 match = scmutil.match(repo[None], pats, opts)
269
279
270 if not showbad:
280 if not showbad:
271 match.bad = lambda f, msg: None
281 match.bad = lambda f, msg: None
272 return match
282 return match
273
283
274 def composestandinmatcher(repo, rmatcher):
284 def composestandinmatcher(repo, rmatcher):
275 '''Return a matcher that accepts standins corresponding to the
285 '''Return a matcher that accepts standins corresponding to the
276 files accepted by rmatcher. Pass the list of files in the matcher
286 files accepted by rmatcher. Pass the list of files in the matcher
277 as the paths specified by the user.'''
287 as the paths specified by the user.'''
278 smatcher = getstandinmatcher(repo, rmatcher.files())
288 smatcher = getstandinmatcher(repo, rmatcher.files())
279 isstandin = smatcher.matchfn
289 isstandin = smatcher.matchfn
280 def composed_matchfn(f):
290 def composed_matchfn(f):
281 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
291 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
282 smatcher.matchfn = composed_matchfn
292 smatcher.matchfn = composed_matchfn
283
293
284 return smatcher
294 return smatcher
285
295
286 def standin(filename):
296 def standin(filename):
287 '''Return the repo-relative path to the standin for the specified big
297 '''Return the repo-relative path to the standin for the specified big
288 file.'''
298 file.'''
289 # Notes:
299 # Notes:
290 # 1) Most callers want an absolute path, but _create_standin() needs
300 # 1) Most callers want an absolute path, but _create_standin() needs
291 # it repo-relative so lfadd() can pass it to repo_add(). So leave
301 # it repo-relative so lfadd() can pass it to repo_add(). So leave
292 # it up to the caller to use repo.wjoin() to get an absolute path.
302 # it up to the caller to use repo.wjoin() to get an absolute path.
293 # 2) Join with '/' because that's what dirstate always uses, even on
303 # 2) Join with '/' because that's what dirstate always uses, even on
294 # Windows. Change existing separator to '/' first in case we are
304 # Windows. Change existing separator to '/' first in case we are
295 # passed filenames from an external source (like the command line).
305 # passed filenames from an external source (like the command line).
296 return shortname + '/' + filename.replace(os.sep, '/')
306 return shortname + '/' + filename.replace(os.sep, '/')
297
307
298 def isstandin(filename):
308 def isstandin(filename):
299 '''Return true if filename is a big file standin. filename must be
309 '''Return true if filename is a big file standin. filename must be
300 in Mercurial's internal form (slash-separated).'''
310 in Mercurial's internal form (slash-separated).'''
301 return filename.startswith(shortname + '/')
311 return filename.startswith(shortname + '/')
302
312
303 def splitstandin(filename):
313 def splitstandin(filename):
304 # Split on / because that's what dirstate always uses, even on Windows.
314 # Split on / because that's what dirstate always uses, even on Windows.
305 # Change local separator to / first just in case we are passed filenames
315 # Change local separator to / first just in case we are passed filenames
306 # from an external source (like the command line).
316 # from an external source (like the command line).
307 bits = filename.replace(os.sep, '/').split('/', 1)
317 bits = filename.replace(os.sep, '/').split('/', 1)
308 if len(bits) == 2 and bits[0] == shortname:
318 if len(bits) == 2 and bits[0] == shortname:
309 return bits[1]
319 return bits[1]
310 else:
320 else:
311 return None
321 return None
312
322
313 def updatestandin(repo, standin):
323 def updatestandin(repo, standin):
314 file = repo.wjoin(splitstandin(standin))
324 file = repo.wjoin(splitstandin(standin))
315 if os.path.exists(file):
325 if os.path.exists(file):
316 hash = hashfile(file)
326 hash = hashfile(file)
317 executable = getexecutable(file)
327 executable = getexecutable(file)
318 writestandin(repo, standin, hash, executable)
328 writestandin(repo, standin, hash, executable)
319
329
320 def readstandin(repo, filename, node=None):
330 def readstandin(repo, filename, node=None):
321 '''read hex hash from standin for filename at given node, or working
331 '''read hex hash from standin for filename at given node, or working
322 directory if no node is given'''
332 directory if no node is given'''
323 return repo[node][standin(filename)].data().strip()
333 return repo[node][standin(filename)].data().strip()
324
334
325 def writestandin(repo, standin, hash, executable):
335 def writestandin(repo, standin, hash, executable):
326 '''write hash to <repo.root>/<standin>'''
336 '''write hash to <repo.root>/<standin>'''
327 writehash(hash, repo.wjoin(standin), executable)
337 writehash(hash, repo.wjoin(standin), executable)
328
338
329 def copyandhash(instream, outfile):
339 def copyandhash(instream, outfile):
330 '''Read bytes from instream (iterable) and write them to outfile,
340 '''Read bytes from instream (iterable) and write them to outfile,
331 computing the SHA-1 hash of the data along the way. Close outfile
341 computing the SHA-1 hash of the data along the way. Close outfile
332 when done and return the binary hash.'''
342 when done and return the binary hash.'''
333 hasher = util.sha1('')
343 hasher = util.sha1('')
334 for data in instream:
344 for data in instream:
335 hasher.update(data)
345 hasher.update(data)
336 outfile.write(data)
346 outfile.write(data)
337
347
338 # Blecch: closing a file that somebody else opened is rude and
348 # Blecch: closing a file that somebody else opened is rude and
339 # wrong. But it's so darn convenient and practical! After all,
349 # wrong. But it's so darn convenient and practical! After all,
340 # outfile was opened just to copy and hash.
350 # outfile was opened just to copy and hash.
341 outfile.close()
351 outfile.close()
342
352
343 return hasher.digest()
353 return hasher.digest()
344
354
345 def hashrepofile(repo, file):
355 def hashrepofile(repo, file):
346 return hashfile(repo.wjoin(file))
356 return hashfile(repo.wjoin(file))
347
357
348 def hashfile(file):
358 def hashfile(file):
349 if not os.path.exists(file):
359 if not os.path.exists(file):
350 return ''
360 return ''
351 hasher = util.sha1('')
361 hasher = util.sha1('')
352 fd = open(file, 'rb')
362 fd = open(file, 'rb')
353 for data in blockstream(fd):
363 for data in blockstream(fd):
354 hasher.update(data)
364 hasher.update(data)
355 fd.close()
365 fd.close()
356 return hasher.hexdigest()
366 return hasher.hexdigest()
357
367
358 class limitreader(object):
368 class limitreader(object):
359 def __init__(self, f, limit):
369 def __init__(self, f, limit):
360 self.f = f
370 self.f = f
361 self.limit = limit
371 self.limit = limit
362
372
363 def read(self, length):
373 def read(self, length):
364 if self.limit == 0:
374 if self.limit == 0:
365 return ''
375 return ''
366 length = length > self.limit and self.limit or length
376 length = length > self.limit and self.limit or length
367 self.limit -= length
377 self.limit -= length
368 return self.f.read(length)
378 return self.f.read(length)
369
379
370 def close(self):
380 def close(self):
371 pass
381 pass
372
382
373 def blockstream(infile, blocksize=128 * 1024):
383 def blockstream(infile, blocksize=128 * 1024):
374 """Generator that yields blocks of data from infile and closes infile."""
384 """Generator that yields blocks of data from infile and closes infile."""
375 while True:
385 while True:
376 data = infile.read(blocksize)
386 data = infile.read(blocksize)
377 if not data:
387 if not data:
378 break
388 break
379 yield data
389 yield data
380 # same blecch as copyandhash() above
390 # same blecch as copyandhash() above
381 infile.close()
391 infile.close()
382
392
383 def readhash(filename):
393 def readhash(filename):
384 rfile = open(filename, 'rb')
394 rfile = open(filename, 'rb')
385 hash = rfile.read(40)
395 hash = rfile.read(40)
386 rfile.close()
396 rfile.close()
387 if len(hash) < 40:
397 if len(hash) < 40:
388 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
398 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
389 % (filename, len(hash)))
399 % (filename, len(hash)))
390 return hash
400 return hash
391
401
392 def writehash(hash, filename, executable):
402 def writehash(hash, filename, executable):
393 util.makedirs(os.path.dirname(filename))
403 util.makedirs(os.path.dirname(filename))
394 util.writefile(filename, hash + '\n')
404 util.writefile(filename, hash + '\n')
395 os.chmod(filename, getmode(executable))
405 os.chmod(filename, getmode(executable))
396
406
397 def getexecutable(filename):
407 def getexecutable(filename):
398 mode = os.stat(filename).st_mode
408 mode = os.stat(filename).st_mode
399 return ((mode & stat.S_IXUSR) and
409 return ((mode & stat.S_IXUSR) and
400 (mode & stat.S_IXGRP) and
410 (mode & stat.S_IXGRP) and
401 (mode & stat.S_IXOTH))
411 (mode & stat.S_IXOTH))
402
412
403 def getmode(executable):
413 def getmode(executable):
404 if executable:
414 if executable:
405 return 0755
415 return 0755
406 else:
416 else:
407 return 0644
417 return 0644
408
418
409 def urljoin(first, second, *arg):
419 def urljoin(first, second, *arg):
410 def join(left, right):
420 def join(left, right):
411 if not left.endswith('/'):
421 if not left.endswith('/'):
412 left += '/'
422 left += '/'
413 if right.startswith('/'):
423 if right.startswith('/'):
414 right = right[1:]
424 right = right[1:]
415 return left + right
425 return left + right
416
426
417 url = join(first, second)
427 url = join(first, second)
418 for a in arg:
428 for a in arg:
419 url = join(url, a)
429 url = join(url, a)
420 return url
430 return url
421
431
422 def hexsha1(data):
432 def hexsha1(data):
423 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
433 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
424 object data"""
434 object data"""
425 h = util.sha1()
435 h = util.sha1()
426 for chunk in util.filechunkiter(data):
436 for chunk in util.filechunkiter(data):
427 h.update(chunk)
437 h.update(chunk)
428 return h.hexdigest()
438 return h.hexdigest()
429
439
430 def httpsendfile(ui, filename):
440 def httpsendfile(ui, filename):
431 return httpconnection.httpsendfile(ui, filename, 'rb')
441 return httpconnection.httpsendfile(ui, filename, 'rb')
432
442
433 def unixpath(path):
443 def unixpath(path):
434 '''Return a version of path normalized for use with the lfdirstate.'''
444 '''Return a version of path normalized for use with the lfdirstate.'''
435 return os.path.normpath(path).replace(os.sep, '/')
445 return os.path.normpath(path).replace(os.sep, '/')
436
446
437 def islfilesrepo(repo):
447 def islfilesrepo(repo):
438 return ('largefiles' in repo.requirements and
448 return ('largefiles' in repo.requirements and
439 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
449 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
440
450
441 def mkstemp(repo, prefix):
451 def mkstemp(repo, prefix):
442 '''Returns a file descriptor and a filename corresponding to a temporary
452 '''Returns a file descriptor and a filename corresponding to a temporary
443 file in the repo's largefiles store.'''
453 file in the repo's largefiles store.'''
444 path = repo.join(longname)
454 path = repo.join(longname)
445 util.makedirs(path)
455 util.makedirs(path)
446 return tempfile.mkstemp(prefix=prefix, dir=path)
456 return tempfile.mkstemp(prefix=prefix, dir=path)
447
457
448 class storeprotonotcapable(Exception):
458 class storeprotonotcapable(Exception):
449 def __init__(self, storetypes):
459 def __init__(self, storetypes):
450 self.storetypes = storetypes
460 self.storetypes = storetypes
@@ -1,451 +1,446 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 '''setup for largefiles repositories: reposetup'''
9 '''setup for largefiles repositories: reposetup'''
10 import copy
10 import copy
11 import types
11 import types
12 import os
12 import os
13
13
14 from mercurial import context, error, manifest, match as match_, util
14 from mercurial import context, error, manifest, match as match_, util
15 from mercurial import node as node_
15 from mercurial import node as node_
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17
17
18 import lfcommands
18 import lfcommands
19 import proto
19 import proto
20 import lfutil
20 import lfutil
21
21
22 def reposetup(ui, repo):
22 def reposetup(ui, repo):
23 # wire repositories should be given new wireproto functions but not the
23 # wire repositories should be given new wireproto functions but not the
24 # other largefiles modifications
24 # other largefiles modifications
25 if not repo.local():
25 if not repo.local():
26 return proto.wirereposetup(ui, repo)
26 return proto.wirereposetup(ui, repo)
27
27
28 for name in ('status', 'commitctx', 'commit', 'push'):
28 for name in ('status', 'commitctx', 'commit', 'push'):
29 method = getattr(repo, name)
29 method = getattr(repo, name)
30 if (isinstance(method, types.FunctionType) and
30 if (isinstance(method, types.FunctionType) and
31 method.func_name == 'wrap'):
31 method.func_name == 'wrap'):
32 ui.warn(_('largefiles: repo method %r appears to have already been'
32 ui.warn(_('largefiles: repo method %r appears to have already been'
33 ' wrapped by another extension: '
33 ' wrapped by another extension: '
34 'largefiles may behave incorrectly\n')
34 'largefiles may behave incorrectly\n')
35 % name)
35 % name)
36
36
37 class lfiles_repo(repo.__class__):
37 class lfiles_repo(repo.__class__):
38 lfstatus = False
38 lfstatus = False
39 def status_nolfiles(self, *args, **kwargs):
39 def status_nolfiles(self, *args, **kwargs):
40 return super(lfiles_repo, self).status(*args, **kwargs)
40 return super(lfiles_repo, self).status(*args, **kwargs)
41
41
42 # When lfstatus is set, return a context that gives the names
42 # When lfstatus is set, return a context that gives the names
43 # of largefiles instead of their corresponding standins and
43 # of largefiles instead of their corresponding standins and
44 # identifies the largefiles as always binary, regardless of
44 # identifies the largefiles as always binary, regardless of
45 # their actual contents.
45 # their actual contents.
46 def __getitem__(self, changeid):
46 def __getitem__(self, changeid):
47 ctx = super(lfiles_repo, self).__getitem__(changeid)
47 ctx = super(lfiles_repo, self).__getitem__(changeid)
48 if self.lfstatus:
48 if self.lfstatus:
49 class lfiles_manifestdict(manifest.manifestdict):
49 class lfiles_manifestdict(manifest.manifestdict):
50 def __contains__(self, filename):
50 def __contains__(self, filename):
51 if super(lfiles_manifestdict,
51 if super(lfiles_manifestdict,
52 self).__contains__(filename):
52 self).__contains__(filename):
53 return True
53 return True
54 return super(lfiles_manifestdict,
54 return super(lfiles_manifestdict,
55 self).__contains__(lfutil.standin(filename))
55 self).__contains__(lfutil.standin(filename))
56 class lfiles_ctx(ctx.__class__):
56 class lfiles_ctx(ctx.__class__):
57 def files(self):
57 def files(self):
58 filenames = super(lfiles_ctx, self).files()
58 filenames = super(lfiles_ctx, self).files()
59 return [lfutil.splitstandin(f) or f for f in filenames]
59 return [lfutil.splitstandin(f) or f for f in filenames]
60 def manifest(self):
60 def manifest(self):
61 man1 = super(lfiles_ctx, self).manifest()
61 man1 = super(lfiles_ctx, self).manifest()
62 man1.__class__ = lfiles_manifestdict
62 man1.__class__ = lfiles_manifestdict
63 return man1
63 return man1
64 def filectx(self, path, fileid=None, filelog=None):
64 def filectx(self, path, fileid=None, filelog=None):
65 try:
65 try:
66 result = super(lfiles_ctx, self).filectx(path,
66 result = super(lfiles_ctx, self).filectx(path,
67 fileid, filelog)
67 fileid, filelog)
68 except error.LookupError:
68 except error.LookupError:
69 # Adding a null character will cause Mercurial to
69 # Adding a null character will cause Mercurial to
70 # identify this as a binary file.
70 # identify this as a binary file.
71 result = super(lfiles_ctx, self).filectx(
71 result = super(lfiles_ctx, self).filectx(
72 lfutil.standin(path), fileid, filelog)
72 lfutil.standin(path), fileid, filelog)
73 olddata = result.data
73 olddata = result.data
74 result.data = lambda: olddata() + '\0'
74 result.data = lambda: olddata() + '\0'
75 return result
75 return result
76 ctx.__class__ = lfiles_ctx
76 ctx.__class__ = lfiles_ctx
77 return ctx
77 return ctx
78
78
79 # Figure out the status of big files and insert them into the
79 # Figure out the status of big files and insert them into the
80 # appropriate list in the result. Also removes standin files
80 # appropriate list in the result. Also removes standin files
81 # from the listing. Revert to the original status if
81 # from the listing. Revert to the original status if
82 # self.lfstatus is False.
82 # self.lfstatus is False.
83 def status(self, node1='.', node2=None, match=None, ignored=False,
83 def status(self, node1='.', node2=None, match=None, ignored=False,
84 clean=False, unknown=False, listsubrepos=False):
84 clean=False, unknown=False, listsubrepos=False):
85 listignored, listclean, listunknown = ignored, clean, unknown
85 listignored, listclean, listunknown = ignored, clean, unknown
86 if not self.lfstatus:
86 if not self.lfstatus:
87 return super(lfiles_repo, self).status(node1, node2, match,
87 return super(lfiles_repo, self).status(node1, node2, match,
88 listignored, listclean, listunknown, listsubrepos)
88 listignored, listclean, listunknown, listsubrepos)
89 else:
89 else:
90 # some calls in this function rely on the old version of status
90 # some calls in this function rely on the old version of status
91 self.lfstatus = False
91 self.lfstatus = False
92 if isinstance(node1, context.changectx):
92 if isinstance(node1, context.changectx):
93 ctx1 = node1
93 ctx1 = node1
94 else:
94 else:
95 ctx1 = repo[node1]
95 ctx1 = repo[node1]
96 if isinstance(node2, context.changectx):
96 if isinstance(node2, context.changectx):
97 ctx2 = node2
97 ctx2 = node2
98 else:
98 else:
99 ctx2 = repo[node2]
99 ctx2 = repo[node2]
100 working = ctx2.rev() is None
100 working = ctx2.rev() is None
101 parentworking = working and ctx1 == self['.']
101 parentworking = working and ctx1 == self['.']
102
102
103 def inctx(file, ctx):
103 def inctx(file, ctx):
104 try:
104 try:
105 if ctx.rev() is None:
105 if ctx.rev() is None:
106 return file in ctx.manifest()
106 return file in ctx.manifest()
107 ctx[file]
107 ctx[file]
108 return True
108 return True
109 except KeyError:
109 except KeyError:
110 return False
110 return False
111
111
112 if match is None:
112 if match is None:
113 match = match_.always(self.root, self.getcwd())
113 match = match_.always(self.root, self.getcwd())
114
114
115 # First check if there were files specified on the
115 # First check if there were files specified on the
116 # command line. If there were, and none of them were
116 # command line. If there were, and none of them were
117 # largefiles, we should just bail here and let super
117 # largefiles, we should just bail here and let super
118 # handle it -- thus gaining a big performance boost.
118 # handle it -- thus gaining a big performance boost.
119 lfdirstate = lfutil.openlfdirstate(ui, self)
119 lfdirstate = lfutil.openlfdirstate(ui, self)
120 if match.files() and not match.anypats():
120 if match.files() and not match.anypats():
121 matchedfiles = [f for f in match.files() if f in lfdirstate]
121 matchedfiles = [f for f in match.files() if f in lfdirstate]
122 if not matchedfiles:
122 if not matchedfiles:
123 return super(lfiles_repo, self).status(node1, node2,
123 return super(lfiles_repo, self).status(node1, node2,
124 match, listignored, listclean,
124 match, listignored, listclean,
125 listunknown, listsubrepos)
125 listunknown, listsubrepos)
126
126
127 # Create a copy of match that matches standins instead
127 # Create a copy of match that matches standins instead
128 # of largefiles.
128 # of largefiles.
129 def tostandin(file):
129 def tostandin(file):
130 if inctx(lfutil.standin(file), ctx2):
130 if inctx(lfutil.standin(file), ctx2):
131 return lfutil.standin(file)
131 return lfutil.standin(file)
132 return file
132 return file
133
133
134 # Create a function that we can use to override what is
134 # Create a function that we can use to override what is
135 # normally the ignore matcher. We've already checked
135 # normally the ignore matcher. We've already checked
136 # for ignored files on the first dirstate walk, and
136 # for ignored files on the first dirstate walk, and
137 # unecessarily re-checking here causes a huge performance
137 # unecessarily re-checking here causes a huge performance
138 # hit because lfdirstate only knows about largefiles
138 # hit because lfdirstate only knows about largefiles
139 def _ignoreoverride(self):
139 def _ignoreoverride(self):
140 return False
140 return False
141
141
142 m = copy.copy(match)
142 m = copy.copy(match)
143 m._files = [tostandin(f) for f in m._files]
143 m._files = [tostandin(f) for f in m._files]
144
144
145 # Get ignored files here even if we weren't asked for them; we
145 # Get ignored files here even if we weren't asked for them; we
146 # must use the result here for filtering later
146 # must use the result here for filtering later
147 result = super(lfiles_repo, self).status(node1, node2, m,
147 result = super(lfiles_repo, self).status(node1, node2, m,
148 True, clean, unknown, listsubrepos)
148 True, clean, unknown, listsubrepos)
149 if working:
149 if working:
150 try:
150 try:
151 # Any non-largefiles that were explicitly listed must be
151 # Any non-largefiles that were explicitly listed must be
152 # taken out or lfdirstate.status will report an error.
152 # taken out or lfdirstate.status will report an error.
153 # The status of these files was already computed using
153 # The status of these files was already computed using
154 # super's status.
154 # super's status.
155 # Override lfdirstate's ignore matcher to not do
155 # Override lfdirstate's ignore matcher to not do
156 # anything
156 # anything
157 orig_ignore = lfdirstate._ignore
157 orig_ignore = lfdirstate._ignore
158 lfdirstate._ignore = _ignoreoverride
158 lfdirstate._ignore = _ignoreoverride
159
159
160 match._files = [f for f in match._files if f in
160 match._files = [f for f in match._files if f in
161 lfdirstate]
161 lfdirstate]
162 # Don't waste time getting the ignored and unknown
162 # Don't waste time getting the ignored and unknown
163 # files again; we already have them
163 # files again; we already have them
164 s = lfdirstate.status(match, [], False,
164 s = lfdirstate.status(match, [], False,
165 listclean, False)
165 listclean, False)
166 (unsure, modified, added, removed, missing, unknown,
166 (unsure, modified, added, removed, missing, unknown,
167 ignored, clean) = s
167 ignored, clean) = s
168 # Replace the list of ignored and unknown files with
168 # Replace the list of ignored and unknown files with
169 # the previously caclulated lists, and strip out the
169 # the previously caclulated lists, and strip out the
170 # largefiles
170 # largefiles
171 lfiles = set(lfdirstate._map)
171 lfiles = set(lfdirstate._map)
172 ignored = set(result[5]).difference(lfiles)
172 ignored = set(result[5]).difference(lfiles)
173 unknown = set(result[4]).difference(lfiles)
173 unknown = set(result[4]).difference(lfiles)
174 if parentworking:
174 if parentworking:
175 for lfile in unsure:
175 for lfile in unsure:
176 standin = lfutil.standin(lfile)
176 standin = lfutil.standin(lfile)
177 if standin not in ctx1:
177 if standin not in ctx1:
178 # from second parent
178 # from second parent
179 modified.append(lfile)
179 modified.append(lfile)
180 elif ctx1[standin].data().strip() \
180 elif ctx1[standin].data().strip() \
181 != lfutil.hashfile(self.wjoin(lfile)):
181 != lfutil.hashfile(self.wjoin(lfile)):
182 modified.append(lfile)
182 modified.append(lfile)
183 else:
183 else:
184 clean.append(lfile)
184 clean.append(lfile)
185 lfdirstate.normal(lfile)
185 lfdirstate.normal(lfile)
186 else:
186 else:
187 tocheck = unsure + modified + added + clean
187 tocheck = unsure + modified + added + clean
188 modified, added, clean = [], [], []
188 modified, added, clean = [], [], []
189
189
190 for lfile in tocheck:
190 for lfile in tocheck:
191 standin = lfutil.standin(lfile)
191 standin = lfutil.standin(lfile)
192 if inctx(standin, ctx1):
192 if inctx(standin, ctx1):
193 if ctx1[standin].data().strip() != \
193 if ctx1[standin].data().strip() != \
194 lfutil.hashfile(self.wjoin(lfile)):
194 lfutil.hashfile(self.wjoin(lfile)):
195 modified.append(lfile)
195 modified.append(lfile)
196 else:
196 else:
197 clean.append(lfile)
197 clean.append(lfile)
198 else:
198 else:
199 added.append(lfile)
199 added.append(lfile)
200 finally:
200 finally:
201 # Replace the original ignore function
201 # Replace the original ignore function
202 lfdirstate._ignore = orig_ignore
202 lfdirstate._ignore = orig_ignore
203
203
204 for standin in ctx1.manifest():
204 for standin in ctx1.manifest():
205 if not lfutil.isstandin(standin):
205 if not lfutil.isstandin(standin):
206 continue
206 continue
207 lfile = lfutil.splitstandin(standin)
207 lfile = lfutil.splitstandin(standin)
208 if not match(lfile):
208 if not match(lfile):
209 continue
209 continue
210 if lfile not in lfdirstate:
210 if lfile not in lfdirstate:
211 removed.append(lfile)
211 removed.append(lfile)
212
212
213 # Filter result lists
213 # Filter result lists
214 result = list(result)
214 result = list(result)
215
215
216 # Largefiles are not really removed when they're
216 # Largefiles are not really removed when they're
217 # still in the normal dirstate. Likewise, normal
217 # still in the normal dirstate. Likewise, normal
218 # files are not really removed if it's still in
218 # files are not really removed if it's still in
219 # lfdirstate. This happens in merges where files
219 # lfdirstate. This happens in merges where files
220 # change type.
220 # change type.
221 removed = [f for f in removed if f not in repo.dirstate]
221 removed = [f for f in removed if f not in repo.dirstate]
222 result[2] = [f for f in result[2] if f not in lfdirstate]
222 result[2] = [f for f in result[2] if f not in lfdirstate]
223
223
224 # Unknown files
224 # Unknown files
225 unknown = set(unknown).difference(ignored)
225 unknown = set(unknown).difference(ignored)
226 result[4] = [f for f in unknown
226 result[4] = [f for f in unknown
227 if (repo.dirstate[f] == '?' and
227 if (repo.dirstate[f] == '?' and
228 not lfutil.isstandin(f))]
228 not lfutil.isstandin(f))]
229 # Ignored files were calculated earlier by the dirstate,
229 # Ignored files were calculated earlier by the dirstate,
230 # and we already stripped out the largefiles from the list
230 # and we already stripped out the largefiles from the list
231 result[5] = ignored
231 result[5] = ignored
232 # combine normal files and largefiles
232 # combine normal files and largefiles
233 normals = [[fn for fn in filelist
233 normals = [[fn for fn in filelist
234 if not lfutil.isstandin(fn)]
234 if not lfutil.isstandin(fn)]
235 for filelist in result]
235 for filelist in result]
236 lfiles = (modified, added, removed, missing, [], [], clean)
236 lfiles = (modified, added, removed, missing, [], [], clean)
237 result = [sorted(list1 + list2)
237 result = [sorted(list1 + list2)
238 for (list1, list2) in zip(normals, lfiles)]
238 for (list1, list2) in zip(normals, lfiles)]
239 else:
239 else:
240 def toname(f):
240 def toname(f):
241 if lfutil.isstandin(f):
241 if lfutil.isstandin(f):
242 return lfutil.splitstandin(f)
242 return lfutil.splitstandin(f)
243 return f
243 return f
244 result = [[toname(f) for f in items] for items in result]
244 result = [[toname(f) for f in items] for items in result]
245
245
246 if not listunknown:
246 if not listunknown:
247 result[4] = []
247 result[4] = []
248 if not listignored:
248 if not listignored:
249 result[5] = []
249 result[5] = []
250 if not listclean:
250 if not listclean:
251 result[6] = []
251 result[6] = []
252 self.lfstatus = True
252 self.lfstatus = True
253 return result
253 return result
254
254
255 # As part of committing, copy all of the largefiles into the
255 # As part of committing, copy all of the largefiles into the
256 # cache.
256 # cache.
257 def commitctx(self, *args, **kwargs):
257 def commitctx(self, *args, **kwargs):
258 node = super(lfiles_repo, self).commitctx(*args, **kwargs)
258 node = super(lfiles_repo, self).commitctx(*args, **kwargs)
259 ctx = self[node]
259 lfutil.copyalltostore(self, node)
260 for filename in ctx.files():
261 if lfutil.isstandin(filename) and filename in ctx.manifest():
262 realfile = lfutil.splitstandin(filename)
263 lfutil.copytostore(self, ctx.node(), realfile)
264
265 return node
260 return node
266
261
267 # Before commit, largefile standins have not had their
262 # Before commit, largefile standins have not had their
268 # contents updated to reflect the hash of their largefile.
263 # contents updated to reflect the hash of their largefile.
269 # Do that here.
264 # Do that here.
270 def commit(self, text="", user=None, date=None, match=None,
265 def commit(self, text="", user=None, date=None, match=None,
271 force=False, editor=False, extra={}):
266 force=False, editor=False, extra={}):
272 orig = super(lfiles_repo, self).commit
267 orig = super(lfiles_repo, self).commit
273
268
274 wlock = repo.wlock()
269 wlock = repo.wlock()
275 try:
270 try:
276 # Case 0: Rebase
271 # Case 0: Rebase
277 # We have to take the time to pull down the new largefiles now.
272 # We have to take the time to pull down the new largefiles now.
278 # Otherwise if we are rebasing, any largefiles that were
273 # Otherwise if we are rebasing, any largefiles that were
279 # modified in the destination changesets get overwritten, either
274 # modified in the destination changesets get overwritten, either
280 # by the rebase or in the first commit after the rebase.
275 # by the rebase or in the first commit after the rebase.
281 # updatelfiles will update the dirstate to mark any pulled
276 # updatelfiles will update the dirstate to mark any pulled
282 # largefiles as modified
277 # largefiles as modified
283 if getattr(repo, "_isrebasing", False):
278 if getattr(repo, "_isrebasing", False):
284 lfcommands.updatelfiles(repo.ui, repo)
279 lfcommands.updatelfiles(repo.ui, repo)
285 result = orig(text=text, user=user, date=date, match=match,
280 result = orig(text=text, user=user, date=date, match=match,
286 force=force, editor=editor, extra=extra)
281 force=force, editor=editor, extra=extra)
287 return result
282 return result
288 # Case 1: user calls commit with no specific files or
283 # Case 1: user calls commit with no specific files or
289 # include/exclude patterns: refresh and commit all files that
284 # include/exclude patterns: refresh and commit all files that
290 # are "dirty".
285 # are "dirty".
291 if ((match is None) or
286 if ((match is None) or
292 (not match.anypats() and not match.files())):
287 (not match.anypats() and not match.files())):
293 # Spend a bit of time here to get a list of files we know
288 # Spend a bit of time here to get a list of files we know
294 # are modified so we can compare only against those.
289 # are modified so we can compare only against those.
295 # It can cost a lot of time (several seconds)
290 # It can cost a lot of time (several seconds)
296 # otherwise to update all standins if the largefiles are
291 # otherwise to update all standins if the largefiles are
297 # large.
292 # large.
298 lfdirstate = lfutil.openlfdirstate(ui, self)
293 lfdirstate = lfutil.openlfdirstate(ui, self)
299 dirtymatch = match_.always(repo.root, repo.getcwd())
294 dirtymatch = match_.always(repo.root, repo.getcwd())
300 s = lfdirstate.status(dirtymatch, [], False, False, False)
295 s = lfdirstate.status(dirtymatch, [], False, False, False)
301 modifiedfiles = []
296 modifiedfiles = []
302 for i in s:
297 for i in s:
303 modifiedfiles.extend(i)
298 modifiedfiles.extend(i)
304 lfiles = lfutil.listlfiles(self)
299 lfiles = lfutil.listlfiles(self)
305 # this only loops through largefiles that exist (not
300 # this only loops through largefiles that exist (not
306 # removed/renamed)
301 # removed/renamed)
307 for lfile in lfiles:
302 for lfile in lfiles:
308 if lfile in modifiedfiles:
303 if lfile in modifiedfiles:
309 if os.path.exists(self.wjoin(lfutil.standin(lfile))):
304 if os.path.exists(self.wjoin(lfutil.standin(lfile))):
310 # this handles the case where a rebase is being
305 # this handles the case where a rebase is being
311 # performed and the working copy is not updated
306 # performed and the working copy is not updated
312 # yet.
307 # yet.
313 if os.path.exists(self.wjoin(lfile)):
308 if os.path.exists(self.wjoin(lfile)):
314 lfutil.updatestandin(self,
309 lfutil.updatestandin(self,
315 lfutil.standin(lfile))
310 lfutil.standin(lfile))
316 lfdirstate.normal(lfile)
311 lfdirstate.normal(lfile)
317 for lfile in lfdirstate:
312 for lfile in lfdirstate:
318 if lfile in modifiedfiles:
313 if lfile in modifiedfiles:
319 if not os.path.exists(
314 if not os.path.exists(
320 repo.wjoin(lfutil.standin(lfile))):
315 repo.wjoin(lfutil.standin(lfile))):
321 lfdirstate.drop(lfile)
316 lfdirstate.drop(lfile)
322
317
323 result = orig(text=text, user=user, date=date, match=match,
318 result = orig(text=text, user=user, date=date, match=match,
324 force=force, editor=editor, extra=extra)
319 force=force, editor=editor, extra=extra)
325 # This needs to be after commit; otherwise precommit hooks
320 # This needs to be after commit; otherwise precommit hooks
326 # get the wrong status
321 # get the wrong status
327 lfdirstate.write()
322 lfdirstate.write()
328 return result
323 return result
329
324
330 for f in match.files():
325 for f in match.files():
331 if lfutil.isstandin(f):
326 if lfutil.isstandin(f):
332 raise util.Abort(
327 raise util.Abort(
333 _('file "%s" is a largefile standin') % f,
328 _('file "%s" is a largefile standin') % f,
334 hint=('commit the largefile itself instead'))
329 hint=('commit the largefile itself instead'))
335
330
336 # Case 2: user calls commit with specified patterns: refresh
331 # Case 2: user calls commit with specified patterns: refresh
337 # any matching big files.
332 # any matching big files.
338 smatcher = lfutil.composestandinmatcher(self, match)
333 smatcher = lfutil.composestandinmatcher(self, match)
339 standins = lfutil.dirstate_walk(self.dirstate, smatcher)
334 standins = lfutil.dirstate_walk(self.dirstate, smatcher)
340
335
341 # No matching big files: get out of the way and pass control to
336 # No matching big files: get out of the way and pass control to
342 # the usual commit() method.
337 # the usual commit() method.
343 if not standins:
338 if not standins:
344 return orig(text=text, user=user, date=date, match=match,
339 return orig(text=text, user=user, date=date, match=match,
345 force=force, editor=editor, extra=extra)
340 force=force, editor=editor, extra=extra)
346
341
347 # Refresh all matching big files. It's possible that the
342 # Refresh all matching big files. It's possible that the
348 # commit will end up failing, in which case the big files will
343 # commit will end up failing, in which case the big files will
349 # stay refreshed. No harm done: the user modified them and
344 # stay refreshed. No harm done: the user modified them and
350 # asked to commit them, so sooner or later we're going to
345 # asked to commit them, so sooner or later we're going to
351 # refresh the standins. Might as well leave them refreshed.
346 # refresh the standins. Might as well leave them refreshed.
352 lfdirstate = lfutil.openlfdirstate(ui, self)
347 lfdirstate = lfutil.openlfdirstate(ui, self)
353 for standin in standins:
348 for standin in standins:
354 lfile = lfutil.splitstandin(standin)
349 lfile = lfutil.splitstandin(standin)
355 if lfdirstate[lfile] <> 'r':
350 if lfdirstate[lfile] <> 'r':
356 lfutil.updatestandin(self, standin)
351 lfutil.updatestandin(self, standin)
357 lfdirstate.normal(lfile)
352 lfdirstate.normal(lfile)
358 else:
353 else:
359 lfdirstate.drop(lfile)
354 lfdirstate.drop(lfile)
360
355
361 # Cook up a new matcher that only matches regular files or
356 # Cook up a new matcher that only matches regular files or
362 # standins corresponding to the big files requested by the
357 # standins corresponding to the big files requested by the
363 # user. Have to modify _files to prevent commit() from
358 # user. Have to modify _files to prevent commit() from
364 # complaining "not tracked" for big files.
359 # complaining "not tracked" for big files.
365 lfiles = lfutil.listlfiles(repo)
360 lfiles = lfutil.listlfiles(repo)
366 match = copy.copy(match)
361 match = copy.copy(match)
367 orig_matchfn = match.matchfn
362 orig_matchfn = match.matchfn
368
363
369 # Check both the list of largefiles and the list of
364 # Check both the list of largefiles and the list of
370 # standins because if a largefile was removed, it
365 # standins because if a largefile was removed, it
371 # won't be in the list of largefiles at this point
366 # won't be in the list of largefiles at this point
372 match._files += sorted(standins)
367 match._files += sorted(standins)
373
368
374 actualfiles = []
369 actualfiles = []
375 for f in match._files:
370 for f in match._files:
376 fstandin = lfutil.standin(f)
371 fstandin = lfutil.standin(f)
377
372
378 # ignore known largefiles and standins
373 # ignore known largefiles and standins
379 if f in lfiles or fstandin in standins:
374 if f in lfiles or fstandin in standins:
380 continue
375 continue
381
376
382 # append directory separator to avoid collisions
377 # append directory separator to avoid collisions
383 if not fstandin.endswith(os.sep):
378 if not fstandin.endswith(os.sep):
384 fstandin += os.sep
379 fstandin += os.sep
385
380
386 # prevalidate matching standin directories
381 # prevalidate matching standin directories
387 if util.any(st for st in match._files
382 if util.any(st for st in match._files
388 if st.startswith(fstandin)):
383 if st.startswith(fstandin)):
389 continue
384 continue
390 actualfiles.append(f)
385 actualfiles.append(f)
391 match._files = actualfiles
386 match._files = actualfiles
392
387
393 def matchfn(f):
388 def matchfn(f):
394 if orig_matchfn(f):
389 if orig_matchfn(f):
395 return f not in lfiles
390 return f not in lfiles
396 else:
391 else:
397 return f in standins
392 return f in standins
398
393
399 match.matchfn = matchfn
394 match.matchfn = matchfn
400 result = orig(text=text, user=user, date=date, match=match,
395 result = orig(text=text, user=user, date=date, match=match,
401 force=force, editor=editor, extra=extra)
396 force=force, editor=editor, extra=extra)
402 # This needs to be after commit; otherwise precommit hooks
397 # This needs to be after commit; otherwise precommit hooks
403 # get the wrong status
398 # get the wrong status
404 lfdirstate.write()
399 lfdirstate.write()
405 return result
400 return result
406 finally:
401 finally:
407 wlock.release()
402 wlock.release()
408
403
409 def push(self, remote, force=False, revs=None, newbranch=False):
404 def push(self, remote, force=False, revs=None, newbranch=False):
410 o = lfutil.findoutgoing(repo, remote, force)
405 o = lfutil.findoutgoing(repo, remote, force)
411 if o:
406 if o:
412 toupload = set()
407 toupload = set()
413 o = repo.changelog.nodesbetween(o, revs)[0]
408 o = repo.changelog.nodesbetween(o, revs)[0]
414 for n in o:
409 for n in o:
415 parents = [p for p in repo.changelog.parents(n)
410 parents = [p for p in repo.changelog.parents(n)
416 if p != node_.nullid]
411 if p != node_.nullid]
417 ctx = repo[n]
412 ctx = repo[n]
418 files = set(ctx.files())
413 files = set(ctx.files())
419 if len(parents) == 2:
414 if len(parents) == 2:
420 mc = ctx.manifest()
415 mc = ctx.manifest()
421 mp1 = ctx.parents()[0].manifest()
416 mp1 = ctx.parents()[0].manifest()
422 mp2 = ctx.parents()[1].manifest()
417 mp2 = ctx.parents()[1].manifest()
423 for f in mp1:
418 for f in mp1:
424 if f not in mc:
419 if f not in mc:
425 files.add(f)
420 files.add(f)
426 for f in mp2:
421 for f in mp2:
427 if f not in mc:
422 if f not in mc:
428 files.add(f)
423 files.add(f)
429 for f in mc:
424 for f in mc:
430 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f,
425 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f,
431 None):
426 None):
432 files.add(f)
427 files.add(f)
433
428
434 toupload = toupload.union(
429 toupload = toupload.union(
435 set([ctx[f].data().strip()
430 set([ctx[f].data().strip()
436 for f in files
431 for f in files
437 if lfutil.isstandin(f) and f in ctx]))
432 if lfutil.isstandin(f) and f in ctx]))
438 lfcommands.uploadlfiles(ui, self, remote, toupload)
433 lfcommands.uploadlfiles(ui, self, remote, toupload)
439 return super(lfiles_repo, self).push(remote, force, revs,
434 return super(lfiles_repo, self).push(remote, force, revs,
440 newbranch)
435 newbranch)
441
436
442 repo.__class__ = lfiles_repo
437 repo.__class__ = lfiles_repo
443
438
444 def checkrequireslfiles(ui, repo, **kwargs):
439 def checkrequireslfiles(ui, repo, **kwargs):
445 if 'largefiles' not in repo.requirements and util.any(
440 if 'largefiles' not in repo.requirements and util.any(
446 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
441 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
447 repo.requirements.add('largefiles')
442 repo.requirements.add('largefiles')
448 repo._writerequirements()
443 repo._writerequirements()
449
444
450 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles)
445 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles)
451 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)
446 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)
General Comments 0
You need to be logged in to leave comments. Login now