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