##// END OF EJS Templates
largefiles: don't copy largefiles from working dir to the store while converting...
Matt Harbison -
r17878:d1d01402 stable
parent child Browse files
Show More
@@ -1,469 +1,469
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
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
19
20 shortname = '.hglf'
20 shortname = '.hglf'
21 longname = 'largefiles'
21 longname = 'largefiles'
22
22
23
23
24 # -- Portability wrappers ----------------------------------------------
24 # -- Portability wrappers ----------------------------------------------
25
25
26 def dirstatewalk(dirstate, matcher, unknown=False, ignored=False):
26 def dirstatewalk(dirstate, matcher, unknown=False, ignored=False):
27 return dirstate.walk(matcher, [], unknown, ignored)
27 return dirstate.walk(matcher, [], unknown, ignored)
28
28
29 def repoadd(repo, list):
29 def repoadd(repo, list):
30 add = repo[None].add
30 add = repo[None].add
31 return add(list)
31 return add(list)
32
32
33 def reporemove(repo, list, unlink=False):
33 def reporemove(repo, list, unlink=False):
34 def remove(list, unlink):
34 def remove(list, unlink):
35 wlock = repo.wlock()
35 wlock = repo.wlock()
36 try:
36 try:
37 if unlink:
37 if unlink:
38 for f in list:
38 for f in list:
39 try:
39 try:
40 util.unlinkpath(repo.wjoin(f))
40 util.unlinkpath(repo.wjoin(f))
41 except OSError, inst:
41 except OSError, inst:
42 if inst.errno != errno.ENOENT:
42 if inst.errno != errno.ENOENT:
43 raise
43 raise
44 repo[None].forget(list)
44 repo[None].forget(list)
45 finally:
45 finally:
46 wlock.release()
46 wlock.release()
47 return remove(list, unlink=unlink)
47 return remove(list, unlink=unlink)
48
48
49 def repoforget(repo, list):
49 def repoforget(repo, list):
50 forget = repo[None].forget
50 forget = repo[None].forget
51 return forget(list)
51 return forget(list)
52
52
53 def findoutgoing(repo, remote, force):
53 def findoutgoing(repo, remote, force):
54 from mercurial import discovery
54 from mercurial import discovery
55 outgoing = discovery.findcommonoutgoing(repo, remote.peer(), force=force)
55 outgoing = discovery.findcommonoutgoing(repo, remote.peer(), force=force)
56 return outgoing.missing
56 return outgoing.missing
57
57
58 # -- Private worker functions ------------------------------------------
58 # -- Private worker functions ------------------------------------------
59
59
60 def getminsize(ui, assumelfiles, opt, default=10):
60 def getminsize(ui, assumelfiles, opt, default=10):
61 lfsize = opt
61 lfsize = opt
62 if not lfsize and assumelfiles:
62 if not lfsize and assumelfiles:
63 lfsize = ui.config(longname, 'minsize', default=default)
63 lfsize = ui.config(longname, 'minsize', default=default)
64 if lfsize:
64 if lfsize:
65 try:
65 try:
66 lfsize = float(lfsize)
66 lfsize = float(lfsize)
67 except ValueError:
67 except ValueError:
68 raise util.Abort(_('largefiles: size must be number (not %s)\n')
68 raise util.Abort(_('largefiles: size must be number (not %s)\n')
69 % lfsize)
69 % lfsize)
70 if lfsize is None:
70 if lfsize is None:
71 raise util.Abort(_('minimum size for largefiles must be specified'))
71 raise util.Abort(_('minimum size for largefiles must be specified'))
72 return lfsize
72 return lfsize
73
73
74 def link(src, dest):
74 def link(src, dest):
75 try:
75 try:
76 util.oslink(src, dest)
76 util.oslink(src, dest)
77 except OSError:
77 except OSError:
78 # if hardlinks fail, fallback on atomic copy
78 # if hardlinks fail, fallback on atomic copy
79 dst = util.atomictempfile(dest)
79 dst = util.atomictempfile(dest)
80 for chunk in util.filechunkiter(open(src, 'rb')):
80 for chunk in util.filechunkiter(open(src, 'rb')):
81 dst.write(chunk)
81 dst.write(chunk)
82 dst.close()
82 dst.close()
83 os.chmod(dest, os.stat(src).st_mode)
83 os.chmod(dest, os.stat(src).st_mode)
84
84
85 def usercachepath(ui, hash):
85 def usercachepath(ui, hash):
86 path = ui.configpath(longname, 'usercache', None)
86 path = ui.configpath(longname, 'usercache', None)
87 if path:
87 if path:
88 path = os.path.join(path, hash)
88 path = os.path.join(path, hash)
89 else:
89 else:
90 if os.name == 'nt':
90 if os.name == 'nt':
91 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
91 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
92 if appdata:
92 if appdata:
93 path = os.path.join(appdata, longname, hash)
93 path = os.path.join(appdata, longname, hash)
94 elif platform.system() == 'Darwin':
94 elif platform.system() == 'Darwin':
95 home = os.getenv('HOME')
95 home = os.getenv('HOME')
96 if home:
96 if home:
97 path = os.path.join(home, 'Library', 'Caches',
97 path = os.path.join(home, 'Library', 'Caches',
98 longname, hash)
98 longname, hash)
99 elif os.name == 'posix':
99 elif os.name == 'posix':
100 path = os.getenv('XDG_CACHE_HOME')
100 path = os.getenv('XDG_CACHE_HOME')
101 if path:
101 if path:
102 path = os.path.join(path, longname, hash)
102 path = os.path.join(path, longname, hash)
103 else:
103 else:
104 home = os.getenv('HOME')
104 home = os.getenv('HOME')
105 if home:
105 if home:
106 path = os.path.join(home, '.cache', longname, hash)
106 path = os.path.join(home, '.cache', longname, hash)
107 else:
107 else:
108 raise util.Abort(_('unknown operating system: %s\n') % os.name)
108 raise util.Abort(_('unknown operating system: %s\n') % os.name)
109 return path
109 return path
110
110
111 def inusercache(ui, hash):
111 def inusercache(ui, hash):
112 path = usercachepath(ui, hash)
112 path = usercachepath(ui, hash)
113 return path and os.path.exists(path)
113 return path and os.path.exists(path)
114
114
115 def findfile(repo, hash):
115 def findfile(repo, hash):
116 if instore(repo, hash):
116 if instore(repo, hash):
117 repo.ui.note(_('found %s in store\n') % hash)
117 repo.ui.note(_('found %s in store\n') % hash)
118 return storepath(repo, hash)
118 return storepath(repo, hash)
119 elif inusercache(repo.ui, hash):
119 elif inusercache(repo.ui, hash):
120 repo.ui.note(_('found %s in system cache\n') % hash)
120 repo.ui.note(_('found %s in system cache\n') % hash)
121 path = storepath(repo, hash)
121 path = storepath(repo, hash)
122 util.makedirs(os.path.dirname(path))
122 util.makedirs(os.path.dirname(path))
123 link(usercachepath(repo.ui, hash), path)
123 link(usercachepath(repo.ui, hash), path)
124 return path
124 return path
125 return None
125 return None
126
126
127 class largefilesdirstate(dirstate.dirstate):
127 class largefilesdirstate(dirstate.dirstate):
128 def __getitem__(self, key):
128 def __getitem__(self, key):
129 return super(largefilesdirstate, self).__getitem__(unixpath(key))
129 return super(largefilesdirstate, self).__getitem__(unixpath(key))
130 def normal(self, f):
130 def normal(self, f):
131 return super(largefilesdirstate, self).normal(unixpath(f))
131 return super(largefilesdirstate, self).normal(unixpath(f))
132 def remove(self, f):
132 def remove(self, f):
133 return super(largefilesdirstate, self).remove(unixpath(f))
133 return super(largefilesdirstate, self).remove(unixpath(f))
134 def add(self, f):
134 def add(self, f):
135 return super(largefilesdirstate, self).add(unixpath(f))
135 return super(largefilesdirstate, self).add(unixpath(f))
136 def drop(self, f):
136 def drop(self, f):
137 return super(largefilesdirstate, self).drop(unixpath(f))
137 return super(largefilesdirstate, self).drop(unixpath(f))
138 def forget(self, f):
138 def forget(self, f):
139 return super(largefilesdirstate, self).forget(unixpath(f))
139 return super(largefilesdirstate, self).forget(unixpath(f))
140 def normallookup(self, f):
140 def normallookup(self, f):
141 return super(largefilesdirstate, self).normallookup(unixpath(f))
141 return super(largefilesdirstate, self).normallookup(unixpath(f))
142
142
143 def openlfdirstate(ui, repo, create=True):
143 def openlfdirstate(ui, repo, create=True):
144 '''
144 '''
145 Return a dirstate object that tracks largefiles: i.e. its root is
145 Return a dirstate object that tracks largefiles: i.e. its root is
146 the repo root, but it is saved in .hg/largefiles/dirstate.
146 the repo root, but it is saved in .hg/largefiles/dirstate.
147 '''
147 '''
148 admin = repo.join(longname)
148 admin = repo.join(longname)
149 opener = scmutil.opener(admin)
149 opener = scmutil.opener(admin)
150 lfdirstate = largefilesdirstate(opener, ui, repo.root,
150 lfdirstate = largefilesdirstate(opener, ui, repo.root,
151 repo.dirstate._validate)
151 repo.dirstate._validate)
152
152
153 # If the largefiles dirstate does not exist, populate and create
153 # If the largefiles dirstate does not exist, populate and create
154 # it. This ensures that we create it on the first meaningful
154 # it. This ensures that we create it on the first meaningful
155 # largefiles operation in a new clone.
155 # largefiles operation in a new clone.
156 if create and not os.path.exists(os.path.join(admin, 'dirstate')):
156 if create and not os.path.exists(os.path.join(admin, 'dirstate')):
157 util.makedirs(admin)
157 util.makedirs(admin)
158 matcher = getstandinmatcher(repo)
158 matcher = getstandinmatcher(repo)
159 for standin in dirstatewalk(repo.dirstate, matcher):
159 for standin in dirstatewalk(repo.dirstate, matcher):
160 lfile = splitstandin(standin)
160 lfile = splitstandin(standin)
161 hash = readstandin(repo, lfile)
161 hash = readstandin(repo, lfile)
162 lfdirstate.normallookup(lfile)
162 lfdirstate.normallookup(lfile)
163 try:
163 try:
164 if hash == hashfile(repo.wjoin(lfile)):
164 if hash == hashfile(repo.wjoin(lfile)):
165 lfdirstate.normal(lfile)
165 lfdirstate.normal(lfile)
166 except OSError, err:
166 except OSError, err:
167 if err.errno != errno.ENOENT:
167 if err.errno != errno.ENOENT:
168 raise
168 raise
169 return lfdirstate
169 return lfdirstate
170
170
171 def lfdirstatestatus(lfdirstate, repo, rev):
171 def lfdirstatestatus(lfdirstate, repo, rev):
172 match = match_.always(repo.root, repo.getcwd())
172 match = match_.always(repo.root, repo.getcwd())
173 s = lfdirstate.status(match, [], False, False, False)
173 s = lfdirstate.status(match, [], False, False, False)
174 unsure, modified, added, removed, missing, unknown, ignored, clean = s
174 unsure, modified, added, removed, missing, unknown, ignored, clean = s
175 for lfile in unsure:
175 for lfile in unsure:
176 if repo[rev][standin(lfile)].data().strip() != \
176 if repo[rev][standin(lfile)].data().strip() != \
177 hashfile(repo.wjoin(lfile)):
177 hashfile(repo.wjoin(lfile)):
178 modified.append(lfile)
178 modified.append(lfile)
179 else:
179 else:
180 clean.append(lfile)
180 clean.append(lfile)
181 lfdirstate.normal(lfile)
181 lfdirstate.normal(lfile)
182 return (modified, added, removed, missing, unknown, ignored, clean)
182 return (modified, added, removed, missing, unknown, ignored, clean)
183
183
184 def listlfiles(repo, rev=None, matcher=None):
184 def listlfiles(repo, rev=None, matcher=None):
185 '''return a list of largefiles in the working copy or the
185 '''return a list of largefiles in the working copy or the
186 specified changeset'''
186 specified changeset'''
187
187
188 if matcher is None:
188 if matcher is None:
189 matcher = getstandinmatcher(repo)
189 matcher = getstandinmatcher(repo)
190
190
191 # ignore unknown files in working directory
191 # ignore unknown files in working directory
192 return [splitstandin(f)
192 return [splitstandin(f)
193 for f in repo[rev].walk(matcher)
193 for f in repo[rev].walk(matcher)
194 if rev is not None or repo.dirstate[f] != '?']
194 if rev is not None or repo.dirstate[f] != '?']
195
195
196 def instore(repo, hash):
196 def instore(repo, hash):
197 return os.path.exists(storepath(repo, hash))
197 return os.path.exists(storepath(repo, hash))
198
198
199 def storepath(repo, hash):
199 def storepath(repo, hash):
200 return repo.join(os.path.join(longname, hash))
200 return repo.join(os.path.join(longname, hash))
201
201
202 def copyfromcache(repo, hash, filename):
202 def copyfromcache(repo, hash, filename):
203 '''Copy the specified largefile from the repo or system cache to
203 '''Copy the specified largefile from the repo or system cache to
204 filename in the repository. Return true on success or false if the
204 filename in the repository. Return true on success or false if the
205 file was not found in either cache (which should not happened:
205 file was not found in either cache (which should not happened:
206 this is meant to be called only after ensuring that the needed
206 this is meant to be called only after ensuring that the needed
207 largefile exists in the cache).'''
207 largefile exists in the cache).'''
208 path = findfile(repo, hash)
208 path = findfile(repo, hash)
209 if path is None:
209 if path is None:
210 return False
210 return False
211 util.makedirs(os.path.dirname(repo.wjoin(filename)))
211 util.makedirs(os.path.dirname(repo.wjoin(filename)))
212 # The write may fail before the file is fully written, but we
212 # The write may fail before the file is fully written, but we
213 # don't use atomic writes in the working copy.
213 # don't use atomic writes in the working copy.
214 shutil.copy(path, repo.wjoin(filename))
214 shutil.copy(path, repo.wjoin(filename))
215 return True
215 return True
216
216
217 def copytostore(repo, rev, file, uploaded=False):
217 def copytostore(repo, rev, file, uploaded=False):
218 hash = readstandin(repo, file, rev)
218 hash = readstandin(repo, file, rev)
219 if instore(repo, hash):
219 if instore(repo, hash):
220 return
220 return
221 copytostoreabsolute(repo, repo.wjoin(file), hash)
221 copytostoreabsolute(repo, repo.wjoin(file), hash)
222
222
223 def copyalltostore(repo, node):
223 def copyalltostore(repo, node):
224 '''Copy all largefiles in a given revision to the store'''
224 '''Copy all largefiles in a given revision to the store'''
225
225
226 ctx = repo[node]
226 ctx = repo[node]
227 for filename in ctx.files():
227 for filename in ctx.files():
228 if isstandin(filename) and filename in ctx.manifest():
228 if isstandin(filename) and filename in ctx.manifest():
229 realfile = splitstandin(filename)
229 realfile = splitstandin(filename)
230 copytostore(repo, ctx.node(), realfile)
230 copytostore(repo, ctx.node(), realfile)
231
231
232
232
233 def copytostoreabsolute(repo, file, hash):
233 def copytostoreabsolute(repo, file, hash):
234 util.makedirs(os.path.dirname(storepath(repo, hash)))
234 util.makedirs(os.path.dirname(storepath(repo, hash)))
235 if inusercache(repo.ui, hash):
235 if inusercache(repo.ui, hash):
236 link(usercachepath(repo.ui, hash), storepath(repo, hash))
236 link(usercachepath(repo.ui, hash), storepath(repo, hash))
237 else:
237 elif not getattr(repo, "_isconverting", False):
238 dst = util.atomictempfile(storepath(repo, hash),
238 dst = util.atomictempfile(storepath(repo, hash),
239 createmode=repo.store.createmode)
239 createmode=repo.store.createmode)
240 for chunk in util.filechunkiter(open(file, 'rb')):
240 for chunk in util.filechunkiter(open(file, 'rb')):
241 dst.write(chunk)
241 dst.write(chunk)
242 dst.close()
242 dst.close()
243 linktousercache(repo, hash)
243 linktousercache(repo, hash)
244
244
245 def linktousercache(repo, hash):
245 def linktousercache(repo, hash):
246 path = usercachepath(repo.ui, hash)
246 path = usercachepath(repo.ui, hash)
247 if path:
247 if path:
248 util.makedirs(os.path.dirname(path))
248 util.makedirs(os.path.dirname(path))
249 link(storepath(repo, hash), path)
249 link(storepath(repo, hash), path)
250
250
251 def getstandinmatcher(repo, pats=[], opts={}):
251 def getstandinmatcher(repo, pats=[], opts={}):
252 '''Return a match object that applies pats to the standin directory'''
252 '''Return a match object that applies pats to the standin directory'''
253 standindir = repo.pathto(shortname)
253 standindir = repo.pathto(shortname)
254 if pats:
254 if pats:
255 # patterns supplied: search standin directory relative to current dir
255 # patterns supplied: search standin directory relative to current dir
256 cwd = repo.getcwd()
256 cwd = repo.getcwd()
257 if os.path.isabs(cwd):
257 if os.path.isabs(cwd):
258 # cwd is an absolute path for hg -R <reponame>
258 # cwd is an absolute path for hg -R <reponame>
259 # work relative to the repository root in this case
259 # work relative to the repository root in this case
260 cwd = ''
260 cwd = ''
261 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
261 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
262 elif os.path.isdir(standindir):
262 elif os.path.isdir(standindir):
263 # no patterns: relative to repo root
263 # no patterns: relative to repo root
264 pats = [standindir]
264 pats = [standindir]
265 else:
265 else:
266 # no patterns and no standin dir: return matcher that matches nothing
266 # no patterns and no standin dir: return matcher that matches nothing
267 match = match_.match(repo.root, None, [], exact=True)
267 match = match_.match(repo.root, None, [], exact=True)
268 match.matchfn = lambda f: False
268 match.matchfn = lambda f: False
269 return match
269 return match
270 return getmatcher(repo, pats, opts, showbad=False)
270 return getmatcher(repo, pats, opts, showbad=False)
271
271
272 def getmatcher(repo, pats=[], opts={}, showbad=True):
272 def getmatcher(repo, pats=[], opts={}, showbad=True):
273 '''Wrapper around scmutil.match() that adds showbad: if false,
273 '''Wrapper around scmutil.match() that adds showbad: if false,
274 neuter the match object's bad() method so it does not print any
274 neuter the match object's bad() method so it does not print any
275 warnings about missing files or directories.'''
275 warnings about missing files or directories.'''
276 match = scmutil.match(repo[None], pats, opts)
276 match = scmutil.match(repo[None], pats, opts)
277
277
278 if not showbad:
278 if not showbad:
279 match.bad = lambda f, msg: None
279 match.bad = lambda f, msg: None
280 return match
280 return match
281
281
282 def composestandinmatcher(repo, rmatcher):
282 def composestandinmatcher(repo, rmatcher):
283 '''Return a matcher that accepts standins corresponding to the
283 '''Return a matcher that accepts standins corresponding to the
284 files accepted by rmatcher. Pass the list of files in the matcher
284 files accepted by rmatcher. Pass the list of files in the matcher
285 as the paths specified by the user.'''
285 as the paths specified by the user.'''
286 smatcher = getstandinmatcher(repo, rmatcher.files())
286 smatcher = getstandinmatcher(repo, rmatcher.files())
287 isstandin = smatcher.matchfn
287 isstandin = smatcher.matchfn
288 def composedmatchfn(f):
288 def composedmatchfn(f):
289 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
289 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
290 smatcher.matchfn = composedmatchfn
290 smatcher.matchfn = composedmatchfn
291
291
292 return smatcher
292 return smatcher
293
293
294 def standin(filename):
294 def standin(filename):
295 '''Return the repo-relative path to the standin for the specified big
295 '''Return the repo-relative path to the standin for the specified big
296 file.'''
296 file.'''
297 # Notes:
297 # Notes:
298 # 1) Some callers want an absolute path, but for instance addlargefiles
298 # 1) Some callers want an absolute path, but for instance addlargefiles
299 # needs it repo-relative so it can be passed to repoadd(). So leave
299 # needs it repo-relative so it can be passed to repoadd(). So leave
300 # it up to the caller to use repo.wjoin() to get an absolute path.
300 # it up to the caller to use repo.wjoin() to get an absolute path.
301 # 2) Join with '/' because that's what dirstate always uses, even on
301 # 2) Join with '/' because that's what dirstate always uses, even on
302 # Windows. Change existing separator to '/' first in case we are
302 # Windows. Change existing separator to '/' first in case we are
303 # passed filenames from an external source (like the command line).
303 # passed filenames from an external source (like the command line).
304 return shortname + '/' + util.pconvert(filename)
304 return shortname + '/' + util.pconvert(filename)
305
305
306 def isstandin(filename):
306 def isstandin(filename):
307 '''Return true if filename is a big file standin. filename must be
307 '''Return true if filename is a big file standin. filename must be
308 in Mercurial's internal form (slash-separated).'''
308 in Mercurial's internal form (slash-separated).'''
309 return filename.startswith(shortname + '/')
309 return filename.startswith(shortname + '/')
310
310
311 def splitstandin(filename):
311 def splitstandin(filename):
312 # Split on / because that's what dirstate always uses, even on Windows.
312 # Split on / because that's what dirstate always uses, even on Windows.
313 # Change local separator to / first just in case we are passed filenames
313 # Change local separator to / first just in case we are passed filenames
314 # from an external source (like the command line).
314 # from an external source (like the command line).
315 bits = util.pconvert(filename).split('/', 1)
315 bits = util.pconvert(filename).split('/', 1)
316 if len(bits) == 2 and bits[0] == shortname:
316 if len(bits) == 2 and bits[0] == shortname:
317 return bits[1]
317 return bits[1]
318 else:
318 else:
319 return None
319 return None
320
320
321 def updatestandin(repo, standin):
321 def updatestandin(repo, standin):
322 file = repo.wjoin(splitstandin(standin))
322 file = repo.wjoin(splitstandin(standin))
323 if os.path.exists(file):
323 if os.path.exists(file):
324 hash = hashfile(file)
324 hash = hashfile(file)
325 executable = getexecutable(file)
325 executable = getexecutable(file)
326 writestandin(repo, standin, hash, executable)
326 writestandin(repo, standin, hash, executable)
327
327
328 def readstandin(repo, filename, node=None):
328 def readstandin(repo, filename, node=None):
329 '''read hex hash from standin for filename at given node, or working
329 '''read hex hash from standin for filename at given node, or working
330 directory if no node is given'''
330 directory if no node is given'''
331 return repo[node][standin(filename)].data().strip()
331 return repo[node][standin(filename)].data().strip()
332
332
333 def writestandin(repo, standin, hash, executable):
333 def writestandin(repo, standin, hash, executable):
334 '''write hash to <repo.root>/<standin>'''
334 '''write hash to <repo.root>/<standin>'''
335 writehash(hash, repo.wjoin(standin), executable)
335 writehash(hash, repo.wjoin(standin), executable)
336
336
337 def copyandhash(instream, outfile):
337 def copyandhash(instream, outfile):
338 '''Read bytes from instream (iterable) and write them to outfile,
338 '''Read bytes from instream (iterable) and write them to outfile,
339 computing the SHA-1 hash of the data along the way. Close outfile
339 computing the SHA-1 hash of the data along the way. Close outfile
340 when done and return the binary hash.'''
340 when done and return the binary hash.'''
341 hasher = util.sha1('')
341 hasher = util.sha1('')
342 for data in instream:
342 for data in instream:
343 hasher.update(data)
343 hasher.update(data)
344 outfile.write(data)
344 outfile.write(data)
345
345
346 # Blecch: closing a file that somebody else opened is rude and
346 # Blecch: closing a file that somebody else opened is rude and
347 # wrong. But it's so darn convenient and practical! After all,
347 # wrong. But it's so darn convenient and practical! After all,
348 # outfile was opened just to copy and hash.
348 # outfile was opened just to copy and hash.
349 outfile.close()
349 outfile.close()
350
350
351 return hasher.digest()
351 return hasher.digest()
352
352
353 def hashrepofile(repo, file):
353 def hashrepofile(repo, file):
354 return hashfile(repo.wjoin(file))
354 return hashfile(repo.wjoin(file))
355
355
356 def hashfile(file):
356 def hashfile(file):
357 if not os.path.exists(file):
357 if not os.path.exists(file):
358 return ''
358 return ''
359 hasher = util.sha1('')
359 hasher = util.sha1('')
360 fd = open(file, 'rb')
360 fd = open(file, 'rb')
361 for data in blockstream(fd):
361 for data in blockstream(fd):
362 hasher.update(data)
362 hasher.update(data)
363 fd.close()
363 fd.close()
364 return hasher.hexdigest()
364 return hasher.hexdigest()
365
365
366 class limitreader(object):
366 class limitreader(object):
367 def __init__(self, f, limit):
367 def __init__(self, f, limit):
368 self.f = f
368 self.f = f
369 self.limit = limit
369 self.limit = limit
370
370
371 def read(self, length):
371 def read(self, length):
372 if self.limit == 0:
372 if self.limit == 0:
373 return ''
373 return ''
374 length = length > self.limit and self.limit or length
374 length = length > self.limit and self.limit or length
375 self.limit -= length
375 self.limit -= length
376 return self.f.read(length)
376 return self.f.read(length)
377
377
378 def close(self):
378 def close(self):
379 pass
379 pass
380
380
381 def blockstream(infile, blocksize=128 * 1024):
381 def blockstream(infile, blocksize=128 * 1024):
382 """Generator that yields blocks of data from infile and closes infile."""
382 """Generator that yields blocks of data from infile and closes infile."""
383 while True:
383 while True:
384 data = infile.read(blocksize)
384 data = infile.read(blocksize)
385 if not data:
385 if not data:
386 break
386 break
387 yield data
387 yield data
388 # same blecch as copyandhash() above
388 # same blecch as copyandhash() above
389 infile.close()
389 infile.close()
390
390
391 def writehash(hash, filename, executable):
391 def writehash(hash, filename, executable):
392 util.makedirs(os.path.dirname(filename))
392 util.makedirs(os.path.dirname(filename))
393 util.writefile(filename, hash + '\n')
393 util.writefile(filename, hash + '\n')
394 os.chmod(filename, getmode(executable))
394 os.chmod(filename, getmode(executable))
395
395
396 def getexecutable(filename):
396 def getexecutable(filename):
397 mode = os.stat(filename).st_mode
397 mode = os.stat(filename).st_mode
398 return ((mode & stat.S_IXUSR) and
398 return ((mode & stat.S_IXUSR) and
399 (mode & stat.S_IXGRP) and
399 (mode & stat.S_IXGRP) and
400 (mode & stat.S_IXOTH))
400 (mode & stat.S_IXOTH))
401
401
402 def getmode(executable):
402 def getmode(executable):
403 if executable:
403 if executable:
404 return 0755
404 return 0755
405 else:
405 else:
406 return 0644
406 return 0644
407
407
408 def urljoin(first, second, *arg):
408 def urljoin(first, second, *arg):
409 def join(left, right):
409 def join(left, right):
410 if not left.endswith('/'):
410 if not left.endswith('/'):
411 left += '/'
411 left += '/'
412 if right.startswith('/'):
412 if right.startswith('/'):
413 right = right[1:]
413 right = right[1:]
414 return left + right
414 return left + right
415
415
416 url = join(first, second)
416 url = join(first, second)
417 for a in arg:
417 for a in arg:
418 url = join(url, a)
418 url = join(url, a)
419 return url
419 return url
420
420
421 def hexsha1(data):
421 def hexsha1(data):
422 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
422 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
423 object data"""
423 object data"""
424 h = util.sha1()
424 h = util.sha1()
425 for chunk in util.filechunkiter(data):
425 for chunk in util.filechunkiter(data):
426 h.update(chunk)
426 h.update(chunk)
427 return h.hexdigest()
427 return h.hexdigest()
428
428
429 def httpsendfile(ui, filename):
429 def httpsendfile(ui, filename):
430 return httpconnection.httpsendfile(ui, filename, 'rb')
430 return httpconnection.httpsendfile(ui, filename, 'rb')
431
431
432 def unixpath(path):
432 def unixpath(path):
433 '''Return a version of path normalized for use with the lfdirstate.'''
433 '''Return a version of path normalized for use with the lfdirstate.'''
434 return util.pconvert(os.path.normpath(path))
434 return util.pconvert(os.path.normpath(path))
435
435
436 def islfilesrepo(repo):
436 def islfilesrepo(repo):
437 if ('largefiles' in repo.requirements and
437 if ('largefiles' in repo.requirements and
438 util.any(shortname + '/' in f[0] for f in repo.store.datafiles())):
438 util.any(shortname + '/' in f[0] for f in repo.store.datafiles())):
439 return True
439 return True
440
440
441 return util.any(openlfdirstate(repo.ui, repo, False))
441 return util.any(openlfdirstate(repo.ui, repo, False))
442
442
443 class storeprotonotcapable(Exception):
443 class storeprotonotcapable(Exception):
444 def __init__(self, storetypes):
444 def __init__(self, storetypes):
445 self.storetypes = storetypes
445 self.storetypes = storetypes
446
446
447 def getcurrentheads(repo):
447 def getcurrentheads(repo):
448 branches = repo.branchmap()
448 branches = repo.branchmap()
449 heads = []
449 heads = []
450 for branch in branches:
450 for branch in branches:
451 newheads = repo.branchheads(branch)
451 newheads = repo.branchheads(branch)
452 heads = heads + newheads
452 heads = heads + newheads
453 return heads
453 return heads
454
454
455 def getstandinsstate(repo):
455 def getstandinsstate(repo):
456 standins = []
456 standins = []
457 matcher = getstandinmatcher(repo)
457 matcher = getstandinmatcher(repo)
458 for standin in dirstatewalk(repo.dirstate, matcher):
458 for standin in dirstatewalk(repo.dirstate, matcher):
459 lfile = splitstandin(standin)
459 lfile = splitstandin(standin)
460 standins.append((lfile, readstandin(repo, lfile)))
460 standins.append((lfile, readstandin(repo, lfile)))
461 return standins
461 return standins
462
462
463 def getlfilestoupdate(oldstandins, newstandins):
463 def getlfilestoupdate(oldstandins, newstandins):
464 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
464 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
465 filelist = []
465 filelist = []
466 for f in changedstandins:
466 for f in changedstandins:
467 if f[0] not in filelist:
467 if f[0] not in filelist:
468 filelist.append(f[0])
468 filelist.append(f[0])
469 return filelist
469 return filelist
@@ -1,1115 +1,1123
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 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10
10
11 import os
11 import os
12 import copy
12 import copy
13
13
14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
15 node, archival, error, merge
15 node, archival, error, merge
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial.node import hex
17 from mercurial.node import hex
18 from hgext import rebase
18 from hgext import rebase
19
19
20 import lfutil
20 import lfutil
21 import lfcommands
21 import lfcommands
22
22
23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24
24
25 def installnormalfilesmatchfn(manifest):
25 def installnormalfilesmatchfn(manifest):
26 '''overrides scmutil.match so that the matcher it returns will ignore all
26 '''overrides scmutil.match so that the matcher it returns will ignore all
27 largefiles'''
27 largefiles'''
28 oldmatch = None # for the closure
28 oldmatch = None # for the closure
29 def overridematch(ctx, pats=[], opts={}, globbed=False,
29 def overridematch(ctx, pats=[], opts={}, globbed=False,
30 default='relpath'):
30 default='relpath'):
31 match = oldmatch(ctx, pats, opts, globbed, default)
31 match = oldmatch(ctx, pats, opts, globbed, default)
32 m = copy.copy(match)
32 m = copy.copy(match)
33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
34 manifest)
34 manifest)
35 m._files = filter(notlfile, m._files)
35 m._files = filter(notlfile, m._files)
36 m._fmap = set(m._files)
36 m._fmap = set(m._files)
37 origmatchfn = m.matchfn
37 origmatchfn = m.matchfn
38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
39 return m
39 return m
40 oldmatch = installmatchfn(overridematch)
40 oldmatch = installmatchfn(overridematch)
41
41
42 def installmatchfn(f):
42 def installmatchfn(f):
43 oldmatch = scmutil.match
43 oldmatch = scmutil.match
44 setattr(f, 'oldmatch', oldmatch)
44 setattr(f, 'oldmatch', oldmatch)
45 scmutil.match = f
45 scmutil.match = f
46 return oldmatch
46 return oldmatch
47
47
48 def restorematchfn():
48 def restorematchfn():
49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
50 was called. no-op if scmutil.match is its original function.
50 was called. no-op if scmutil.match is its original function.
51
51
52 Note that n calls to installnormalfilesmatchfn will require n calls to
52 Note that n calls to installnormalfilesmatchfn will require n calls to
53 restore matchfn to reverse'''
53 restore matchfn to reverse'''
54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
55
55
56 def addlargefiles(ui, repo, *pats, **opts):
56 def addlargefiles(ui, repo, *pats, **opts):
57 large = opts.pop('large', None)
57 large = opts.pop('large', None)
58 lfsize = lfutil.getminsize(
58 lfsize = lfutil.getminsize(
59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
60
60
61 lfmatcher = None
61 lfmatcher = None
62 if lfutil.islfilesrepo(repo):
62 if lfutil.islfilesrepo(repo):
63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
64 if lfpats:
64 if lfpats:
65 lfmatcher = match_.match(repo.root, '', list(lfpats))
65 lfmatcher = match_.match(repo.root, '', list(lfpats))
66
66
67 lfnames = []
67 lfnames = []
68 m = scmutil.match(repo[None], pats, opts)
68 m = scmutil.match(repo[None], pats, opts)
69 m.bad = lambda x, y: None
69 m.bad = lambda x, y: None
70 wctx = repo[None]
70 wctx = repo[None]
71 for f in repo.walk(m):
71 for f in repo.walk(m):
72 exact = m.exact(f)
72 exact = m.exact(f)
73 lfile = lfutil.standin(f) in wctx
73 lfile = lfutil.standin(f) in wctx
74 nfile = f in wctx
74 nfile = f in wctx
75 exists = lfile or nfile
75 exists = lfile or nfile
76
76
77 # Don't warn the user when they attempt to add a normal tracked file.
77 # Don't warn the user when they attempt to add a normal tracked file.
78 # The normal add code will do that for us.
78 # The normal add code will do that for us.
79 if exact and exists:
79 if exact and exists:
80 if lfile:
80 if lfile:
81 ui.warn(_('%s already a largefile\n') % f)
81 ui.warn(_('%s already a largefile\n') % f)
82 continue
82 continue
83
83
84 if (exact or not exists) and not lfutil.isstandin(f):
84 if (exact or not exists) and not lfutil.isstandin(f):
85 wfile = repo.wjoin(f)
85 wfile = repo.wjoin(f)
86
86
87 # In case the file was removed previously, but not committed
87 # In case the file was removed previously, but not committed
88 # (issue3507)
88 # (issue3507)
89 if not os.path.exists(wfile):
89 if not os.path.exists(wfile):
90 continue
90 continue
91
91
92 abovemin = (lfsize and
92 abovemin = (lfsize and
93 os.lstat(wfile).st_size >= lfsize * 1024 * 1024)
93 os.lstat(wfile).st_size >= lfsize * 1024 * 1024)
94 if large or abovemin or (lfmatcher and lfmatcher(f)):
94 if large or abovemin or (lfmatcher and lfmatcher(f)):
95 lfnames.append(f)
95 lfnames.append(f)
96 if ui.verbose or not exact:
96 if ui.verbose or not exact:
97 ui.status(_('adding %s as a largefile\n') % m.rel(f))
97 ui.status(_('adding %s as a largefile\n') % m.rel(f))
98
98
99 bad = []
99 bad = []
100 standins = []
100 standins = []
101
101
102 # Need to lock, otherwise there could be a race condition between
102 # Need to lock, otherwise there could be a race condition between
103 # when standins are created and added to the repo.
103 # when standins are created and added to the repo.
104 wlock = repo.wlock()
104 wlock = repo.wlock()
105 try:
105 try:
106 if not opts.get('dry_run'):
106 if not opts.get('dry_run'):
107 lfdirstate = lfutil.openlfdirstate(ui, repo)
107 lfdirstate = lfutil.openlfdirstate(ui, repo)
108 for f in lfnames:
108 for f in lfnames:
109 standinname = lfutil.standin(f)
109 standinname = lfutil.standin(f)
110 lfutil.writestandin(repo, standinname, hash='',
110 lfutil.writestandin(repo, standinname, hash='',
111 executable=lfutil.getexecutable(repo.wjoin(f)))
111 executable=lfutil.getexecutable(repo.wjoin(f)))
112 standins.append(standinname)
112 standins.append(standinname)
113 if lfdirstate[f] == 'r':
113 if lfdirstate[f] == 'r':
114 lfdirstate.normallookup(f)
114 lfdirstate.normallookup(f)
115 else:
115 else:
116 lfdirstate.add(f)
116 lfdirstate.add(f)
117 lfdirstate.write()
117 lfdirstate.write()
118 bad += [lfutil.splitstandin(f)
118 bad += [lfutil.splitstandin(f)
119 for f in lfutil.repoadd(repo, standins)
119 for f in lfutil.repoadd(repo, standins)
120 if f in m.files()]
120 if f in m.files()]
121 finally:
121 finally:
122 wlock.release()
122 wlock.release()
123 return bad
123 return bad
124
124
125 def removelargefiles(ui, repo, *pats, **opts):
125 def removelargefiles(ui, repo, *pats, **opts):
126 after = opts.get('after')
126 after = opts.get('after')
127 if not pats and not after:
127 if not pats and not after:
128 raise util.Abort(_('no files specified'))
128 raise util.Abort(_('no files specified'))
129 m = scmutil.match(repo[None], pats, opts)
129 m = scmutil.match(repo[None], pats, opts)
130 try:
130 try:
131 repo.lfstatus = True
131 repo.lfstatus = True
132 s = repo.status(match=m, clean=True)
132 s = repo.status(match=m, clean=True)
133 finally:
133 finally:
134 repo.lfstatus = False
134 repo.lfstatus = False
135 manifest = repo[None].manifest()
135 manifest = repo[None].manifest()
136 modified, added, deleted, clean = [[f for f in list
136 modified, added, deleted, clean = [[f for f in list
137 if lfutil.standin(f) in manifest]
137 if lfutil.standin(f) in manifest]
138 for list in [s[0], s[1], s[3], s[6]]]
138 for list in [s[0], s[1], s[3], s[6]]]
139
139
140 def warn(files, reason):
140 def warn(files, reason):
141 for f in files:
141 for f in files:
142 ui.warn(_('not removing %s: %s (use forget to undo)\n')
142 ui.warn(_('not removing %s: %s (use forget to undo)\n')
143 % (m.rel(f), reason))
143 % (m.rel(f), reason))
144 return int(len(files) > 0)
144 return int(len(files) > 0)
145
145
146 result = 0
146 result = 0
147
147
148 if after:
148 if after:
149 remove, forget = deleted, []
149 remove, forget = deleted, []
150 result = warn(modified + added + clean, _('file still exists'))
150 result = warn(modified + added + clean, _('file still exists'))
151 else:
151 else:
152 remove, forget = deleted + clean, []
152 remove, forget = deleted + clean, []
153 result = warn(modified, _('file is modified'))
153 result = warn(modified, _('file is modified'))
154 result = warn(added, _('file has been marked for add')) or result
154 result = warn(added, _('file has been marked for add')) or result
155
155
156 for f in sorted(remove + forget):
156 for f in sorted(remove + forget):
157 if ui.verbose or not m.exact(f):
157 if ui.verbose or not m.exact(f):
158 ui.status(_('removing %s\n') % m.rel(f))
158 ui.status(_('removing %s\n') % m.rel(f))
159
159
160 # Need to lock because standin files are deleted then removed from the
160 # Need to lock because standin files are deleted then removed from the
161 # repository and we could race in-between.
161 # repository and we could race in-between.
162 wlock = repo.wlock()
162 wlock = repo.wlock()
163 try:
163 try:
164 lfdirstate = lfutil.openlfdirstate(ui, repo)
164 lfdirstate = lfutil.openlfdirstate(ui, repo)
165 for f in remove:
165 for f in remove:
166 if not after:
166 if not after:
167 # If this is being called by addremove, notify the user that we
167 # If this is being called by addremove, notify the user that we
168 # are removing the file.
168 # are removing the file.
169 if getattr(repo, "_isaddremove", False):
169 if getattr(repo, "_isaddremove", False):
170 ui.status(_('removing %s\n') % f)
170 ui.status(_('removing %s\n') % f)
171 if os.path.exists(repo.wjoin(f)):
171 if os.path.exists(repo.wjoin(f)):
172 util.unlinkpath(repo.wjoin(f))
172 util.unlinkpath(repo.wjoin(f))
173 lfdirstate.remove(f)
173 lfdirstate.remove(f)
174 lfdirstate.write()
174 lfdirstate.write()
175 forget = [lfutil.standin(f) for f in forget]
175 forget = [lfutil.standin(f) for f in forget]
176 remove = [lfutil.standin(f) for f in remove]
176 remove = [lfutil.standin(f) for f in remove]
177 lfutil.repoforget(repo, forget)
177 lfutil.repoforget(repo, forget)
178 # If this is being called by addremove, let the original addremove
178 # If this is being called by addremove, let the original addremove
179 # function handle this.
179 # function handle this.
180 if not getattr(repo, "_isaddremove", False):
180 if not getattr(repo, "_isaddremove", False):
181 lfutil.reporemove(repo, remove, unlink=True)
181 lfutil.reporemove(repo, remove, unlink=True)
182 else:
182 else:
183 lfutil.reporemove(repo, remove, unlink=False)
183 lfutil.reporemove(repo, remove, unlink=False)
184 finally:
184 finally:
185 wlock.release()
185 wlock.release()
186
186
187 return result
187 return result
188
188
189 # For overriding mercurial.hgweb.webcommands so that largefiles will
189 # For overriding mercurial.hgweb.webcommands so that largefiles will
190 # appear at their right place in the manifests.
190 # appear at their right place in the manifests.
191 def decodepath(orig, path):
191 def decodepath(orig, path):
192 return lfutil.splitstandin(path) or path
192 return lfutil.splitstandin(path) or path
193
193
194 # -- Wrappers: modify existing commands --------------------------------
194 # -- Wrappers: modify existing commands --------------------------------
195
195
196 # Add works by going through the files that the user wanted to add and
196 # Add works by going through the files that the user wanted to add and
197 # checking if they should be added as largefiles. Then it makes a new
197 # checking if they should be added as largefiles. Then it makes a new
198 # matcher which matches only the normal files and runs the original
198 # matcher which matches only the normal files and runs the original
199 # version of add.
199 # version of add.
200 def overrideadd(orig, ui, repo, *pats, **opts):
200 def overrideadd(orig, ui, repo, *pats, **opts):
201 normal = opts.pop('normal')
201 normal = opts.pop('normal')
202 if normal:
202 if normal:
203 if opts.get('large'):
203 if opts.get('large'):
204 raise util.Abort(_('--normal cannot be used with --large'))
204 raise util.Abort(_('--normal cannot be used with --large'))
205 return orig(ui, repo, *pats, **opts)
205 return orig(ui, repo, *pats, **opts)
206 bad = addlargefiles(ui, repo, *pats, **opts)
206 bad = addlargefiles(ui, repo, *pats, **opts)
207 installnormalfilesmatchfn(repo[None].manifest())
207 installnormalfilesmatchfn(repo[None].manifest())
208 result = orig(ui, repo, *pats, **opts)
208 result = orig(ui, repo, *pats, **opts)
209 restorematchfn()
209 restorematchfn()
210
210
211 return (result == 1 or bad) and 1 or 0
211 return (result == 1 or bad) and 1 or 0
212
212
213 def overrideremove(orig, ui, repo, *pats, **opts):
213 def overrideremove(orig, ui, repo, *pats, **opts):
214 installnormalfilesmatchfn(repo[None].manifest())
214 installnormalfilesmatchfn(repo[None].manifest())
215 result = orig(ui, repo, *pats, **opts)
215 result = orig(ui, repo, *pats, **opts)
216 restorematchfn()
216 restorematchfn()
217 return removelargefiles(ui, repo, *pats, **opts) or result
217 return removelargefiles(ui, repo, *pats, **opts) or result
218
218
219 def overridestatusfn(orig, repo, rev2, **opts):
219 def overridestatusfn(orig, repo, rev2, **opts):
220 try:
220 try:
221 repo._repo.lfstatus = True
221 repo._repo.lfstatus = True
222 return orig(repo, rev2, **opts)
222 return orig(repo, rev2, **opts)
223 finally:
223 finally:
224 repo._repo.lfstatus = False
224 repo._repo.lfstatus = False
225
225
226 def overridestatus(orig, ui, repo, *pats, **opts):
226 def overridestatus(orig, ui, repo, *pats, **opts):
227 try:
227 try:
228 repo.lfstatus = True
228 repo.lfstatus = True
229 return orig(ui, repo, *pats, **opts)
229 return orig(ui, repo, *pats, **opts)
230 finally:
230 finally:
231 repo.lfstatus = False
231 repo.lfstatus = False
232
232
233 def overridedirty(orig, repo, ignoreupdate=False):
233 def overridedirty(orig, repo, ignoreupdate=False):
234 try:
234 try:
235 repo._repo.lfstatus = True
235 repo._repo.lfstatus = True
236 return orig(repo, ignoreupdate)
236 return orig(repo, ignoreupdate)
237 finally:
237 finally:
238 repo._repo.lfstatus = False
238 repo._repo.lfstatus = False
239
239
240 def overridelog(orig, ui, repo, *pats, **opts):
240 def overridelog(orig, ui, repo, *pats, **opts):
241 try:
241 try:
242 repo.lfstatus = True
242 repo.lfstatus = True
243 return orig(ui, repo, *pats, **opts)
243 return orig(ui, repo, *pats, **opts)
244 finally:
244 finally:
245 repo.lfstatus = False
245 repo.lfstatus = False
246
246
247 def overrideverify(orig, ui, repo, *pats, **opts):
247 def overrideverify(orig, ui, repo, *pats, **opts):
248 large = opts.pop('large', False)
248 large = opts.pop('large', False)
249 all = opts.pop('lfa', False)
249 all = opts.pop('lfa', False)
250 contents = opts.pop('lfc', False)
250 contents = opts.pop('lfc', False)
251
251
252 result = orig(ui, repo, *pats, **opts)
252 result = orig(ui, repo, *pats, **opts)
253 if large:
253 if large:
254 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
254 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
255 return result
255 return result
256
256
257 # Override needs to refresh standins so that update's normal merge
257 # Override needs to refresh standins so that update's normal merge
258 # will go through properly. Then the other update hook (overriding repo.update)
258 # will go through properly. Then the other update hook (overriding repo.update)
259 # will get the new files. Filemerge is also overridden so that the merge
259 # will get the new files. Filemerge is also overridden so that the merge
260 # will merge standins correctly.
260 # will merge standins correctly.
261 def overrideupdate(orig, ui, repo, *pats, **opts):
261 def overrideupdate(orig, ui, repo, *pats, **opts):
262 lfdirstate = lfutil.openlfdirstate(ui, repo)
262 lfdirstate = lfutil.openlfdirstate(ui, repo)
263 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
263 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
264 False, False)
264 False, False)
265 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
265 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
266
266
267 # Need to lock between the standins getting updated and their
267 # Need to lock between the standins getting updated and their
268 # largefiles getting updated
268 # largefiles getting updated
269 wlock = repo.wlock()
269 wlock = repo.wlock()
270 try:
270 try:
271 if opts['check']:
271 if opts['check']:
272 mod = len(modified) > 0
272 mod = len(modified) > 0
273 for lfile in unsure:
273 for lfile in unsure:
274 standin = lfutil.standin(lfile)
274 standin = lfutil.standin(lfile)
275 if repo['.'][standin].data().strip() != \
275 if repo['.'][standin].data().strip() != \
276 lfutil.hashfile(repo.wjoin(lfile)):
276 lfutil.hashfile(repo.wjoin(lfile)):
277 mod = True
277 mod = True
278 else:
278 else:
279 lfdirstate.normal(lfile)
279 lfdirstate.normal(lfile)
280 lfdirstate.write()
280 lfdirstate.write()
281 if mod:
281 if mod:
282 raise util.Abort(_('uncommitted local changes'))
282 raise util.Abort(_('uncommitted local changes'))
283 # XXX handle removed differently
283 # XXX handle removed differently
284 if not opts['clean']:
284 if not opts['clean']:
285 for lfile in unsure + modified + added:
285 for lfile in unsure + modified + added:
286 lfutil.updatestandin(repo, lfutil.standin(lfile))
286 lfutil.updatestandin(repo, lfutil.standin(lfile))
287 finally:
287 finally:
288 wlock.release()
288 wlock.release()
289 return orig(ui, repo, *pats, **opts)
289 return orig(ui, repo, *pats, **opts)
290
290
291 # Before starting the manifest merge, merge.updates will call
291 # Before starting the manifest merge, merge.updates will call
292 # _checkunknown to check if there are any files in the merged-in
292 # _checkunknown to check if there are any files in the merged-in
293 # changeset that collide with unknown files in the working copy.
293 # changeset that collide with unknown files in the working copy.
294 #
294 #
295 # The largefiles are seen as unknown, so this prevents us from merging
295 # The largefiles are seen as unknown, so this prevents us from merging
296 # in a file 'foo' if we already have a largefile with the same name.
296 # in a file 'foo' if we already have a largefile with the same name.
297 #
297 #
298 # The overridden function filters the unknown files by removing any
298 # The overridden function filters the unknown files by removing any
299 # largefiles. This makes the merge proceed and we can then handle this
299 # largefiles. This makes the merge proceed and we can then handle this
300 # case further in the overridden manifestmerge function below.
300 # case further in the overridden manifestmerge function below.
301 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
301 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
302 if lfutil.standin(f) in wctx:
302 if lfutil.standin(f) in wctx:
303 return False
303 return False
304 return origfn(repo, wctx, mctx, f)
304 return origfn(repo, wctx, mctx, f)
305
305
306 # The manifest merge handles conflicts on the manifest level. We want
306 # The manifest merge handles conflicts on the manifest level. We want
307 # to handle changes in largefile-ness of files at this level too.
307 # to handle changes in largefile-ness of files at this level too.
308 #
308 #
309 # The strategy is to run the original manifestmerge and then process
309 # The strategy is to run the original manifestmerge and then process
310 # the action list it outputs. There are two cases we need to deal with:
310 # the action list it outputs. There are two cases we need to deal with:
311 #
311 #
312 # 1. Normal file in p1, largefile in p2. Here the largefile is
312 # 1. Normal file in p1, largefile in p2. Here the largefile is
313 # detected via its standin file, which will enter the working copy
313 # detected via its standin file, which will enter the working copy
314 # with a "get" action. It is not "merge" since the standin is all
314 # with a "get" action. It is not "merge" since the standin is all
315 # Mercurial is concerned with at this level -- the link to the
315 # Mercurial is concerned with at this level -- the link to the
316 # existing normal file is not relevant here.
316 # existing normal file is not relevant here.
317 #
317 #
318 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
318 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
319 # since the largefile will be present in the working copy and
319 # since the largefile will be present in the working copy and
320 # different from the normal file in p2. Mercurial therefore
320 # different from the normal file in p2. Mercurial therefore
321 # triggers a merge action.
321 # triggers a merge action.
322 #
322 #
323 # In both cases, we prompt the user and emit new actions to either
323 # In both cases, we prompt the user and emit new actions to either
324 # remove the standin (if the normal file was kept) or to remove the
324 # remove the standin (if the normal file was kept) or to remove the
325 # normal file and get the standin (if the largefile was kept). The
325 # normal file and get the standin (if the largefile was kept). The
326 # default prompt answer is to use the largefile version since it was
326 # default prompt answer is to use the largefile version since it was
327 # presumably changed on purpose.
327 # presumably changed on purpose.
328 #
328 #
329 # Finally, the merge.applyupdates function will then take care of
329 # Finally, the merge.applyupdates function will then take care of
330 # writing the files into the working copy and lfcommands.updatelfiles
330 # writing the files into the working copy and lfcommands.updatelfiles
331 # will update the largefiles.
331 # will update the largefiles.
332 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
332 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
333 actions = origfn(repo, p1, p2, pa, overwrite, partial)
333 actions = origfn(repo, p1, p2, pa, overwrite, partial)
334 processed = []
334 processed = []
335
335
336 for action in actions:
336 for action in actions:
337 if overwrite:
337 if overwrite:
338 processed.append(action)
338 processed.append(action)
339 continue
339 continue
340 f, m = action[:2]
340 f, m = action[:2]
341
341
342 choices = (_('&Largefile'), _('&Normal file'))
342 choices = (_('&Largefile'), _('&Normal file'))
343 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
343 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
344 # Case 1: normal file in the working copy, largefile in
344 # Case 1: normal file in the working copy, largefile in
345 # the second parent
345 # the second parent
346 lfile = lfutil.splitstandin(f)
346 lfile = lfutil.splitstandin(f)
347 standin = f
347 standin = f
348 msg = _('%s has been turned into a largefile\n'
348 msg = _('%s has been turned into a largefile\n'
349 'use (l)argefile or keep as (n)ormal file?') % lfile
349 'use (l)argefile or keep as (n)ormal file?') % lfile
350 if repo.ui.promptchoice(msg, choices, 0) == 0:
350 if repo.ui.promptchoice(msg, choices, 0) == 0:
351 processed.append((lfile, "r"))
351 processed.append((lfile, "r"))
352 processed.append((standin, "g", p2.flags(standin)))
352 processed.append((standin, "g", p2.flags(standin)))
353 else:
353 else:
354 processed.append((standin, "r"))
354 processed.append((standin, "r"))
355 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
355 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
356 # Case 2: largefile in the working copy, normal file in
356 # Case 2: largefile in the working copy, normal file in
357 # the second parent
357 # the second parent
358 standin = lfutil.standin(f)
358 standin = lfutil.standin(f)
359 lfile = f
359 lfile = f
360 msg = _('%s has been turned into a normal file\n'
360 msg = _('%s has been turned into a normal file\n'
361 'keep as (l)argefile or use (n)ormal file?') % lfile
361 'keep as (l)argefile or use (n)ormal file?') % lfile
362 if repo.ui.promptchoice(msg, choices, 0) == 0:
362 if repo.ui.promptchoice(msg, choices, 0) == 0:
363 processed.append((lfile, "r"))
363 processed.append((lfile, "r"))
364 else:
364 else:
365 processed.append((standin, "r"))
365 processed.append((standin, "r"))
366 processed.append((lfile, "g", p2.flags(lfile)))
366 processed.append((lfile, "g", p2.flags(lfile)))
367 else:
367 else:
368 processed.append(action)
368 processed.append(action)
369
369
370 return processed
370 return processed
371
371
372 # Override filemerge to prompt the user about how they wish to merge
372 # Override filemerge to prompt the user about how they wish to merge
373 # largefiles. This will handle identical edits, and copy/rename +
373 # largefiles. This will handle identical edits, and copy/rename +
374 # edit without prompting the user.
374 # edit without prompting the user.
375 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
375 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
376 # Use better variable names here. Because this is a wrapper we cannot
376 # Use better variable names here. Because this is a wrapper we cannot
377 # change the variable names in the function declaration.
377 # change the variable names in the function declaration.
378 fcdest, fcother, fcancestor = fcd, fco, fca
378 fcdest, fcother, fcancestor = fcd, fco, fca
379 if not lfutil.isstandin(orig):
379 if not lfutil.isstandin(orig):
380 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
380 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
381 else:
381 else:
382 if not fcother.cmp(fcdest): # files identical?
382 if not fcother.cmp(fcdest): # files identical?
383 return None
383 return None
384
384
385 # backwards, use working dir parent as ancestor
385 # backwards, use working dir parent as ancestor
386 if fcancestor == fcother:
386 if fcancestor == fcother:
387 fcancestor = fcdest.parents()[0]
387 fcancestor = fcdest.parents()[0]
388
388
389 if orig != fcother.path():
389 if orig != fcother.path():
390 repo.ui.status(_('merging %s and %s to %s\n')
390 repo.ui.status(_('merging %s and %s to %s\n')
391 % (lfutil.splitstandin(orig),
391 % (lfutil.splitstandin(orig),
392 lfutil.splitstandin(fcother.path()),
392 lfutil.splitstandin(fcother.path()),
393 lfutil.splitstandin(fcdest.path())))
393 lfutil.splitstandin(fcdest.path())))
394 else:
394 else:
395 repo.ui.status(_('merging %s\n')
395 repo.ui.status(_('merging %s\n')
396 % lfutil.splitstandin(fcdest.path()))
396 % lfutil.splitstandin(fcdest.path()))
397
397
398 if fcancestor.path() != fcother.path() and fcother.data() == \
398 if fcancestor.path() != fcother.path() and fcother.data() == \
399 fcancestor.data():
399 fcancestor.data():
400 return 0
400 return 0
401 if fcancestor.path() != fcdest.path() and fcdest.data() == \
401 if fcancestor.path() != fcdest.path() and fcdest.data() == \
402 fcancestor.data():
402 fcancestor.data():
403 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
403 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
404 return 0
404 return 0
405
405
406 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
406 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
407 'keep (l)ocal or take (o)ther?') %
407 'keep (l)ocal or take (o)ther?') %
408 lfutil.splitstandin(orig),
408 lfutil.splitstandin(orig),
409 (_('&Local'), _('&Other')), 0) == 0:
409 (_('&Local'), _('&Other')), 0) == 0:
410 return 0
410 return 0
411 else:
411 else:
412 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
412 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
413 return 0
413 return 0
414
414
415 # Copy first changes the matchers to match standins instead of
415 # Copy first changes the matchers to match standins instead of
416 # largefiles. Then it overrides util.copyfile in that function it
416 # largefiles. Then it overrides util.copyfile in that function it
417 # checks if the destination largefile already exists. It also keeps a
417 # checks if the destination largefile already exists. It also keeps a
418 # list of copied files so that the largefiles can be copied and the
418 # list of copied files so that the largefiles can be copied and the
419 # dirstate updated.
419 # dirstate updated.
420 def overridecopy(orig, ui, repo, pats, opts, rename=False):
420 def overridecopy(orig, ui, repo, pats, opts, rename=False):
421 # doesn't remove largefile on rename
421 # doesn't remove largefile on rename
422 if len(pats) < 2:
422 if len(pats) < 2:
423 # this isn't legal, let the original function deal with it
423 # this isn't legal, let the original function deal with it
424 return orig(ui, repo, pats, opts, rename)
424 return orig(ui, repo, pats, opts, rename)
425
425
426 def makestandin(relpath):
426 def makestandin(relpath):
427 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
427 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
428 return os.path.join(repo.wjoin(lfutil.standin(path)))
428 return os.path.join(repo.wjoin(lfutil.standin(path)))
429
429
430 fullpats = scmutil.expandpats(pats)
430 fullpats = scmutil.expandpats(pats)
431 dest = fullpats[-1]
431 dest = fullpats[-1]
432
432
433 if os.path.isdir(dest):
433 if os.path.isdir(dest):
434 if not os.path.isdir(makestandin(dest)):
434 if not os.path.isdir(makestandin(dest)):
435 os.makedirs(makestandin(dest))
435 os.makedirs(makestandin(dest))
436 # This could copy both lfiles and normal files in one command,
436 # This could copy both lfiles and normal files in one command,
437 # but we don't want to do that. First replace their matcher to
437 # but we don't want to do that. First replace their matcher to
438 # only match normal files and run it, then replace it to just
438 # only match normal files and run it, then replace it to just
439 # match largefiles and run it again.
439 # match largefiles and run it again.
440 nonormalfiles = False
440 nonormalfiles = False
441 nolfiles = False
441 nolfiles = False
442 try:
442 try:
443 try:
443 try:
444 installnormalfilesmatchfn(repo[None].manifest())
444 installnormalfilesmatchfn(repo[None].manifest())
445 result = orig(ui, repo, pats, opts, rename)
445 result = orig(ui, repo, pats, opts, rename)
446 except util.Abort, e:
446 except util.Abort, e:
447 if str(e) != _('no files to copy'):
447 if str(e) != _('no files to copy'):
448 raise e
448 raise e
449 else:
449 else:
450 nonormalfiles = True
450 nonormalfiles = True
451 result = 0
451 result = 0
452 finally:
452 finally:
453 restorematchfn()
453 restorematchfn()
454
454
455 # The first rename can cause our current working directory to be removed.
455 # The first rename can cause our current working directory to be removed.
456 # In that case there is nothing left to copy/rename so just quit.
456 # In that case there is nothing left to copy/rename so just quit.
457 try:
457 try:
458 repo.getcwd()
458 repo.getcwd()
459 except OSError:
459 except OSError:
460 return result
460 return result
461
461
462 try:
462 try:
463 try:
463 try:
464 # When we call orig below it creates the standins but we don't add
464 # When we call orig below it creates the standins but we don't add
465 # them to the dir state until later so lock during that time.
465 # them to the dir state until later so lock during that time.
466 wlock = repo.wlock()
466 wlock = repo.wlock()
467
467
468 manifest = repo[None].manifest()
468 manifest = repo[None].manifest()
469 oldmatch = None # for the closure
469 oldmatch = None # for the closure
470 def overridematch(ctx, pats=[], opts={}, globbed=False,
470 def overridematch(ctx, pats=[], opts={}, globbed=False,
471 default='relpath'):
471 default='relpath'):
472 newpats = []
472 newpats = []
473 # The patterns were previously mangled to add the standin
473 # The patterns were previously mangled to add the standin
474 # directory; we need to remove that now
474 # directory; we need to remove that now
475 for pat in pats:
475 for pat in pats:
476 if match_.patkind(pat) is None and lfutil.shortname in pat:
476 if match_.patkind(pat) is None and lfutil.shortname in pat:
477 newpats.append(pat.replace(lfutil.shortname, ''))
477 newpats.append(pat.replace(lfutil.shortname, ''))
478 else:
478 else:
479 newpats.append(pat)
479 newpats.append(pat)
480 match = oldmatch(ctx, newpats, opts, globbed, default)
480 match = oldmatch(ctx, newpats, opts, globbed, default)
481 m = copy.copy(match)
481 m = copy.copy(match)
482 lfile = lambda f: lfutil.standin(f) in manifest
482 lfile = lambda f: lfutil.standin(f) in manifest
483 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
483 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
484 m._fmap = set(m._files)
484 m._fmap = set(m._files)
485 origmatchfn = m.matchfn
485 origmatchfn = m.matchfn
486 m.matchfn = lambda f: (lfutil.isstandin(f) and
486 m.matchfn = lambda f: (lfutil.isstandin(f) and
487 (f in manifest) and
487 (f in manifest) and
488 origmatchfn(lfutil.splitstandin(f)) or
488 origmatchfn(lfutil.splitstandin(f)) or
489 None)
489 None)
490 return m
490 return m
491 oldmatch = installmatchfn(overridematch)
491 oldmatch = installmatchfn(overridematch)
492 listpats = []
492 listpats = []
493 for pat in pats:
493 for pat in pats:
494 if match_.patkind(pat) is not None:
494 if match_.patkind(pat) is not None:
495 listpats.append(pat)
495 listpats.append(pat)
496 else:
496 else:
497 listpats.append(makestandin(pat))
497 listpats.append(makestandin(pat))
498
498
499 try:
499 try:
500 origcopyfile = util.copyfile
500 origcopyfile = util.copyfile
501 copiedfiles = []
501 copiedfiles = []
502 def overridecopyfile(src, dest):
502 def overridecopyfile(src, dest):
503 if (lfutil.shortname in src and
503 if (lfutil.shortname in src and
504 dest.startswith(repo.wjoin(lfutil.shortname))):
504 dest.startswith(repo.wjoin(lfutil.shortname))):
505 destlfile = dest.replace(lfutil.shortname, '')
505 destlfile = dest.replace(lfutil.shortname, '')
506 if not opts['force'] and os.path.exists(destlfile):
506 if not opts['force'] and os.path.exists(destlfile):
507 raise IOError('',
507 raise IOError('',
508 _('destination largefile already exists'))
508 _('destination largefile already exists'))
509 copiedfiles.append((src, dest))
509 copiedfiles.append((src, dest))
510 origcopyfile(src, dest)
510 origcopyfile(src, dest)
511
511
512 util.copyfile = overridecopyfile
512 util.copyfile = overridecopyfile
513 result += orig(ui, repo, listpats, opts, rename)
513 result += orig(ui, repo, listpats, opts, rename)
514 finally:
514 finally:
515 util.copyfile = origcopyfile
515 util.copyfile = origcopyfile
516
516
517 lfdirstate = lfutil.openlfdirstate(ui, repo)
517 lfdirstate = lfutil.openlfdirstate(ui, repo)
518 for (src, dest) in copiedfiles:
518 for (src, dest) in copiedfiles:
519 if (lfutil.shortname in src and
519 if (lfutil.shortname in src and
520 dest.startswith(repo.wjoin(lfutil.shortname))):
520 dest.startswith(repo.wjoin(lfutil.shortname))):
521 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
521 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
522 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
522 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
523 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
523 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
524 if not os.path.isdir(destlfiledir):
524 if not os.path.isdir(destlfiledir):
525 os.makedirs(destlfiledir)
525 os.makedirs(destlfiledir)
526 if rename:
526 if rename:
527 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
527 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
528 lfdirstate.remove(srclfile)
528 lfdirstate.remove(srclfile)
529 else:
529 else:
530 util.copyfile(repo.wjoin(srclfile),
530 util.copyfile(repo.wjoin(srclfile),
531 repo.wjoin(destlfile))
531 repo.wjoin(destlfile))
532
532
533 lfdirstate.add(destlfile)
533 lfdirstate.add(destlfile)
534 lfdirstate.write()
534 lfdirstate.write()
535 except util.Abort, e:
535 except util.Abort, e:
536 if str(e) != _('no files to copy'):
536 if str(e) != _('no files to copy'):
537 raise e
537 raise e
538 else:
538 else:
539 nolfiles = True
539 nolfiles = True
540 finally:
540 finally:
541 restorematchfn()
541 restorematchfn()
542 wlock.release()
542 wlock.release()
543
543
544 if nolfiles and nonormalfiles:
544 if nolfiles and nonormalfiles:
545 raise util.Abort(_('no files to copy'))
545 raise util.Abort(_('no files to copy'))
546
546
547 return result
547 return result
548
548
549 # When the user calls revert, we have to be careful to not revert any
549 # When the user calls revert, we have to be careful to not revert any
550 # changes to other largefiles accidentally. This means we have to keep
550 # changes to other largefiles accidentally. This means we have to keep
551 # track of the largefiles that are being reverted so we only pull down
551 # track of the largefiles that are being reverted so we only pull down
552 # the necessary largefiles.
552 # the necessary largefiles.
553 #
553 #
554 # Standins are only updated (to match the hash of largefiles) before
554 # Standins are only updated (to match the hash of largefiles) before
555 # commits. Update the standins then run the original revert, changing
555 # commits. Update the standins then run the original revert, changing
556 # the matcher to hit standins instead of largefiles. Based on the
556 # the matcher to hit standins instead of largefiles. Based on the
557 # resulting standins update the largefiles. Then return the standins
557 # resulting standins update the largefiles. Then return the standins
558 # to their proper state
558 # to their proper state
559 def overriderevert(orig, ui, repo, *pats, **opts):
559 def overriderevert(orig, ui, repo, *pats, **opts):
560 # Because we put the standins in a bad state (by updating them)
560 # Because we put the standins in a bad state (by updating them)
561 # and then return them to a correct state we need to lock to
561 # and then return them to a correct state we need to lock to
562 # prevent others from changing them in their incorrect state.
562 # prevent others from changing them in their incorrect state.
563 wlock = repo.wlock()
563 wlock = repo.wlock()
564 try:
564 try:
565 lfdirstate = lfutil.openlfdirstate(ui, repo)
565 lfdirstate = lfutil.openlfdirstate(ui, repo)
566 (modified, added, removed, missing, unknown, ignored, clean) = \
566 (modified, added, removed, missing, unknown, ignored, clean) = \
567 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
567 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
568 for lfile in modified:
568 for lfile in modified:
569 lfutil.updatestandin(repo, lfutil.standin(lfile))
569 lfutil.updatestandin(repo, lfutil.standin(lfile))
570 for lfile in missing:
570 for lfile in missing:
571 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
571 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
572 os.unlink(repo.wjoin(lfutil.standin(lfile)))
572 os.unlink(repo.wjoin(lfutil.standin(lfile)))
573
573
574 try:
574 try:
575 ctx = scmutil.revsingle(repo, opts.get('rev'))
575 ctx = scmutil.revsingle(repo, opts.get('rev'))
576 oldmatch = None # for the closure
576 oldmatch = None # for the closure
577 def overridematch(ctx, pats=[], opts={}, globbed=False,
577 def overridematch(ctx, pats=[], opts={}, globbed=False,
578 default='relpath'):
578 default='relpath'):
579 match = oldmatch(ctx, pats, opts, globbed, default)
579 match = oldmatch(ctx, pats, opts, globbed, default)
580 m = copy.copy(match)
580 m = copy.copy(match)
581 def tostandin(f):
581 def tostandin(f):
582 if lfutil.standin(f) in ctx:
582 if lfutil.standin(f) in ctx:
583 return lfutil.standin(f)
583 return lfutil.standin(f)
584 elif lfutil.standin(f) in repo[None]:
584 elif lfutil.standin(f) in repo[None]:
585 return None
585 return None
586 return f
586 return f
587 m._files = [tostandin(f) for f in m._files]
587 m._files = [tostandin(f) for f in m._files]
588 m._files = [f for f in m._files if f is not None]
588 m._files = [f for f in m._files if f is not None]
589 m._fmap = set(m._files)
589 m._fmap = set(m._files)
590 origmatchfn = m.matchfn
590 origmatchfn = m.matchfn
591 def matchfn(f):
591 def matchfn(f):
592 if lfutil.isstandin(f):
592 if lfutil.isstandin(f):
593 # We need to keep track of what largefiles are being
593 # We need to keep track of what largefiles are being
594 # matched so we know which ones to update later --
594 # matched so we know which ones to update later --
595 # otherwise we accidentally revert changes to other
595 # otherwise we accidentally revert changes to other
596 # largefiles. This is repo-specific, so duckpunch the
596 # largefiles. This is repo-specific, so duckpunch the
597 # repo object to keep the list of largefiles for us
597 # repo object to keep the list of largefiles for us
598 # later.
598 # later.
599 if origmatchfn(lfutil.splitstandin(f)) and \
599 if origmatchfn(lfutil.splitstandin(f)) and \
600 (f in repo[None] or f in ctx):
600 (f in repo[None] or f in ctx):
601 lfileslist = getattr(repo, '_lfilestoupdate', [])
601 lfileslist = getattr(repo, '_lfilestoupdate', [])
602 lfileslist.append(lfutil.splitstandin(f))
602 lfileslist.append(lfutil.splitstandin(f))
603 repo._lfilestoupdate = lfileslist
603 repo._lfilestoupdate = lfileslist
604 return True
604 return True
605 else:
605 else:
606 return False
606 return False
607 return origmatchfn(f)
607 return origmatchfn(f)
608 m.matchfn = matchfn
608 m.matchfn = matchfn
609 return m
609 return m
610 oldmatch = installmatchfn(overridematch)
610 oldmatch = installmatchfn(overridematch)
611 scmutil.match
611 scmutil.match
612 matches = overridematch(repo[None], pats, opts)
612 matches = overridematch(repo[None], pats, opts)
613 orig(ui, repo, *pats, **opts)
613 orig(ui, repo, *pats, **opts)
614 finally:
614 finally:
615 restorematchfn()
615 restorematchfn()
616 lfileslist = getattr(repo, '_lfilestoupdate', [])
616 lfileslist = getattr(repo, '_lfilestoupdate', [])
617 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
617 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
618 printmessage=False)
618 printmessage=False)
619
619
620 # empty out the largefiles list so we start fresh next time
620 # empty out the largefiles list so we start fresh next time
621 repo._lfilestoupdate = []
621 repo._lfilestoupdate = []
622 for lfile in modified:
622 for lfile in modified:
623 if lfile in lfileslist:
623 if lfile in lfileslist:
624 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
624 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
625 in repo['.']:
625 in repo['.']:
626 lfutil.writestandin(repo, lfutil.standin(lfile),
626 lfutil.writestandin(repo, lfutil.standin(lfile),
627 repo['.'][lfile].data().strip(),
627 repo['.'][lfile].data().strip(),
628 'x' in repo['.'][lfile].flags())
628 'x' in repo['.'][lfile].flags())
629 lfdirstate = lfutil.openlfdirstate(ui, repo)
629 lfdirstate = lfutil.openlfdirstate(ui, repo)
630 for lfile in added:
630 for lfile in added:
631 standin = lfutil.standin(lfile)
631 standin = lfutil.standin(lfile)
632 if standin not in ctx and (standin in matches or opts.get('all')):
632 if standin not in ctx and (standin in matches or opts.get('all')):
633 if lfile in lfdirstate:
633 if lfile in lfdirstate:
634 lfdirstate.drop(lfile)
634 lfdirstate.drop(lfile)
635 util.unlinkpath(repo.wjoin(standin))
635 util.unlinkpath(repo.wjoin(standin))
636 lfdirstate.write()
636 lfdirstate.write()
637 finally:
637 finally:
638 wlock.release()
638 wlock.release()
639
639
640 def hgupdate(orig, repo, node):
640 def hgupdate(orig, repo, node):
641 # Only call updatelfiles the standins that have changed to save time
641 # Only call updatelfiles the standins that have changed to save time
642 oldstandins = lfutil.getstandinsstate(repo)
642 oldstandins = lfutil.getstandinsstate(repo)
643 result = orig(repo, node)
643 result = orig(repo, node)
644 newstandins = lfutil.getstandinsstate(repo)
644 newstandins = lfutil.getstandinsstate(repo)
645 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
645 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
646 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, printmessage=True)
646 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, printmessage=True)
647 return result
647 return result
648
648
649 def hgclean(orig, repo, node, show_stats=True):
649 def hgclean(orig, repo, node, show_stats=True):
650 result = orig(repo, node, show_stats)
650 result = orig(repo, node, show_stats)
651 lfcommands.updatelfiles(repo.ui, repo)
651 lfcommands.updatelfiles(repo.ui, repo)
652 return result
652 return result
653
653
654 def hgmerge(orig, repo, node, force=None, remind=True):
654 def hgmerge(orig, repo, node, force=None, remind=True):
655 # Mark the repo as being in the middle of a merge, so that
655 # Mark the repo as being in the middle of a merge, so that
656 # updatelfiles() will know that it needs to trust the standins in
656 # updatelfiles() will know that it needs to trust the standins in
657 # the working copy, not in the standins in the current node
657 # the working copy, not in the standins in the current node
658 repo._ismerging = True
658 repo._ismerging = True
659 try:
659 try:
660 result = orig(repo, node, force, remind)
660 result = orig(repo, node, force, remind)
661 lfcommands.updatelfiles(repo.ui, repo)
661 lfcommands.updatelfiles(repo.ui, repo)
662 finally:
662 finally:
663 repo._ismerging = False
663 repo._ismerging = False
664 return result
664 return result
665
665
666 # When we rebase a repository with remotely changed largefiles, we need to
666 # When we rebase a repository with remotely changed largefiles, we need to
667 # take some extra care so that the largefiles are correctly updated in the
667 # take some extra care so that the largefiles are correctly updated in the
668 # working copy
668 # working copy
669 def overridepull(orig, ui, repo, source=None, **opts):
669 def overridepull(orig, ui, repo, source=None, **opts):
670 revsprepull = len(repo)
670 revsprepull = len(repo)
671 if opts.get('rebase', False):
671 if opts.get('rebase', False):
672 repo._isrebasing = True
672 repo._isrebasing = True
673 try:
673 try:
674 if opts.get('update'):
674 if opts.get('update'):
675 del opts['update']
675 del opts['update']
676 ui.debug('--update and --rebase are not compatible, ignoring '
676 ui.debug('--update and --rebase are not compatible, ignoring '
677 'the update flag\n')
677 'the update flag\n')
678 del opts['rebase']
678 del opts['rebase']
679 cmdutil.bailifchanged(repo)
679 cmdutil.bailifchanged(repo)
680 origpostincoming = commands.postincoming
680 origpostincoming = commands.postincoming
681 def _dummy(*args, **kwargs):
681 def _dummy(*args, **kwargs):
682 pass
682 pass
683 commands.postincoming = _dummy
683 commands.postincoming = _dummy
684 if not source:
684 if not source:
685 source = 'default'
685 source = 'default'
686 repo.lfpullsource = source
686 repo.lfpullsource = source
687 try:
687 try:
688 result = commands.pull(ui, repo, source, **opts)
688 result = commands.pull(ui, repo, source, **opts)
689 finally:
689 finally:
690 commands.postincoming = origpostincoming
690 commands.postincoming = origpostincoming
691 revspostpull = len(repo)
691 revspostpull = len(repo)
692 if revspostpull > revsprepull:
692 if revspostpull > revsprepull:
693 result = result or rebase.rebase(ui, repo)
693 result = result or rebase.rebase(ui, repo)
694 finally:
694 finally:
695 repo._isrebasing = False
695 repo._isrebasing = False
696 else:
696 else:
697 if not source:
697 if not source:
698 source = 'default'
698 source = 'default'
699 repo.lfpullsource = source
699 repo.lfpullsource = source
700 oldheads = lfutil.getcurrentheads(repo)
700 oldheads = lfutil.getcurrentheads(repo)
701 result = orig(ui, repo, source, **opts)
701 result = orig(ui, repo, source, **opts)
702 # If we do not have the new largefiles for any new heads we pulled, we
702 # If we do not have the new largefiles for any new heads we pulled, we
703 # will run into a problem later if we try to merge or rebase with one of
703 # will run into a problem later if we try to merge or rebase with one of
704 # these heads, so cache the largefiles now directly into the system
704 # these heads, so cache the largefiles now directly into the system
705 # cache.
705 # cache.
706 ui.status(_("caching new largefiles\n"))
706 ui.status(_("caching new largefiles\n"))
707 numcached = 0
707 numcached = 0
708 heads = lfutil.getcurrentheads(repo)
708 heads = lfutil.getcurrentheads(repo)
709 newheads = set(heads).difference(set(oldheads))
709 newheads = set(heads).difference(set(oldheads))
710 for head in newheads:
710 for head in newheads:
711 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
711 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
712 numcached += len(cached)
712 numcached += len(cached)
713 ui.status(_("%d largefiles cached\n") % numcached)
713 ui.status(_("%d largefiles cached\n") % numcached)
714 if opts.get('all_largefiles'):
714 if opts.get('all_largefiles'):
715 revspostpull = len(repo)
715 revspostpull = len(repo)
716 revs = []
716 revs = []
717 for rev in xrange(revsprepull + 1, revspostpull):
717 for rev in xrange(revsprepull + 1, revspostpull):
718 revs.append(repo[rev].rev())
718 revs.append(repo[rev].rev())
719 lfcommands.downloadlfiles(ui, repo, revs)
719 lfcommands.downloadlfiles(ui, repo, revs)
720 return result
720 return result
721
721
722 def overrideclone(orig, ui, source, dest=None, **opts):
722 def overrideclone(orig, ui, source, dest=None, **opts):
723 d = dest
723 d = dest
724 if d is None:
724 if d is None:
725 d = hg.defaultdest(source)
725 d = hg.defaultdest(source)
726 if opts.get('all_largefiles') and not hg.islocal(d):
726 if opts.get('all_largefiles') and not hg.islocal(d):
727 raise util.Abort(_(
727 raise util.Abort(_(
728 '--all-largefiles is incompatible with non-local destination %s' %
728 '--all-largefiles is incompatible with non-local destination %s' %
729 d))
729 d))
730
730
731 return orig(ui, source, dest, **opts)
731 return orig(ui, source, dest, **opts)
732
732
733 def hgclone(orig, ui, opts, *args, **kwargs):
733 def hgclone(orig, ui, opts, *args, **kwargs):
734 result = orig(ui, opts, *args, **kwargs)
734 result = orig(ui, opts, *args, **kwargs)
735
735
736 if result is not None:
736 if result is not None:
737 sourcerepo, destrepo = result
737 sourcerepo, destrepo = result
738 repo = destrepo.local()
738 repo = destrepo.local()
739
739
740 # The .hglf directory must exist for the standin matcher to match
740 # The .hglf directory must exist for the standin matcher to match
741 # anything (which listlfiles uses for each rev), and .hg/largefiles is
741 # anything (which listlfiles uses for each rev), and .hg/largefiles is
742 # assumed to exist by the code that caches the downloaded file. These
742 # assumed to exist by the code that caches the downloaded file. These
743 # directories exist if clone updated to any rev. (If the repo does not
743 # directories exist if clone updated to any rev. (If the repo does not
744 # have largefiles, download never gets to the point of needing
744 # have largefiles, download never gets to the point of needing
745 # .hg/largefiles, and the standin matcher won't match anything anyway.)
745 # .hg/largefiles, and the standin matcher won't match anything anyway.)
746 if 'largefiles' in repo.requirements:
746 if 'largefiles' in repo.requirements:
747 if opts.get('noupdate'):
747 if opts.get('noupdate'):
748 util.makedirs(repo.pathto(lfutil.shortname))
748 util.makedirs(repo.pathto(lfutil.shortname))
749 util.makedirs(repo.join(lfutil.longname))
749 util.makedirs(repo.join(lfutil.longname))
750
750
751 # Caching is implicitly limited to 'rev' option, since the dest repo was
751 # Caching is implicitly limited to 'rev' option, since the dest repo was
752 # truncated at that point. The user may expect a download count with
752 # truncated at that point. The user may expect a download count with
753 # this option, so attempt whether or not this is a largefile repo.
753 # this option, so attempt whether or not this is a largefile repo.
754 if opts.get('all_largefiles'):
754 if opts.get('all_largefiles'):
755 success, missing = lfcommands.downloadlfiles(ui, repo, None)
755 success, missing = lfcommands.downloadlfiles(ui, repo, None)
756
756
757 if missing != 0:
757 if missing != 0:
758 return None
758 return None
759
759
760 return result
760 return result
761
761
762 def overriderebase(orig, ui, repo, **opts):
762 def overriderebase(orig, ui, repo, **opts):
763 repo._isrebasing = True
763 repo._isrebasing = True
764 try:
764 try:
765 return orig(ui, repo, **opts)
765 return orig(ui, repo, **opts)
766 finally:
766 finally:
767 repo._isrebasing = False
767 repo._isrebasing = False
768
768
769 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
769 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
770 prefix=None, mtime=None, subrepos=None):
770 prefix=None, mtime=None, subrepos=None):
771 # No need to lock because we are only reading history and
771 # No need to lock because we are only reading history and
772 # largefile caches, neither of which are modified.
772 # largefile caches, neither of which are modified.
773 lfcommands.cachelfiles(repo.ui, repo, node)
773 lfcommands.cachelfiles(repo.ui, repo, node)
774
774
775 if kind not in archival.archivers:
775 if kind not in archival.archivers:
776 raise util.Abort(_("unknown archive type '%s'") % kind)
776 raise util.Abort(_("unknown archive type '%s'") % kind)
777
777
778 ctx = repo[node]
778 ctx = repo[node]
779
779
780 if kind == 'files':
780 if kind == 'files':
781 if prefix:
781 if prefix:
782 raise util.Abort(
782 raise util.Abort(
783 _('cannot give prefix when archiving to files'))
783 _('cannot give prefix when archiving to files'))
784 else:
784 else:
785 prefix = archival.tidyprefix(dest, kind, prefix)
785 prefix = archival.tidyprefix(dest, kind, prefix)
786
786
787 def write(name, mode, islink, getdata):
787 def write(name, mode, islink, getdata):
788 if matchfn and not matchfn(name):
788 if matchfn and not matchfn(name):
789 return
789 return
790 data = getdata()
790 data = getdata()
791 if decode:
791 if decode:
792 data = repo.wwritedata(name, data)
792 data = repo.wwritedata(name, data)
793 archiver.addfile(prefix + name, mode, islink, data)
793 archiver.addfile(prefix + name, mode, islink, data)
794
794
795 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
795 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
796
796
797 if repo.ui.configbool("ui", "archivemeta", True):
797 if repo.ui.configbool("ui", "archivemeta", True):
798 def metadata():
798 def metadata():
799 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
799 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
800 hex(repo.changelog.node(0)), hex(node), ctx.branch())
800 hex(repo.changelog.node(0)), hex(node), ctx.branch())
801
801
802 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
802 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
803 if repo.tagtype(t) == 'global')
803 if repo.tagtype(t) == 'global')
804 if not tags:
804 if not tags:
805 repo.ui.pushbuffer()
805 repo.ui.pushbuffer()
806 opts = {'template': '{latesttag}\n{latesttagdistance}',
806 opts = {'template': '{latesttag}\n{latesttagdistance}',
807 'style': '', 'patch': None, 'git': None}
807 'style': '', 'patch': None, 'git': None}
808 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
808 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
809 ltags, dist = repo.ui.popbuffer().split('\n')
809 ltags, dist = repo.ui.popbuffer().split('\n')
810 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
810 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
811 tags += 'latesttagdistance: %s\n' % dist
811 tags += 'latesttagdistance: %s\n' % dist
812
812
813 return base + tags
813 return base + tags
814
814
815 write('.hg_archival.txt', 0644, False, metadata)
815 write('.hg_archival.txt', 0644, False, metadata)
816
816
817 for f in ctx:
817 for f in ctx:
818 ff = ctx.flags(f)
818 ff = ctx.flags(f)
819 getdata = ctx[f].data
819 getdata = ctx[f].data
820 if lfutil.isstandin(f):
820 if lfutil.isstandin(f):
821 path = lfutil.findfile(repo, getdata().strip())
821 path = lfutil.findfile(repo, getdata().strip())
822 if path is None:
822 if path is None:
823 raise util.Abort(
823 raise util.Abort(
824 _('largefile %s not found in repo store or system cache')
824 _('largefile %s not found in repo store or system cache')
825 % lfutil.splitstandin(f))
825 % lfutil.splitstandin(f))
826 f = lfutil.splitstandin(f)
826 f = lfutil.splitstandin(f)
827
827
828 def getdatafn():
828 def getdatafn():
829 fd = None
829 fd = None
830 try:
830 try:
831 fd = open(path, 'rb')
831 fd = open(path, 'rb')
832 return fd.read()
832 return fd.read()
833 finally:
833 finally:
834 if fd:
834 if fd:
835 fd.close()
835 fd.close()
836
836
837 getdata = getdatafn
837 getdata = getdatafn
838 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
838 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
839
839
840 if subrepos:
840 if subrepos:
841 for subpath in ctx.substate:
841 for subpath in ctx.substate:
842 sub = ctx.sub(subpath)
842 sub = ctx.sub(subpath)
843 submatch = match_.narrowmatcher(subpath, matchfn)
843 submatch = match_.narrowmatcher(subpath, matchfn)
844 sub.archive(repo.ui, archiver, prefix, submatch)
844 sub.archive(repo.ui, archiver, prefix, submatch)
845
845
846 archiver.done()
846 archiver.done()
847
847
848 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
848 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
849 repo._get(repo._state + ('hg',))
849 repo._get(repo._state + ('hg',))
850 rev = repo._state[1]
850 rev = repo._state[1]
851 ctx = repo._repo[rev]
851 ctx = repo._repo[rev]
852
852
853 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
853 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
854
854
855 def write(name, mode, islink, getdata):
855 def write(name, mode, islink, getdata):
856 # At this point, the standin has been replaced with the largefile name,
856 # At this point, the standin has been replaced with the largefile name,
857 # so the normal matcher works here without the lfutil variants.
857 # so the normal matcher works here without the lfutil variants.
858 if match and not match(f):
858 if match and not match(f):
859 return
859 return
860 data = getdata()
860 data = getdata()
861
861
862 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
862 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
863
863
864 for f in ctx:
864 for f in ctx:
865 ff = ctx.flags(f)
865 ff = ctx.flags(f)
866 getdata = ctx[f].data
866 getdata = ctx[f].data
867 if lfutil.isstandin(f):
867 if lfutil.isstandin(f):
868 path = lfutil.findfile(repo._repo, getdata().strip())
868 path = lfutil.findfile(repo._repo, getdata().strip())
869 if path is None:
869 if path is None:
870 raise util.Abort(
870 raise util.Abort(
871 _('largefile %s not found in repo store or system cache')
871 _('largefile %s not found in repo store or system cache')
872 % lfutil.splitstandin(f))
872 % lfutil.splitstandin(f))
873 f = lfutil.splitstandin(f)
873 f = lfutil.splitstandin(f)
874
874
875 def getdatafn():
875 def getdatafn():
876 fd = None
876 fd = None
877 try:
877 try:
878 fd = open(os.path.join(prefix, path), 'rb')
878 fd = open(os.path.join(prefix, path), 'rb')
879 return fd.read()
879 return fd.read()
880 finally:
880 finally:
881 if fd:
881 if fd:
882 fd.close()
882 fd.close()
883
883
884 getdata = getdatafn
884 getdata = getdatafn
885
885
886 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
886 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
887
887
888 for subpath in ctx.substate:
888 for subpath in ctx.substate:
889 sub = ctx.sub(subpath)
889 sub = ctx.sub(subpath)
890 submatch = match_.narrowmatcher(subpath, match)
890 submatch = match_.narrowmatcher(subpath, match)
891 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
891 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
892 submatch)
892 submatch)
893
893
894 # If a largefile is modified, the change is not reflected in its
894 # If a largefile is modified, the change is not reflected in its
895 # standin until a commit. cmdutil.bailifchanged() raises an exception
895 # standin until a commit. cmdutil.bailifchanged() raises an exception
896 # if the repo has uncommitted changes. Wrap it to also check if
896 # if the repo has uncommitted changes. Wrap it to also check if
897 # largefiles were changed. This is used by bisect and backout.
897 # largefiles were changed. This is used by bisect and backout.
898 def overridebailifchanged(orig, repo):
898 def overridebailifchanged(orig, repo):
899 orig(repo)
899 orig(repo)
900 repo.lfstatus = True
900 repo.lfstatus = True
901 modified, added, removed, deleted = repo.status()[:4]
901 modified, added, removed, deleted = repo.status()[:4]
902 repo.lfstatus = False
902 repo.lfstatus = False
903 if modified or added or removed or deleted:
903 if modified or added or removed or deleted:
904 raise util.Abort(_('outstanding uncommitted changes'))
904 raise util.Abort(_('outstanding uncommitted changes'))
905
905
906 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
906 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
907 def overridefetch(orig, ui, repo, *pats, **opts):
907 def overridefetch(orig, ui, repo, *pats, **opts):
908 repo.lfstatus = True
908 repo.lfstatus = True
909 modified, added, removed, deleted = repo.status()[:4]
909 modified, added, removed, deleted = repo.status()[:4]
910 repo.lfstatus = False
910 repo.lfstatus = False
911 if modified or added or removed or deleted:
911 if modified or added or removed or deleted:
912 raise util.Abort(_('outstanding uncommitted changes'))
912 raise util.Abort(_('outstanding uncommitted changes'))
913 return orig(ui, repo, *pats, **opts)
913 return orig(ui, repo, *pats, **opts)
914
914
915 def overrideforget(orig, ui, repo, *pats, **opts):
915 def overrideforget(orig, ui, repo, *pats, **opts):
916 installnormalfilesmatchfn(repo[None].manifest())
916 installnormalfilesmatchfn(repo[None].manifest())
917 result = orig(ui, repo, *pats, **opts)
917 result = orig(ui, repo, *pats, **opts)
918 restorematchfn()
918 restorematchfn()
919 m = scmutil.match(repo[None], pats, opts)
919 m = scmutil.match(repo[None], pats, opts)
920
920
921 try:
921 try:
922 repo.lfstatus = True
922 repo.lfstatus = True
923 s = repo.status(match=m, clean=True)
923 s = repo.status(match=m, clean=True)
924 finally:
924 finally:
925 repo.lfstatus = False
925 repo.lfstatus = False
926 forget = sorted(s[0] + s[1] + s[3] + s[6])
926 forget = sorted(s[0] + s[1] + s[3] + s[6])
927 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
927 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
928
928
929 for f in forget:
929 for f in forget:
930 if lfutil.standin(f) not in repo.dirstate and not \
930 if lfutil.standin(f) not in repo.dirstate and not \
931 os.path.isdir(m.rel(lfutil.standin(f))):
931 os.path.isdir(m.rel(lfutil.standin(f))):
932 ui.warn(_('not removing %s: file is already untracked\n')
932 ui.warn(_('not removing %s: file is already untracked\n')
933 % m.rel(f))
933 % m.rel(f))
934 result = 1
934 result = 1
935
935
936 for f in forget:
936 for f in forget:
937 if ui.verbose or not m.exact(f):
937 if ui.verbose or not m.exact(f):
938 ui.status(_('removing %s\n') % m.rel(f))
938 ui.status(_('removing %s\n') % m.rel(f))
939
939
940 # Need to lock because standin files are deleted then removed from the
940 # Need to lock because standin files are deleted then removed from the
941 # repository and we could race in-between.
941 # repository and we could race in-between.
942 wlock = repo.wlock()
942 wlock = repo.wlock()
943 try:
943 try:
944 lfdirstate = lfutil.openlfdirstate(ui, repo)
944 lfdirstate = lfutil.openlfdirstate(ui, repo)
945 for f in forget:
945 for f in forget:
946 if lfdirstate[f] == 'a':
946 if lfdirstate[f] == 'a':
947 lfdirstate.drop(f)
947 lfdirstate.drop(f)
948 else:
948 else:
949 lfdirstate.remove(f)
949 lfdirstate.remove(f)
950 lfdirstate.write()
950 lfdirstate.write()
951 lfutil.reporemove(repo, [lfutil.standin(f) for f in forget],
951 lfutil.reporemove(repo, [lfutil.standin(f) for f in forget],
952 unlink=True)
952 unlink=True)
953 finally:
953 finally:
954 wlock.release()
954 wlock.release()
955
955
956 return result
956 return result
957
957
958 def getoutgoinglfiles(ui, repo, dest=None, **opts):
958 def getoutgoinglfiles(ui, repo, dest=None, **opts):
959 dest = ui.expandpath(dest or 'default-push', dest or 'default')
959 dest = ui.expandpath(dest or 'default-push', dest or 'default')
960 dest, branches = hg.parseurl(dest, opts.get('branch'))
960 dest, branches = hg.parseurl(dest, opts.get('branch'))
961 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
961 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
962 if revs:
962 if revs:
963 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
963 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
964
964
965 try:
965 try:
966 remote = hg.peer(repo, opts, dest)
966 remote = hg.peer(repo, opts, dest)
967 except error.RepoError:
967 except error.RepoError:
968 return None
968 return None
969 o = lfutil.findoutgoing(repo, remote, False)
969 o = lfutil.findoutgoing(repo, remote, False)
970 if not o:
970 if not o:
971 return o
971 return o
972 o = repo.changelog.nodesbetween(o, revs)[0]
972 o = repo.changelog.nodesbetween(o, revs)[0]
973 if opts.get('newest_first'):
973 if opts.get('newest_first'):
974 o.reverse()
974 o.reverse()
975
975
976 toupload = set()
976 toupload = set()
977 for n in o:
977 for n in o:
978 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
978 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
979 ctx = repo[n]
979 ctx = repo[n]
980 files = set(ctx.files())
980 files = set(ctx.files())
981 if len(parents) == 2:
981 if len(parents) == 2:
982 mc = ctx.manifest()
982 mc = ctx.manifest()
983 mp1 = ctx.parents()[0].manifest()
983 mp1 = ctx.parents()[0].manifest()
984 mp2 = ctx.parents()[1].manifest()
984 mp2 = ctx.parents()[1].manifest()
985 for f in mp1:
985 for f in mp1:
986 if f not in mc:
986 if f not in mc:
987 files.add(f)
987 files.add(f)
988 for f in mp2:
988 for f in mp2:
989 if f not in mc:
989 if f not in mc:
990 files.add(f)
990 files.add(f)
991 for f in mc:
991 for f in mc:
992 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
992 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
993 files.add(f)
993 files.add(f)
994 toupload = toupload.union(
994 toupload = toupload.union(
995 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
995 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
996 return toupload
996 return toupload
997
997
998 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
998 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
999 result = orig(ui, repo, dest, **opts)
999 result = orig(ui, repo, dest, **opts)
1000
1000
1001 if opts.pop('large', None):
1001 if opts.pop('large', None):
1002 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
1002 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
1003 if toupload is None:
1003 if toupload is None:
1004 ui.status(_('largefiles: No remote repo\n'))
1004 ui.status(_('largefiles: No remote repo\n'))
1005 elif not toupload:
1005 elif not toupload:
1006 ui.status(_('largefiles: no files to upload\n'))
1006 ui.status(_('largefiles: no files to upload\n'))
1007 else:
1007 else:
1008 ui.status(_('largefiles to upload:\n'))
1008 ui.status(_('largefiles to upload:\n'))
1009 for file in toupload:
1009 for file in toupload:
1010 ui.status(lfutil.splitstandin(file) + '\n')
1010 ui.status(lfutil.splitstandin(file) + '\n')
1011 ui.status('\n')
1011 ui.status('\n')
1012
1012
1013 return result
1013 return result
1014
1014
1015 def overridesummary(orig, ui, repo, *pats, **opts):
1015 def overridesummary(orig, ui, repo, *pats, **opts):
1016 try:
1016 try:
1017 repo.lfstatus = True
1017 repo.lfstatus = True
1018 orig(ui, repo, *pats, **opts)
1018 orig(ui, repo, *pats, **opts)
1019 finally:
1019 finally:
1020 repo.lfstatus = False
1020 repo.lfstatus = False
1021
1021
1022 if opts.pop('large', None):
1022 if opts.pop('large', None):
1023 toupload = getoutgoinglfiles(ui, repo, None, **opts)
1023 toupload = getoutgoinglfiles(ui, repo, None, **opts)
1024 if toupload is None:
1024 if toupload is None:
1025 ui.status(_('largefiles: No remote repo\n'))
1025 ui.status(_('largefiles: No remote repo\n'))
1026 elif not toupload:
1026 elif not toupload:
1027 ui.status(_('largefiles: (no files to upload)\n'))
1027 ui.status(_('largefiles: (no files to upload)\n'))
1028 else:
1028 else:
1029 ui.status(_('largefiles: %d to upload\n') % len(toupload))
1029 ui.status(_('largefiles: %d to upload\n') % len(toupload))
1030
1030
1031 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1031 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1032 similarity=None):
1032 similarity=None):
1033 if not lfutil.islfilesrepo(repo):
1033 if not lfutil.islfilesrepo(repo):
1034 return orig(repo, pats, opts, dry_run, similarity)
1034 return orig(repo, pats, opts, dry_run, similarity)
1035 # Get the list of missing largefiles so we can remove them
1035 # Get the list of missing largefiles so we can remove them
1036 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1036 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1037 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
1037 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
1038 False, False)
1038 False, False)
1039 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
1039 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
1040
1040
1041 # Call into the normal remove code, but the removing of the standin, we want
1041 # Call into the normal remove code, but the removing of the standin, we want
1042 # to have handled by original addremove. Monkey patching here makes sure
1042 # to have handled by original addremove. Monkey patching here makes sure
1043 # we don't remove the standin in the largefiles code, preventing a very
1043 # we don't remove the standin in the largefiles code, preventing a very
1044 # confused state later.
1044 # confused state later.
1045 if missing:
1045 if missing:
1046 m = [repo.wjoin(f) for f in missing]
1046 m = [repo.wjoin(f) for f in missing]
1047 repo._isaddremove = True
1047 repo._isaddremove = True
1048 removelargefiles(repo.ui, repo, *m, **opts)
1048 removelargefiles(repo.ui, repo, *m, **opts)
1049 repo._isaddremove = False
1049 repo._isaddremove = False
1050 # Call into the normal add code, and any files that *should* be added as
1050 # Call into the normal add code, and any files that *should* be added as
1051 # largefiles will be
1051 # largefiles will be
1052 addlargefiles(repo.ui, repo, *pats, **opts)
1052 addlargefiles(repo.ui, repo, *pats, **opts)
1053 # Now that we've handled largefiles, hand off to the original addremove
1053 # Now that we've handled largefiles, hand off to the original addremove
1054 # function to take care of the rest. Make sure it doesn't do anything with
1054 # function to take care of the rest. Make sure it doesn't do anything with
1055 # largefiles by installing a matcher that will ignore them.
1055 # largefiles by installing a matcher that will ignore them.
1056 installnormalfilesmatchfn(repo[None].manifest())
1056 installnormalfilesmatchfn(repo[None].manifest())
1057 result = orig(repo, pats, opts, dry_run, similarity)
1057 result = orig(repo, pats, opts, dry_run, similarity)
1058 restorematchfn()
1058 restorematchfn()
1059 return result
1059 return result
1060
1060
1061 # Calling purge with --all will cause the largefiles to be deleted.
1061 # Calling purge with --all will cause the largefiles to be deleted.
1062 # Override repo.status to prevent this from happening.
1062 # Override repo.status to prevent this from happening.
1063 def overridepurge(orig, ui, repo, *dirs, **opts):
1063 def overridepurge(orig, ui, repo, *dirs, **opts):
1064 oldstatus = repo.status
1064 oldstatus = repo.status
1065 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1065 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1066 clean=False, unknown=False, listsubrepos=False):
1066 clean=False, unknown=False, listsubrepos=False):
1067 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1067 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1068 listsubrepos)
1068 listsubrepos)
1069 lfdirstate = lfutil.openlfdirstate(ui, repo)
1069 lfdirstate = lfutil.openlfdirstate(ui, repo)
1070 modified, added, removed, deleted, unknown, ignored, clean = r
1070 modified, added, removed, deleted, unknown, ignored, clean = r
1071 unknown = [f for f in unknown if lfdirstate[f] == '?']
1071 unknown = [f for f in unknown if lfdirstate[f] == '?']
1072 ignored = [f for f in ignored if lfdirstate[f] == '?']
1072 ignored = [f for f in ignored if lfdirstate[f] == '?']
1073 return modified, added, removed, deleted, unknown, ignored, clean
1073 return modified, added, removed, deleted, unknown, ignored, clean
1074 repo.status = overridestatus
1074 repo.status = overridestatus
1075 orig(ui, repo, *dirs, **opts)
1075 orig(ui, repo, *dirs, **opts)
1076 repo.status = oldstatus
1076 repo.status = oldstatus
1077
1077
1078 def overriderollback(orig, ui, repo, **opts):
1078 def overriderollback(orig, ui, repo, **opts):
1079 result = orig(ui, repo, **opts)
1079 result = orig(ui, repo, **opts)
1080 merge.update(repo, node=None, branchmerge=False, force=True,
1080 merge.update(repo, node=None, branchmerge=False, force=True,
1081 partial=lfutil.isstandin)
1081 partial=lfutil.isstandin)
1082 wlock = repo.wlock()
1082 wlock = repo.wlock()
1083 try:
1083 try:
1084 lfdirstate = lfutil.openlfdirstate(ui, repo)
1084 lfdirstate = lfutil.openlfdirstate(ui, repo)
1085 lfiles = lfutil.listlfiles(repo)
1085 lfiles = lfutil.listlfiles(repo)
1086 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1086 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1087 for file in lfiles:
1087 for file in lfiles:
1088 if file in oldlfiles:
1088 if file in oldlfiles:
1089 lfdirstate.normallookup(file)
1089 lfdirstate.normallookup(file)
1090 else:
1090 else:
1091 lfdirstate.add(file)
1091 lfdirstate.add(file)
1092 lfdirstate.write()
1092 lfdirstate.write()
1093 finally:
1093 finally:
1094 wlock.release()
1094 wlock.release()
1095 return result
1095 return result
1096
1096
1097 def overridetransplant(orig, ui, repo, *revs, **opts):
1097 def overridetransplant(orig, ui, repo, *revs, **opts):
1098 try:
1098 try:
1099 oldstandins = lfutil.getstandinsstate(repo)
1099 oldstandins = lfutil.getstandinsstate(repo)
1100 repo._istransplanting = True
1100 repo._istransplanting = True
1101 result = orig(ui, repo, *revs, **opts)
1101 result = orig(ui, repo, *revs, **opts)
1102 newstandins = lfutil.getstandinsstate(repo)
1102 newstandins = lfutil.getstandinsstate(repo)
1103 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1103 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1104 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1104 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1105 printmessage=True)
1105 printmessage=True)
1106 finally:
1106 finally:
1107 repo._istransplanting = False
1107 repo._istransplanting = False
1108 return result
1108 return result
1109
1109
1110 def overridecat(orig, ui, repo, file1, *pats, **opts):
1110 def overridecat(orig, ui, repo, file1, *pats, **opts):
1111 ctx = scmutil.revsingle(repo, opts.get('rev'))
1111 ctx = scmutil.revsingle(repo, opts.get('rev'))
1112 if not lfutil.standin(file1) in ctx:
1112 if not lfutil.standin(file1) in ctx:
1113 result = orig(ui, repo, file1, *pats, **opts)
1113 result = orig(ui, repo, file1, *pats, **opts)
1114 return result
1114 return result
1115 return lfcommands.catlfile(repo, file1, ctx.rev(), opts.get('output'))
1115 return lfcommands.catlfile(repo, file1, ctx.rev(), opts.get('output'))
1116
1117 def mercurialsinkbefore(orig, sink):
1118 sink.repo._isconverting = True
1119 orig(sink)
1120
1121 def mercurialsinkafter(orig, sink):
1122 sink.repo._isconverting = False
1123 orig(sink)
@@ -1,170 +1,177
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 extension: uisetup'''
9 '''setup for largefiles extension: uisetup'''
10
10
11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
12 httppeer, localrepo, merge, scmutil, sshpeer, sshserver, wireproto
12 httppeer, localrepo, merge, scmutil, sshpeer, sshserver, wireproto
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial.hgweb import hgweb_mod, protocol, webcommands
14 from mercurial.hgweb import hgweb_mod, protocol, webcommands
15 from mercurial.subrepo import hgsubrepo
15 from mercurial.subrepo import hgsubrepo
16
16
17 import overrides
17 import overrides
18 import proto
18 import proto
19
19
20 def uisetup(ui):
20 def uisetup(ui):
21 # Disable auto-status for some commands which assume that all
21 # Disable auto-status for some commands which assume that all
22 # files in the result are under Mercurial's control
22 # files in the result are under Mercurial's control
23
23
24 entry = extensions.wrapcommand(commands.table, 'add',
24 entry = extensions.wrapcommand(commands.table, 'add',
25 overrides.overrideadd)
25 overrides.overrideadd)
26 addopt = [('', 'large', None, _('add as largefile')),
26 addopt = [('', 'large', None, _('add as largefile')),
27 ('', 'normal', None, _('add as normal file')),
27 ('', 'normal', None, _('add as normal file')),
28 ('', 'lfsize', '', _('add all files above this size '
28 ('', 'lfsize', '', _('add all files above this size '
29 '(in megabytes) as largefiles '
29 '(in megabytes) as largefiles '
30 '(default: 10)'))]
30 '(default: 10)'))]
31 entry[1].extend(addopt)
31 entry[1].extend(addopt)
32
32
33 # The scmutil function is called both by the (trivial) addremove command,
33 # The scmutil function is called both by the (trivial) addremove command,
34 # and in the process of handling commit -A (issue3542)
34 # and in the process of handling commit -A (issue3542)
35 entry = extensions.wrapfunction(scmutil, 'addremove',
35 entry = extensions.wrapfunction(scmutil, 'addremove',
36 overrides.scmutiladdremove)
36 overrides.scmutiladdremove)
37 entry = extensions.wrapcommand(commands.table, 'remove',
37 entry = extensions.wrapcommand(commands.table, 'remove',
38 overrides.overrideremove)
38 overrides.overrideremove)
39 entry = extensions.wrapcommand(commands.table, 'forget',
39 entry = extensions.wrapcommand(commands.table, 'forget',
40 overrides.overrideforget)
40 overrides.overrideforget)
41
41
42 # Subrepos call status function
42 # Subrepos call status function
43 entry = extensions.wrapcommand(commands.table, 'status',
43 entry = extensions.wrapcommand(commands.table, 'status',
44 overrides.overridestatus)
44 overrides.overridestatus)
45 entry = extensions.wrapfunction(hgsubrepo, 'status',
45 entry = extensions.wrapfunction(hgsubrepo, 'status',
46 overrides.overridestatusfn)
46 overrides.overridestatusfn)
47
47
48 entry = extensions.wrapcommand(commands.table, 'log',
48 entry = extensions.wrapcommand(commands.table, 'log',
49 overrides.overridelog)
49 overrides.overridelog)
50 entry = extensions.wrapcommand(commands.table, 'rollback',
50 entry = extensions.wrapcommand(commands.table, 'rollback',
51 overrides.overriderollback)
51 overrides.overriderollback)
52 entry = extensions.wrapcommand(commands.table, 'verify',
52 entry = extensions.wrapcommand(commands.table, 'verify',
53 overrides.overrideverify)
53 overrides.overrideverify)
54
54
55 verifyopt = [('', 'large', None, _('verify largefiles')),
55 verifyopt = [('', 'large', None, _('verify largefiles')),
56 ('', 'lfa', None,
56 ('', 'lfa', None,
57 _('verify all revisions of largefiles not just current')),
57 _('verify all revisions of largefiles not just current')),
58 ('', 'lfc', None,
58 ('', 'lfc', None,
59 _('verify largefile contents not just existence'))]
59 _('verify largefile contents not just existence'))]
60 entry[1].extend(verifyopt)
60 entry[1].extend(verifyopt)
61
61
62 entry = extensions.wrapcommand(commands.table, 'outgoing',
62 entry = extensions.wrapcommand(commands.table, 'outgoing',
63 overrides.overrideoutgoing)
63 overrides.overrideoutgoing)
64 outgoingopt = [('', 'large', None, _('display outgoing largefiles'))]
64 outgoingopt = [('', 'large', None, _('display outgoing largefiles'))]
65 entry[1].extend(outgoingopt)
65 entry[1].extend(outgoingopt)
66 entry = extensions.wrapcommand(commands.table, 'summary',
66 entry = extensions.wrapcommand(commands.table, 'summary',
67 overrides.overridesummary)
67 overrides.overridesummary)
68 summaryopt = [('', 'large', None, _('display outgoing largefiles'))]
68 summaryopt = [('', 'large', None, _('display outgoing largefiles'))]
69 entry[1].extend(summaryopt)
69 entry[1].extend(summaryopt)
70
70
71 entry = extensions.wrapcommand(commands.table, 'update',
71 entry = extensions.wrapcommand(commands.table, 'update',
72 overrides.overrideupdate)
72 overrides.overrideupdate)
73 entry = extensions.wrapcommand(commands.table, 'pull',
73 entry = extensions.wrapcommand(commands.table, 'pull',
74 overrides.overridepull)
74 overrides.overridepull)
75 pullopt = [('', 'all-largefiles', None,
75 pullopt = [('', 'all-largefiles', None,
76 _('download all pulled versions of largefiles'))]
76 _('download all pulled versions of largefiles'))]
77 entry[1].extend(pullopt)
77 entry[1].extend(pullopt)
78 entry = extensions.wrapcommand(commands.table, 'clone',
78 entry = extensions.wrapcommand(commands.table, 'clone',
79 overrides.overrideclone)
79 overrides.overrideclone)
80 cloneopt = [('', 'all-largefiles', None,
80 cloneopt = [('', 'all-largefiles', None,
81 _('download all versions of all largefiles'))]
81 _('download all versions of all largefiles'))]
82 entry[1].extend(cloneopt)
82 entry[1].extend(cloneopt)
83 entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
83 entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
84
84
85 entry = extensions.wrapcommand(commands.table, 'cat',
85 entry = extensions.wrapcommand(commands.table, 'cat',
86 overrides.overridecat)
86 overrides.overridecat)
87 entry = extensions.wrapfunction(merge, '_checkunknownfile',
87 entry = extensions.wrapfunction(merge, '_checkunknownfile',
88 overrides.overridecheckunknownfile)
88 overrides.overridecheckunknownfile)
89 entry = extensions.wrapfunction(merge, 'manifestmerge',
89 entry = extensions.wrapfunction(merge, 'manifestmerge',
90 overrides.overridemanifestmerge)
90 overrides.overridemanifestmerge)
91 entry = extensions.wrapfunction(filemerge, 'filemerge',
91 entry = extensions.wrapfunction(filemerge, 'filemerge',
92 overrides.overridefilemerge)
92 overrides.overridefilemerge)
93 entry = extensions.wrapfunction(cmdutil, 'copy',
93 entry = extensions.wrapfunction(cmdutil, 'copy',
94 overrides.overridecopy)
94 overrides.overridecopy)
95
95
96 # Summary calls dirty on the subrepos
96 # Summary calls dirty on the subrepos
97 entry = extensions.wrapfunction(hgsubrepo, 'dirty',
97 entry = extensions.wrapfunction(hgsubrepo, 'dirty',
98 overrides.overridedirty)
98 overrides.overridedirty)
99
99
100 # Backout calls revert so we need to override both the command and the
100 # Backout calls revert so we need to override both the command and the
101 # function
101 # function
102 entry = extensions.wrapcommand(commands.table, 'revert',
102 entry = extensions.wrapcommand(commands.table, 'revert',
103 overrides.overriderevert)
103 overrides.overriderevert)
104 entry = extensions.wrapfunction(commands, 'revert',
104 entry = extensions.wrapfunction(commands, 'revert',
105 overrides.overriderevert)
105 overrides.overriderevert)
106
106
107 # clone uses hg._update instead of hg.update even though they are the
107 # clone uses hg._update instead of hg.update even though they are the
108 # same function... so wrap both of them)
108 # same function... so wrap both of them)
109 extensions.wrapfunction(hg, 'update', overrides.hgupdate)
109 extensions.wrapfunction(hg, 'update', overrides.hgupdate)
110 extensions.wrapfunction(hg, '_update', overrides.hgupdate)
110 extensions.wrapfunction(hg, '_update', overrides.hgupdate)
111 extensions.wrapfunction(hg, 'clean', overrides.hgclean)
111 extensions.wrapfunction(hg, 'clean', overrides.hgclean)
112 extensions.wrapfunction(hg, 'merge', overrides.hgmerge)
112 extensions.wrapfunction(hg, 'merge', overrides.hgmerge)
113
113
114 extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
114 extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
115 extensions.wrapfunction(hgsubrepo, 'archive', overrides.hgsubrepoarchive)
115 extensions.wrapfunction(hgsubrepo, 'archive', overrides.hgsubrepoarchive)
116 extensions.wrapfunction(cmdutil, 'bailifchanged',
116 extensions.wrapfunction(cmdutil, 'bailifchanged',
117 overrides.overridebailifchanged)
117 overrides.overridebailifchanged)
118
118
119 # create the new wireproto commands ...
119 # create the new wireproto commands ...
120 wireproto.commands['putlfile'] = (proto.putlfile, 'sha')
120 wireproto.commands['putlfile'] = (proto.putlfile, 'sha')
121 wireproto.commands['getlfile'] = (proto.getlfile, 'sha')
121 wireproto.commands['getlfile'] = (proto.getlfile, 'sha')
122 wireproto.commands['statlfile'] = (proto.statlfile, 'sha')
122 wireproto.commands['statlfile'] = (proto.statlfile, 'sha')
123
123
124 # ... and wrap some existing ones
124 # ... and wrap some existing ones
125 wireproto.commands['capabilities'] = (proto.capabilities, '')
125 wireproto.commands['capabilities'] = (proto.capabilities, '')
126 wireproto.commands['heads'] = (proto.heads, '')
126 wireproto.commands['heads'] = (proto.heads, '')
127 wireproto.commands['lheads'] = (wireproto.heads, '')
127 wireproto.commands['lheads'] = (wireproto.heads, '')
128
128
129 # make putlfile behave the same as push and {get,stat}lfile behave
129 # make putlfile behave the same as push and {get,stat}lfile behave
130 # the same as pull w.r.t. permissions checks
130 # the same as pull w.r.t. permissions checks
131 hgweb_mod.perms['putlfile'] = 'push'
131 hgweb_mod.perms['putlfile'] = 'push'
132 hgweb_mod.perms['getlfile'] = 'pull'
132 hgweb_mod.perms['getlfile'] = 'pull'
133 hgweb_mod.perms['statlfile'] = 'pull'
133 hgweb_mod.perms['statlfile'] = 'pull'
134
134
135 extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath)
135 extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath)
136
136
137 # the hello wireproto command uses wireproto.capabilities, so it won't see
137 # the hello wireproto command uses wireproto.capabilities, so it won't see
138 # our largefiles capability unless we replace the actual function as well.
138 # our largefiles capability unless we replace the actual function as well.
139 proto.capabilitiesorig = wireproto.capabilities
139 proto.capabilitiesorig = wireproto.capabilities
140 wireproto.capabilities = proto.capabilities
140 wireproto.capabilities = proto.capabilities
141
141
142 # these let us reject non-largefiles clients and make them display
142 # these let us reject non-largefiles clients and make them display
143 # our error messages
143 # our error messages
144 protocol.webproto.refuseclient = proto.webprotorefuseclient
144 protocol.webproto.refuseclient = proto.webprotorefuseclient
145 sshserver.sshserver.refuseclient = proto.sshprotorefuseclient
145 sshserver.sshserver.refuseclient = proto.sshprotorefuseclient
146
146
147 # can't do this in reposetup because it needs to have happened before
147 # can't do this in reposetup because it needs to have happened before
148 # wirerepo.__init__ is called
148 # wirerepo.__init__ is called
149 proto.ssholdcallstream = sshpeer.sshpeer._callstream
149 proto.ssholdcallstream = sshpeer.sshpeer._callstream
150 proto.httpoldcallstream = httppeer.httppeer._callstream
150 proto.httpoldcallstream = httppeer.httppeer._callstream
151 sshpeer.sshpeer._callstream = proto.sshrepocallstream
151 sshpeer.sshpeer._callstream = proto.sshrepocallstream
152 httppeer.httppeer._callstream = proto.httprepocallstream
152 httppeer.httppeer._callstream = proto.httprepocallstream
153
153
154 # don't die on seeing a repo with the largefiles requirement
154 # don't die on seeing a repo with the largefiles requirement
155 localrepo.localrepository.supported |= set(['largefiles'])
155 localrepo.localrepository.supported |= set(['largefiles'])
156
156
157 # override some extensions' stuff as well
157 # override some extensions' stuff as well
158 for name, module in extensions.extensions():
158 for name, module in extensions.extensions():
159 if name == 'fetch':
159 if name == 'fetch':
160 extensions.wrapcommand(getattr(module, 'cmdtable'), 'fetch',
160 extensions.wrapcommand(getattr(module, 'cmdtable'), 'fetch',
161 overrides.overridefetch)
161 overrides.overridefetch)
162 if name == 'purge':
162 if name == 'purge':
163 extensions.wrapcommand(getattr(module, 'cmdtable'), 'purge',
163 extensions.wrapcommand(getattr(module, 'cmdtable'), 'purge',
164 overrides.overridepurge)
164 overrides.overridepurge)
165 if name == 'rebase':
165 if name == 'rebase':
166 extensions.wrapcommand(getattr(module, 'cmdtable'), 'rebase',
166 extensions.wrapcommand(getattr(module, 'cmdtable'), 'rebase',
167 overrides.overriderebase)
167 overrides.overriderebase)
168 if name == 'transplant':
168 if name == 'transplant':
169 extensions.wrapcommand(getattr(module, 'cmdtable'), 'transplant',
169 extensions.wrapcommand(getattr(module, 'cmdtable'), 'transplant',
170 overrides.overridetransplant)
170 overrides.overridetransplant)
171 if name == 'convert':
172 convcmd = getattr(module, 'convcmd')
173 hgsink = getattr(convcmd, 'mercurial_sink')
174 extensions.wrapfunction(hgsink, 'before',
175 overrides.mercurialsinkbefore)
176 extensions.wrapfunction(hgsink, 'after',
177 overrides.mercurialsinkafter)
@@ -1,338 +1,357
1 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
1 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
2 $ mkdir "${USERCACHE}"
2 $ mkdir "${USERCACHE}"
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > largefiles =
5 > largefiles =
6 > share =
6 > share =
7 > graphlog =
7 > graphlog =
8 > mq =
8 > mq =
9 > convert =
9 > convert =
10 > [largefiles]
10 > [largefiles]
11 > minsize = 0.5
11 > minsize = 0.5
12 > patterns = **.other
12 > patterns = **.other
13 > **.dat
13 > **.dat
14 > usercache=${USERCACHE}
14 > usercache=${USERCACHE}
15 > EOF
15 > EOF
16
16
17 "lfconvert" works
17 "lfconvert" works
18 $ hg init bigfile-repo
18 $ hg init bigfile-repo
19 $ cd bigfile-repo
19 $ cd bigfile-repo
20 $ cat >> .hg/hgrc <<EOF
20 $ cat >> .hg/hgrc <<EOF
21 > [extensions]
21 > [extensions]
22 > largefiles = !
22 > largefiles = !
23 > EOF
23 > EOF
24 $ mkdir sub
24 $ mkdir sub
25 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
25 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
26 $ dd if=/dev/zero bs=1k count=256 > large2 2> /dev/null
26 $ dd if=/dev/zero bs=1k count=256 > large2 2> /dev/null
27 $ echo normal > normal1
27 $ echo normal > normal1
28 $ echo alsonormal > sub/normal2
28 $ echo alsonormal > sub/normal2
29 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
29 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
30 $ hg addremove
30 $ hg addremove
31 adding large
31 adding large
32 adding large2
32 adding large2
33 adding normal1
33 adding normal1
34 adding sub/maybelarge.dat
34 adding sub/maybelarge.dat
35 adding sub/normal2
35 adding sub/normal2
36 $ hg commit -m"add large, normal1" large normal1
36 $ hg commit -m"add large, normal1" large normal1
37 $ hg commit -m"add sub/*" sub
37 $ hg commit -m"add sub/*" sub
38
38
39 Test tag parsing
39 Test tag parsing
40 $ cat >> .hgtags <<EOF
40 $ cat >> .hgtags <<EOF
41 > IncorrectlyFormattedTag!
41 > IncorrectlyFormattedTag!
42 > invalidhash sometag
42 > invalidhash sometag
43 > 0123456789abcdef anothertag
43 > 0123456789abcdef anothertag
44 > EOF
44 > EOF
45 $ hg add .hgtags
45 $ hg add .hgtags
46 $ hg commit -m"add large2" large2 .hgtags
46 $ hg commit -m"add large2" large2 .hgtags
47
47
48 Test link+rename largefile codepath
48 Test link+rename largefile codepath
49 $ [ -d .hg/largefiles ] && echo fail || echo pass
49 $ [ -d .hg/largefiles ] && echo fail || echo pass
50 pass
50 pass
51 $ cd ..
51 $ cd ..
52 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
52 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
53 initializing destination largefiles-repo
53 initializing destination largefiles-repo
54 skipping incorrectly formatted tag IncorrectlyFormattedTag!
54 skipping incorrectly formatted tag IncorrectlyFormattedTag!
55 skipping incorrectly formatted id invalidhash
55 skipping incorrectly formatted id invalidhash
56 no mapping for id 0123456789abcdef
56 no mapping for id 0123456789abcdef
57 #if symlink
57 #if symlink
58 $ hg --cwd bigfile-repo rename large2 large3
58 $ hg --cwd bigfile-repo rename large2 large3
59 $ ln -sf large bigfile-repo/large3
59 $ ln -sf large bigfile-repo/large3
60 $ hg --cwd bigfile-repo commit -m"make large2 a symlink" large2 large3
60 $ hg --cwd bigfile-repo commit -m"make large2 a symlink" large2 large3
61 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo-symlink
61 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo-symlink
62 initializing destination largefiles-repo-symlink
62 initializing destination largefiles-repo-symlink
63 skipping incorrectly formatted tag IncorrectlyFormattedTag!
63 skipping incorrectly formatted tag IncorrectlyFormattedTag!
64 skipping incorrectly formatted id invalidhash
64 skipping incorrectly formatted id invalidhash
65 no mapping for id 0123456789abcdef
65 no mapping for id 0123456789abcdef
66 abort: renamed/copied largefile large3 becomes symlink
66 abort: renamed/copied largefile large3 becomes symlink
67 [255]
67 [255]
68 #endif
68 #endif
69 $ cd bigfile-repo
69 $ cd bigfile-repo
70 $ hg strip --no-backup 2
70 $ hg strip --no-backup 2
71 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
71 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
72 $ cd ..
72 $ cd ..
73 $ rm -rf largefiles-repo largefiles-repo-symlink
73 $ rm -rf largefiles-repo largefiles-repo-symlink
74
74
75 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
75 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
76 initializing destination largefiles-repo
76 initializing destination largefiles-repo
77
77
78 "lfconvert" converts content correctly
78 "lfconvert" converts content correctly
79 $ cd largefiles-repo
79 $ cd largefiles-repo
80 $ hg up
80 $ hg up
81 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 getting changed largefiles
82 getting changed largefiles
83 2 largefiles updated, 0 removed
83 2 largefiles updated, 0 removed
84 $ hg locate
84 $ hg locate
85 .hglf/large
85 .hglf/large
86 .hglf/sub/maybelarge.dat
86 .hglf/sub/maybelarge.dat
87 normal1
87 normal1
88 sub/normal2
88 sub/normal2
89 $ cat normal1
89 $ cat normal1
90 normal
90 normal
91 $ cat sub/normal2
91 $ cat sub/normal2
92 alsonormal
92 alsonormal
93 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
93 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
94 ec87a838931d4d5d2e94a04644788a55 large
94 ec87a838931d4d5d2e94a04644788a55 large
95 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
95 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
96
96
97 "lfconvert" adds 'largefiles' to .hg/requires.
97 "lfconvert" adds 'largefiles' to .hg/requires.
98 $ cat .hg/requires
98 $ cat .hg/requires
99 largefiles
99 largefiles
100 revlogv1
100 revlogv1
101 fncache
101 fncache
102 store
102 store
103 dotencode
103 dotencode
104
104
105 "lfconvert" includes a newline at the end of the standin files.
105 "lfconvert" includes a newline at the end of the standin files.
106 $ cat .hglf/large .hglf/sub/maybelarge.dat
106 $ cat .hglf/large .hglf/sub/maybelarge.dat
107 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
107 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
108 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
108 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
109 $ cd ..
109 $ cd ..
110
110
111 add some changesets to rename/remove/merge
111 add some changesets to rename/remove/merge
112 $ cd bigfile-repo
112 $ cd bigfile-repo
113 $ hg mv -q sub stuff
113 $ hg mv -q sub stuff
114 $ hg commit -m"rename sub/ to stuff/"
114 $ hg commit -m"rename sub/ to stuff/"
115 $ hg update -q 1
115 $ hg update -q 1
116 $ echo blah >> normal3
116 $ echo blah >> normal3
117 $ echo blah >> sub/normal2
117 $ echo blah >> sub/normal2
118 $ echo blah >> sub/maybelarge.dat
118 $ echo blah >> sub/maybelarge.dat
119 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
119 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
120 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
120 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
121 $ hg commit -A -m"add normal3, modify sub/*"
121 $ hg commit -A -m"add normal3, modify sub/*"
122 adding normal3
122 adding normal3
123 created new head
123 created new head
124 $ hg rm large normal3
124 $ hg rm large normal3
125 $ hg commit -q -m"remove large, normal3"
125 $ hg commit -q -m"remove large, normal3"
126 $ hg merge
126 $ hg merge
127 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
127 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
128 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
128 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
129 merging stuff/maybelarge.dat incomplete! (edit conflicts, then use 'hg resolve --mark')
129 merging stuff/maybelarge.dat incomplete! (edit conflicts, then use 'hg resolve --mark')
130 merging sub/normal2 and stuff/normal2 to stuff/normal2
130 merging sub/normal2 and stuff/normal2 to stuff/normal2
131 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
131 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
132 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
132 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
133 [1]
133 [1]
134 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
134 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
135 $ hg resolve -m stuff/maybelarge.dat
135 $ hg resolve -m stuff/maybelarge.dat
136 $ hg commit -m"merge"
136 $ hg commit -m"merge"
137 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
137 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
138 @ 5:4884f215abda merge
138 @ 5:4884f215abda merge
139 |\
139 |\
140 | o 4:7285f817b77e remove large, normal3
140 | o 4:7285f817b77e remove large, normal3
141 | |
141 | |
142 | o 3:67e3892e3534 add normal3, modify sub/*
142 | o 3:67e3892e3534 add normal3, modify sub/*
143 | |
143 | |
144 o | 2:c96c8beb5d56 rename sub/ to stuff/
144 o | 2:c96c8beb5d56 rename sub/ to stuff/
145 |/
145 |/
146 o 1:020c65d24e11 add sub/*
146 o 1:020c65d24e11 add sub/*
147 |
147 |
148 o 0:117b8328f97a add large, normal1
148 o 0:117b8328f97a add large, normal1
149
149
150 $ cd ..
150 $ cd ..
151
151
152 lfconvert with rename, merge, and remove
152 lfconvert with rename, merge, and remove
153 $ rm -rf largefiles-repo
153 $ rm -rf largefiles-repo
154 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
154 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
155 initializing destination largefiles-repo
155 initializing destination largefiles-repo
156 $ cd largefiles-repo
156 $ cd largefiles-repo
157 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
157 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
158 o 5:8e05f5f2b77e merge
158 o 5:8e05f5f2b77e merge
159 |\
159 |\
160 | o 4:a5a02de7a8e4 remove large, normal3
160 | o 4:a5a02de7a8e4 remove large, normal3
161 | |
161 | |
162 | o 3:55759520c76f add normal3, modify sub/*
162 | o 3:55759520c76f add normal3, modify sub/*
163 | |
163 | |
164 o | 2:261ad3f3f037 rename sub/ to stuff/
164 o | 2:261ad3f3f037 rename sub/ to stuff/
165 |/
165 |/
166 o 1:334e5237836d add sub/*
166 o 1:334e5237836d add sub/*
167 |
167 |
168 o 0:d4892ec57ce2 add large, normal1
168 o 0:d4892ec57ce2 add large, normal1
169
169
170 $ hg locate -r 2
170 $ hg locate -r 2
171 .hglf/large
171 .hglf/large
172 .hglf/stuff/maybelarge.dat
172 .hglf/stuff/maybelarge.dat
173 normal1
173 normal1
174 stuff/normal2
174 stuff/normal2
175 $ hg locate -r 3
175 $ hg locate -r 3
176 .hglf/large
176 .hglf/large
177 .hglf/sub/maybelarge.dat
177 .hglf/sub/maybelarge.dat
178 normal1
178 normal1
179 normal3
179 normal3
180 sub/normal2
180 sub/normal2
181 $ hg locate -r 4
181 $ hg locate -r 4
182 .hglf/sub/maybelarge.dat
182 .hglf/sub/maybelarge.dat
183 normal1
183 normal1
184 sub/normal2
184 sub/normal2
185 $ hg locate -r 5
185 $ hg locate -r 5
186 .hglf/stuff/maybelarge.dat
186 .hglf/stuff/maybelarge.dat
187 normal1
187 normal1
188 stuff/normal2
188 stuff/normal2
189 $ hg update
189 $ hg update
190 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 getting changed largefiles
191 getting changed largefiles
192 1 largefiles updated, 0 removed
192 1 largefiles updated, 0 removed
193 $ cat stuff/normal2
193 $ cat stuff/normal2
194 alsonormal
194 alsonormal
195 blah
195 blah
196 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
196 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
197 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
197 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
198 $ cat .hglf/stuff/maybelarge.dat
198 $ cat .hglf/stuff/maybelarge.dat
199 76236b6a2c6102826c61af4297dd738fb3b1de38
199 76236b6a2c6102826c61af4297dd738fb3b1de38
200 $ cd ..
200 $ cd ..
201
201
202 "lfconvert" error cases
202 "lfconvert" error cases
203 $ hg lfconvert http://localhost/foo foo
203 $ hg lfconvert http://localhost/foo foo
204 abort: http://localhost/foo is not a local Mercurial repo
204 abort: http://localhost/foo is not a local Mercurial repo
205 [255]
205 [255]
206 $ hg lfconvert foo ssh://localhost/foo
206 $ hg lfconvert foo ssh://localhost/foo
207 abort: ssh://localhost/foo is not a local Mercurial repo
207 abort: ssh://localhost/foo is not a local Mercurial repo
208 [255]
208 [255]
209 $ hg lfconvert nosuchrepo foo
209 $ hg lfconvert nosuchrepo foo
210 abort: repository nosuchrepo not found!
210 abort: repository nosuchrepo not found!
211 [255]
211 [255]
212 $ hg share -q -U bigfile-repo shared
212 $ hg share -q -U bigfile-repo shared
213 $ printf 'bogus' > shared/.hg/sharedpath
213 $ printf 'bogus' > shared/.hg/sharedpath
214 $ hg lfconvert shared foo
214 $ hg lfconvert shared foo
215 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus! (glob)
215 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus! (glob)
216 [255]
216 [255]
217 $ hg lfconvert bigfile-repo largefiles-repo
217 $ hg lfconvert bigfile-repo largefiles-repo
218 initializing destination largefiles-repo
218 initializing destination largefiles-repo
219 abort: repository largefiles-repo already exists!
219 abort: repository largefiles-repo already exists!
220 [255]
220 [255]
221
221
222 add another largefile to the new largefiles repo
222 add another largefile to the new largefiles repo
223 $ cd largefiles-repo
223 $ cd largefiles-repo
224 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
224 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
225 $ hg add --lfsize=1 anotherlarge
225 $ hg add --lfsize=1 anotherlarge
226 $ hg commit -m "add anotherlarge (should be a largefile)"
226 $ hg commit -m "add anotherlarge (should be a largefile)"
227 $ cat .hglf/anotherlarge
227 $ cat .hglf/anotherlarge
228 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
228 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
229 $ cd ..
229 $ cd ..
230
230
231 round-trip: converting back to a normal (non-largefiles) repo with
231 round-trip: converting back to a normal (non-largefiles) repo with
232 "lfconvert --to-normal" should give the same as ../bigfile-repo
232 "lfconvert --to-normal" should give the same as ../bigfile-repo
233 $ cd largefiles-repo
233 $ cd largefiles-repo
234 $ hg lfconvert --to-normal . ../normal-repo
234 $ hg lfconvert --to-normal . ../normal-repo
235 initializing destination ../normal-repo
235 initializing destination ../normal-repo
236 $ cd ../normal-repo
236 $ cd ../normal-repo
237 $ cat >> .hg/hgrc <<EOF
237 $ cat >> .hg/hgrc <<EOF
238 > [extensions]
238 > [extensions]
239 > largefiles = !
239 > largefiles = !
240 > EOF
240 > EOF
241
241
242 # Hmmm: the changeset ID for rev 5 is different from the original
242 # Hmmm: the changeset ID for rev 5 is different from the original
243 # normal repo (../bigfile-repo), because the changelog filelist
243 # normal repo (../bigfile-repo), because the changelog filelist
244 # differs between the two incarnations of rev 5: this repo includes
244 # differs between the two incarnations of rev 5: this repo includes
245 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
245 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
246 # removes 'large' relative to the first parent in both repos, it seems
246 # removes 'large' relative to the first parent in both repos, it seems
247 # to me that lfconvert is doing a *better* job than
247 # to me that lfconvert is doing a *better* job than
248 # "hg remove" + "hg merge" + "hg commit".
248 # "hg remove" + "hg merge" + "hg commit".
249 # $ hg -R ../bigfile-repo debugdata -c 5
249 # $ hg -R ../bigfile-repo debugdata -c 5
250 # $ hg debugdata -c 5
250 # $ hg debugdata -c 5
251 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
251 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
252 o 6:1635824e6f59 add anotherlarge (should be a largefile)
252 o 6:1635824e6f59 add anotherlarge (should be a largefile)
253 |
253 |
254 o 5:7215f8deeaaf merge
254 o 5:7215f8deeaaf merge
255 |\
255 |\
256 | o 4:7285f817b77e remove large, normal3
256 | o 4:7285f817b77e remove large, normal3
257 | |
257 | |
258 | o 3:67e3892e3534 add normal3, modify sub/*
258 | o 3:67e3892e3534 add normal3, modify sub/*
259 | |
259 | |
260 o | 2:c96c8beb5d56 rename sub/ to stuff/
260 o | 2:c96c8beb5d56 rename sub/ to stuff/
261 |/
261 |/
262 o 1:020c65d24e11 add sub/*
262 o 1:020c65d24e11 add sub/*
263 |
263 |
264 o 0:117b8328f97a add large, normal1
264 o 0:117b8328f97a add large, normal1
265
265
266 $ hg update
266 $ hg update
267 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
268 $ hg locate
268 $ hg locate
269 anotherlarge
269 anotherlarge
270 normal1
270 normal1
271 stuff/maybelarge.dat
271 stuff/maybelarge.dat
272 stuff/normal2
272 stuff/normal2
273 $ [ -d .hg/largefiles ] && echo fail || echo pass
273 $ [ -d .hg/largefiles ] && echo fail || echo pass
274 pass
274 pass
275
275
276 $ cd ..
276 $ cd ..
277
277
278 Clearing the usercache ensures that commitctx doesn't try to cache largefiles
279 from the working dir on a convert.
280 $ rm "${USERCACHE}"/*
278 $ hg convert largefiles-repo
281 $ hg convert largefiles-repo
279 assuming destination largefiles-repo-hg
282 assuming destination largefiles-repo-hg
280 initializing destination largefiles-repo-hg repository
283 initializing destination largefiles-repo-hg repository
281 scanning source...
284 scanning source...
282 sorting...
285 sorting...
283 converting...
286 converting...
284 6 add large, normal1
287 6 add large, normal1
285 5 add sub/*
288 5 add sub/*
286 4 rename sub/ to stuff/
289 4 rename sub/ to stuff/
287 3 add normal3, modify sub/*
290 3 add normal3, modify sub/*
288 2 remove large, normal3
291 2 remove large, normal3
289 1 merge
292 1 merge
290 0 add anotherlarge (should be a largefile)
293 0 add anotherlarge (should be a largefile)
291
294
292 $ hg -R largefiles-repo-hg glog --template "{rev}:{node|short} {desc|firstline}\n"
295 $ hg -R largefiles-repo-hg glog --template "{rev}:{node|short} {desc|firstline}\n"
293 o 6:17126745edfd add anotherlarge (should be a largefile)
296 o 6:17126745edfd add anotherlarge (should be a largefile)
294 |
297 |
295 o 5:9cc5aa7204f0 merge
298 o 5:9cc5aa7204f0 merge
296 |\
299 |\
297 | o 4:a5a02de7a8e4 remove large, normal3
300 | o 4:a5a02de7a8e4 remove large, normal3
298 | |
301 | |
299 | o 3:55759520c76f add normal3, modify sub/*
302 | o 3:55759520c76f add normal3, modify sub/*
300 | |
303 | |
301 o | 2:261ad3f3f037 rename sub/ to stuff/
304 o | 2:261ad3f3f037 rename sub/ to stuff/
302 |/
305 |/
303 o 1:334e5237836d add sub/*
306 o 1:334e5237836d add sub/*
304 |
307 |
305 o 0:d4892ec57ce2 add large, normal1
308 o 0:d4892ec57ce2 add large, normal1
306
309
310 Verify will fail (for now) if the usercache is purged before converting, since
311 largefiles are not cached in the converted repo's local store by the conversion
312 process.
307 $ hg -R largefiles-repo-hg verify --large --lfa
313 $ hg -R largefiles-repo-hg verify --large --lfa
308 checking changesets
314 checking changesets
309 checking manifests
315 checking manifests
310 crosschecking files in changesets and manifests
316 crosschecking files in changesets and manifests
311 checking files
317 checking files
312 8 files, 7 changesets, 12 total revisions
318 8 files, 7 changesets, 12 total revisions
313 searching 7 changesets for largefiles
319 searching 7 changesets for largefiles
320 changeset 0:d4892ec57ce2: large missing
321 (looked for hash 2e000fa7e85759c7f4c254d4d9c33ef481e459a7)
322 changeset 1:334e5237836d: sub/maybelarge.dat missing
323 (looked for hash 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c)
324 changeset 2:261ad3f3f037: stuff/maybelarge.dat missing
325 (looked for hash 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c)
326 changeset 3:55759520c76f: sub/maybelarge.dat missing
327 (looked for hash 76236b6a2c6102826c61af4297dd738fb3b1de38)
328 changeset 5:9cc5aa7204f0: stuff/maybelarge.dat missing
329 (looked for hash 76236b6a2c6102826c61af4297dd738fb3b1de38)
330 changeset 6:17126745edfd: anotherlarge missing
331 (looked for hash 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3)
314 verified existence of 6 revisions of 4 largefiles
332 verified existence of 6 revisions of 4 largefiles
333 [1]
315 $ hg -R largefiles-repo-hg showconfig paths
334 $ hg -R largefiles-repo-hg showconfig paths
316
335
317
336
318 Avoid a traceback if a largefile isn't available (issue3519)
337 Avoid a traceback if a largefile isn't available (issue3519)
319
338
320 Ensure the largefile can be cached in the source if necessary
339 Ensure the largefile can be cached in the source if necessary
321 $ hg clone -U largefiles-repo issue3519
340 $ hg clone -U largefiles-repo issue3519
322 $ rm "${USERCACHE}"/*
341 $ rm -f "${USERCACHE}"/*
323 $ hg lfconvert --to-normal issue3519 normalized3519
342 $ hg lfconvert --to-normal issue3519 normalized3519
324 initializing destination normalized3519
343 initializing destination normalized3519
325
344
326 Ensure the abort message is useful if a largefile is entirely unavailable
345 Ensure the abort message is useful if a largefile is entirely unavailable
327 $ rm -rf normalized3519
346 $ rm -rf normalized3519
328 $ rm "${USERCACHE}"/*
347 $ rm "${USERCACHE}"/*
329 $ rm issue3519/.hg/largefiles/*
348 $ rm issue3519/.hg/largefiles/*
330 $ rm largefiles-repo/.hg/largefiles/*
349 $ rm largefiles-repo/.hg/largefiles/*
331 $ hg lfconvert --to-normal issue3519 normalized3519
350 $ hg lfconvert --to-normal issue3519 normalized3519
332 initializing destination normalized3519
351 initializing destination normalized3519
333 large: can't get file locally
352 large: can't get file locally
334 (no default or default-push path set in hgrc)
353 (no default or default-push path set in hgrc)
335 abort: missing largefile 'large' from revision d4892ec57ce212905215fad1d9018f56b99202ad
354 abort: missing largefile 'large' from revision d4892ec57ce212905215fad1d9018f56b99202ad
336 [255]
355 [255]
337
356
338
357
General Comments 0
You need to be logged in to leave comments. Login now