##// END OF EJS Templates
largefiles: refactor downloading of all largefiles to generic function
Na'Tosha Bard -
r16691:7d6a660c default
parent child Browse files
Show More
@@ -1,517 +1,535 b''
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 '''High-level command function for lfconvert, plus the cmdtable.'''
10 10
11 11 import os
12 12 import shutil
13 13
14 from mercurial import util, match as match_, hg, node, context, error, cmdutil
14 from mercurial import util, match as match_, hg, node, context, error, \
15 cmdutil, scmutil
15 16 from mercurial.i18n import _
16 17
17 18 import lfutil
18 19 import basestore
19 20
20 21 # -- Commands ----------------------------------------------------------
21 22
22 23 def lfconvert(ui, src, dest, *pats, **opts):
23 24 '''convert a normal repository to a largefiles repository
24 25
25 26 Convert repository SOURCE to a new repository DEST, identical to
26 27 SOURCE except that certain files will be converted as largefiles:
27 28 specifically, any file that matches any PATTERN *or* whose size is
28 29 above the minimum size threshold is converted as a largefile. The
29 30 size used to determine whether or not to track a file as a
30 31 largefile is the size of the first version of the file. The
31 32 minimum size can be specified either with --size or in
32 33 configuration as ``largefiles.size``.
33 34
34 35 After running this command you will need to make sure that
35 36 largefiles is enabled anywhere you intend to push the new
36 37 repository.
37 38
38 39 Use --to-normal to convert largefiles back to normal files; after
39 40 this, the DEST repository can be used without largefiles at all.'''
40 41
41 42 if opts['to_normal']:
42 43 tolfile = False
43 44 else:
44 45 tolfile = True
45 46 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
46 47
47 48 if not hg.islocal(src):
48 49 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49 50 if not hg.islocal(dest):
50 51 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
51 52
52 53 rsrc = hg.repository(ui, src)
53 54 ui.status(_('initializing destination %s\n') % dest)
54 55 rdst = hg.repository(ui, dest, create=True)
55 56
56 57 success = False
57 58 try:
58 59 # Lock destination to prevent modification while it is converted to.
59 60 # Don't need to lock src because we are just reading from its history
60 61 # which can't change.
61 62 dstlock = rdst.lock()
62 63
63 64 # Get a list of all changesets in the source. The easy way to do this
64 65 # is to simply walk the changelog, using changelog.nodesbewteen().
65 66 # Take a look at mercurial/revlog.py:639 for more details.
66 67 # Use a generator instead of a list to decrease memory usage
67 68 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
68 69 rsrc.heads())[0])
69 70 revmap = {node.nullid: node.nullid}
70 71 if tolfile:
71 72 lfiles = set()
72 73 normalfiles = set()
73 74 if not pats:
74 75 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
75 76 if pats:
76 77 matcher = match_.match(rsrc.root, '', list(pats))
77 78 else:
78 79 matcher = None
79 80
80 81 lfiletohash = {}
81 82 for ctx in ctxs:
82 83 ui.progress(_('converting revisions'), ctx.rev(),
83 84 unit=_('revision'), total=rsrc['tip'].rev())
84 85 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
85 86 lfiles, normalfiles, matcher, size, lfiletohash)
86 87 ui.progress(_('converting revisions'), None)
87 88
88 89 if os.path.exists(rdst.wjoin(lfutil.shortname)):
89 90 shutil.rmtree(rdst.wjoin(lfutil.shortname))
90 91
91 92 for f in lfiletohash.keys():
92 93 if os.path.isfile(rdst.wjoin(f)):
93 94 os.unlink(rdst.wjoin(f))
94 95 try:
95 96 os.removedirs(os.path.dirname(rdst.wjoin(f)))
96 97 except OSError:
97 98 pass
98 99
99 100 # If there were any files converted to largefiles, add largefiles
100 101 # to the destination repository's requirements.
101 102 if lfiles:
102 103 rdst.requirements.add('largefiles')
103 104 rdst._writerequirements()
104 105 else:
105 106 for ctx in ctxs:
106 107 ui.progress(_('converting revisions'), ctx.rev(),
107 108 unit=_('revision'), total=rsrc['tip'].rev())
108 109 _addchangeset(ui, rsrc, rdst, ctx, revmap)
109 110
110 111 ui.progress(_('converting revisions'), None)
111 112 success = True
112 113 finally:
113 114 if not success:
114 115 # we failed, remove the new directory
115 116 shutil.rmtree(rdst.root)
116 117 dstlock.release()
117 118
118 119 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
119 120 # Convert src parents to dst parents
120 121 parents = _convertparents(ctx, revmap)
121 122
122 123 # Generate list of changed files
123 124 files = _getchangedfiles(ctx, parents)
124 125
125 126 def getfilectx(repo, memctx, f):
126 127 if lfutil.standin(f) in files:
127 128 # if the file isn't in the manifest then it was removed
128 129 # or renamed, raise IOError to indicate this
129 130 try:
130 131 fctx = ctx.filectx(lfutil.standin(f))
131 132 except error.LookupError:
132 133 raise IOError
133 134 renamed = fctx.renamed()
134 135 if renamed:
135 136 renamed = lfutil.splitstandin(renamed[0])
136 137
137 138 hash = fctx.data().strip()
138 139 path = lfutil.findfile(rsrc, hash)
139 140 ### TODO: What if the file is not cached?
140 141 data = ''
141 142 fd = None
142 143 try:
143 144 fd = open(path, 'rb')
144 145 data = fd.read()
145 146 finally:
146 147 if fd:
147 148 fd.close()
148 149 return context.memfilectx(f, data, 'l' in fctx.flags(),
149 150 'x' in fctx.flags(), renamed)
150 151 else:
151 152 return _getnormalcontext(repo.ui, ctx, f, revmap)
152 153
153 154 dstfiles = []
154 155 for file in files:
155 156 if lfutil.isstandin(file):
156 157 dstfiles.append(lfutil.splitstandin(file))
157 158 else:
158 159 dstfiles.append(file)
159 160 # Commit
160 161 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
161 162
162 163 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
163 164 matcher, size, lfiletohash):
164 165 # Convert src parents to dst parents
165 166 parents = _convertparents(ctx, revmap)
166 167
167 168 # Generate list of changed files
168 169 files = _getchangedfiles(ctx, parents)
169 170
170 171 dstfiles = []
171 172 for f in files:
172 173 if f not in lfiles and f not in normalfiles:
173 174 islfile = _islfile(f, ctx, matcher, size)
174 175 # If this file was renamed or copied then copy
175 176 # the lfileness of its predecessor
176 177 if f in ctx.manifest():
177 178 fctx = ctx.filectx(f)
178 179 renamed = fctx.renamed()
179 180 renamedlfile = renamed and renamed[0] in lfiles
180 181 islfile |= renamedlfile
181 182 if 'l' in fctx.flags():
182 183 if renamedlfile:
183 184 raise util.Abort(
184 185 _('renamed/copied largefile %s becomes symlink')
185 186 % f)
186 187 islfile = False
187 188 if islfile:
188 189 lfiles.add(f)
189 190 else:
190 191 normalfiles.add(f)
191 192
192 193 if f in lfiles:
193 194 dstfiles.append(lfutil.standin(f))
194 195 # largefile in manifest if it has not been removed/renamed
195 196 if f in ctx.manifest():
196 197 fctx = ctx.filectx(f)
197 198 if 'l' in fctx.flags():
198 199 renamed = fctx.renamed()
199 200 if renamed and renamed[0] in lfiles:
200 201 raise util.Abort(_('largefile %s becomes symlink') % f)
201 202
202 203 # largefile was modified, update standins
203 204 fullpath = rdst.wjoin(f)
204 205 util.makedirs(os.path.dirname(fullpath))
205 206 m = util.sha1('')
206 207 m.update(ctx[f].data())
207 208 hash = m.hexdigest()
208 209 if f not in lfiletohash or lfiletohash[f] != hash:
209 210 try:
210 211 fd = open(fullpath, 'wb')
211 212 fd.write(ctx[f].data())
212 213 finally:
213 214 if fd:
214 215 fd.close()
215 216 executable = 'x' in ctx[f].flags()
216 217 os.chmod(fullpath, lfutil.getmode(executable))
217 218 lfutil.writestandin(rdst, lfutil.standin(f), hash,
218 219 executable)
219 220 lfiletohash[f] = hash
220 221 else:
221 222 # normal file
222 223 dstfiles.append(f)
223 224
224 225 def getfilectx(repo, memctx, f):
225 226 if lfutil.isstandin(f):
226 227 # if the file isn't in the manifest then it was removed
227 228 # or renamed, raise IOError to indicate this
228 229 srcfname = lfutil.splitstandin(f)
229 230 try:
230 231 fctx = ctx.filectx(srcfname)
231 232 except error.LookupError:
232 233 raise IOError
233 234 renamed = fctx.renamed()
234 235 if renamed:
235 236 # standin is always a largefile because largefile-ness
236 237 # doesn't change after rename or copy
237 238 renamed = lfutil.standin(renamed[0])
238 239
239 240 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
240 241 fctx.flags(), 'x' in fctx.flags(), renamed)
241 242 else:
242 243 return _getnormalcontext(repo.ui, ctx, f, revmap)
243 244
244 245 # Commit
245 246 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
246 247
247 248 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
248 249 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
249 250 getfilectx, ctx.user(), ctx.date(), ctx.extra())
250 251 ret = rdst.commitctx(mctx)
251 252 rdst.setparents(ret)
252 253 revmap[ctx.node()] = rdst.changelog.tip()
253 254
254 255 # Generate list of changed files
255 256 def _getchangedfiles(ctx, parents):
256 257 files = set(ctx.files())
257 258 if node.nullid not in parents:
258 259 mc = ctx.manifest()
259 260 mp1 = ctx.parents()[0].manifest()
260 261 mp2 = ctx.parents()[1].manifest()
261 262 files |= (set(mp1) | set(mp2)) - set(mc)
262 263 for f in mc:
263 264 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
264 265 files.add(f)
265 266 return files
266 267
267 268 # Convert src parents to dst parents
268 269 def _convertparents(ctx, revmap):
269 270 parents = []
270 271 for p in ctx.parents():
271 272 parents.append(revmap[p.node()])
272 273 while len(parents) < 2:
273 274 parents.append(node.nullid)
274 275 return parents
275 276
276 277 # Get memfilectx for a normal file
277 278 def _getnormalcontext(ui, ctx, f, revmap):
278 279 try:
279 280 fctx = ctx.filectx(f)
280 281 except error.LookupError:
281 282 raise IOError
282 283 renamed = fctx.renamed()
283 284 if renamed:
284 285 renamed = renamed[0]
285 286
286 287 data = fctx.data()
287 288 if f == '.hgtags':
288 289 data = _converttags (ui, revmap, data)
289 290 return context.memfilectx(f, data, 'l' in fctx.flags(),
290 291 'x' in fctx.flags(), renamed)
291 292
292 293 # Remap tag data using a revision map
293 294 def _converttags(ui, revmap, data):
294 295 newdata = []
295 296 for line in data.splitlines():
296 297 try:
297 298 id, name = line.split(' ', 1)
298 299 except ValueError:
299 300 ui.warn(_('skipping incorrectly formatted tag %s\n'
300 301 % line))
301 302 continue
302 303 try:
303 304 newid = node.bin(id)
304 305 except TypeError:
305 306 ui.warn(_('skipping incorrectly formatted id %s\n'
306 307 % id))
307 308 continue
308 309 try:
309 310 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
310 311 name))
311 312 except KeyError:
312 313 ui.warn(_('no mapping for id %s\n') % id)
313 314 continue
314 315 return ''.join(newdata)
315 316
316 317 def _islfile(file, ctx, matcher, size):
317 318 '''Return true if file should be considered a largefile, i.e.
318 319 matcher matches it or it is larger than size.'''
319 320 # never store special .hg* files as largefiles
320 321 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
321 322 return False
322 323 if matcher and matcher(file):
323 324 return True
324 325 try:
325 326 return ctx.filectx(file).size() >= size * 1024 * 1024
326 327 except error.LookupError:
327 328 return False
328 329
329 330 def uploadlfiles(ui, rsrc, rdst, files):
330 331 '''upload largefiles to the central store'''
331 332
332 333 if not files:
333 334 return
334 335
335 336 store = basestore._openstore(rsrc, rdst, put=True)
336 337
337 338 at = 0
338 339 files = filter(lambda h: not store.exists(h), files)
339 340 for hash in files:
340 341 ui.progress(_('uploading largefiles'), at, unit='largefile',
341 342 total=len(files))
342 343 source = lfutil.findfile(rsrc, hash)
343 344 if not source:
344 345 raise util.Abort(_('largefile %s missing from store'
345 346 ' (needs to be uploaded)') % hash)
346 347 # XXX check for errors here
347 348 store.put(source, hash)
348 349 at += 1
349 350 ui.progress(_('uploading largefiles'), None)
350 351
351 352 def verifylfiles(ui, repo, all=False, contents=False):
352 353 '''Verify that every big file revision in the current changeset
353 354 exists in the central store. With --contents, also verify that
354 355 the contents of each big file revision are correct (SHA-1 hash
355 356 matches the revision ID). With --all, check every changeset in
356 357 this repository.'''
357 358 if all:
358 359 # Pass a list to the function rather than an iterator because we know a
359 360 # list will work.
360 361 revs = range(len(repo))
361 362 else:
362 363 revs = ['.']
363 364
364 365 store = basestore._openstore(repo)
365 366 return store.verify(revs, contents=contents)
366 367
367 368 def cachelfiles(ui, repo, node):
368 369 '''cachelfiles ensures that all largefiles needed by the specified revision
369 370 are present in the repository's largefile cache.
370 371
371 372 returns a tuple (cached, missing). cached is the list of files downloaded
372 373 by this operation; missing is the list of files that were needed but could
373 374 not be found.'''
374 375 lfiles = lfutil.listlfiles(repo, node)
375 376 toget = []
376 377
377 378 for lfile in lfiles:
378 379 # If we are mid-merge, then we have to trust the standin that is in the
379 380 # working copy to have the correct hashvalue. This is because the
380 381 # original hg.merge() already updated the standin as part of the normal
381 382 # merge process -- we just have to udpate the largefile to match.
382 383 if (getattr(repo, "_ismerging", False) and
383 384 os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
384 385 expectedhash = lfutil.readstandin(repo, lfile)
385 386 else:
386 387 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
387 388
388 389 # if it exists and its hash matches, it might have been locally
389 390 # modified before updating and the user chose 'local'. in this case,
390 391 # it will not be in any store, so don't look for it.
391 392 if ((not os.path.exists(repo.wjoin(lfile)) or
392 393 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
393 394 not lfutil.findfile(repo, expectedhash)):
394 395 toget.append((lfile, expectedhash))
395 396
396 397 if toget:
397 398 store = basestore._openstore(repo)
398 399 ret = store.get(toget)
399 400 return ret
400 401
401 402 return ([], [])
402 403
404 def downloadlfiles(ui, repo, rev=None):
405 matchfn = scmutil.match(repo[None],
406 [repo.wjoin(lfutil.shortname)], {})
407 def prepare(ctx, fns):
408 pass
409 totalsuccess = 0
410 totalmissing = 0
411 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
412 prepare):
413 success, missing = cachelfiles(ui, repo, ctx.node())
414 totalsuccess += len(success)
415 totalmissing += len(missing)
416 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
417 if totalmissing > 0:
418 ui.status(_("%d largefiles failed to download\n") % totalmissing)
419 return totalsuccess, totalmissing
420
403 421 def updatelfiles(ui, repo, filelist=None, printmessage=True):
404 422 wlock = repo.wlock()
405 423 try:
406 424 lfdirstate = lfutil.openlfdirstate(ui, repo)
407 425 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
408 426
409 427 if filelist is not None:
410 428 lfiles = [f for f in lfiles if f in filelist]
411 429
412 430 printed = False
413 431 if printmessage and lfiles:
414 432 ui.status(_('getting changed largefiles\n'))
415 433 printed = True
416 434 cachelfiles(ui, repo, '.')
417 435
418 436 updated, removed = 0, 0
419 437 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
420 438 # increment the appropriate counter according to _updatelfile's
421 439 # return value
422 440 updated += i > 0 and i or 0
423 441 removed -= i < 0 and i or 0
424 442 if printmessage and (removed or updated) and not printed:
425 443 ui.status(_('getting changed largefiles\n'))
426 444 printed = True
427 445
428 446 lfdirstate.write()
429 447 if printed and printmessage:
430 448 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
431 449 removed))
432 450 finally:
433 451 wlock.release()
434 452
435 453 def _updatelfile(repo, lfdirstate, lfile):
436 454 '''updates a single largefile and copies the state of its standin from
437 455 the repository's dirstate to its state in the lfdirstate.
438 456
439 457 returns 1 if the file was modified, -1 if the file was removed, 0 if the
440 458 file was unchanged, and None if the needed largefile was missing from the
441 459 cache.'''
442 460 ret = 0
443 461 abslfile = repo.wjoin(lfile)
444 462 absstandin = repo.wjoin(lfutil.standin(lfile))
445 463 if os.path.exists(absstandin):
446 464 if os.path.exists(absstandin+'.orig'):
447 465 shutil.copyfile(abslfile, abslfile+'.orig')
448 466 expecthash = lfutil.readstandin(repo, lfile)
449 467 if (expecthash != '' and
450 468 (not os.path.exists(abslfile) or
451 469 expecthash != lfutil.hashfile(abslfile))):
452 470 if not lfutil.copyfromcache(repo, expecthash, lfile):
453 471 # use normallookup() to allocate entry in largefiles dirstate,
454 472 # because lack of it misleads lfilesrepo.status() into
455 473 # recognition that such cache missing files are REMOVED.
456 474 lfdirstate.normallookup(lfile)
457 475 return None # don't try to set the mode
458 476 ret = 1
459 477 mode = os.stat(absstandin).st_mode
460 478 if mode != os.stat(abslfile).st_mode:
461 479 os.chmod(abslfile, mode)
462 480 ret = 1
463 481 else:
464 482 # Remove lfiles for which the standin is deleted, unless the
465 483 # lfile is added to the repository again. This happens when a
466 484 # largefile is converted back to a normal file: the standin
467 485 # disappears, but a new (normal) file appears as the lfile.
468 486 if os.path.exists(abslfile) and lfile not in repo[None]:
469 487 util.unlinkpath(abslfile)
470 488 ret = -1
471 489 state = repo.dirstate[lfutil.standin(lfile)]
472 490 if state == 'n':
473 491 # When rebasing, we need to synchronize the standin and the largefile,
474 492 # because otherwise the largefile will get reverted. But for commit's
475 493 # sake, we have to mark the file as unclean.
476 494 if getattr(repo, "_isrebasing", False):
477 495 lfdirstate.normallookup(lfile)
478 496 else:
479 497 lfdirstate.normal(lfile)
480 498 elif state == 'r':
481 499 lfdirstate.remove(lfile)
482 500 elif state == 'a':
483 501 lfdirstate.add(lfile)
484 502 elif state == '?':
485 503 lfdirstate.drop(lfile)
486 504 return ret
487 505
488 506 def catlfile(repo, lfile, rev, filename):
489 507 hash = lfutil.readstandin(repo, lfile, rev)
490 508 if not lfutil.inusercache(repo.ui, hash):
491 509 store = basestore._openstore(repo)
492 510 success, missing = store.get([(lfile, hash)])
493 511 if len(success) != 1:
494 512 raise util.Abort(
495 513 _('largefile %s is not in cache and could not be downloaded')
496 514 % lfile)
497 515 path = lfutil.usercachepath(repo.ui, hash)
498 516 fpout = cmdutil.makefileobj(repo, filename)
499 517 fpin = open(path, "rb")
500 518 fpout.write(fpin.read())
501 519 fpout.close()
502 520 fpin.close()
503 521 return 0
504 522
505 523 # -- hg commands declarations ------------------------------------------------
506 524
507 525 cmdtable = {
508 526 'lfconvert': (lfconvert,
509 527 [('s', 'size', '',
510 528 _('minimum size (MB) for files to be converted '
511 529 'as largefiles'),
512 530 'SIZE'),
513 531 ('', 'to-normal', False,
514 532 _('convert from a largefiles repo to a normal repo')),
515 533 ],
516 534 _('hg lfconvert SOURCE DEST [FILE ...]')),
517 535 }
@@ -1,1065 +1,1053 b''
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:
85 85 abovemin = (lfsize and
86 86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
87 87 if large or abovemin or (lfmatcher and lfmatcher(f)):
88 88 lfnames.append(f)
89 89 if ui.verbose or not exact:
90 90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
91 91
92 92 bad = []
93 93 standins = []
94 94
95 95 # Need to lock, otherwise there could be a race condition between
96 96 # when standins are created and added to the repo.
97 97 wlock = repo.wlock()
98 98 try:
99 99 if not opts.get('dry_run'):
100 100 lfdirstate = lfutil.openlfdirstate(ui, repo)
101 101 for f in lfnames:
102 102 standinname = lfutil.standin(f)
103 103 lfutil.writestandin(repo, standinname, hash='',
104 104 executable=lfutil.getexecutable(repo.wjoin(f)))
105 105 standins.append(standinname)
106 106 if lfdirstate[f] == 'r':
107 107 lfdirstate.normallookup(f)
108 108 else:
109 109 lfdirstate.add(f)
110 110 lfdirstate.write()
111 111 bad += [lfutil.splitstandin(f)
112 112 for f in lfutil.repoadd(repo, standins)
113 113 if f in m.files()]
114 114 finally:
115 115 wlock.release()
116 116 return bad
117 117
118 118 def removelargefiles(ui, repo, *pats, **opts):
119 119 after = opts.get('after')
120 120 if not pats and not after:
121 121 raise util.Abort(_('no files specified'))
122 122 m = scmutil.match(repo[None], pats, opts)
123 123 try:
124 124 repo.lfstatus = True
125 125 s = repo.status(match=m, clean=True)
126 126 finally:
127 127 repo.lfstatus = False
128 128 manifest = repo[None].manifest()
129 129 modified, added, deleted, clean = [[f for f in list
130 130 if lfutil.standin(f) in manifest]
131 131 for list in [s[0], s[1], s[3], s[6]]]
132 132
133 133 def warn(files, reason):
134 134 for f in files:
135 135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
136 136 % (m.rel(f), reason))
137 137
138 138 if after:
139 139 remove, forget = deleted, []
140 140 warn(modified + added + clean, _('file still exists'))
141 141 else:
142 142 remove, forget = deleted + clean, []
143 143 warn(modified, _('file is modified'))
144 144 warn(added, _('file has been marked for add'))
145 145
146 146 for f in sorted(remove + forget):
147 147 if ui.verbose or not m.exact(f):
148 148 ui.status(_('removing %s\n') % m.rel(f))
149 149
150 150 # Need to lock because standin files are deleted then removed from the
151 151 # repository and we could race inbetween.
152 152 wlock = repo.wlock()
153 153 try:
154 154 lfdirstate = lfutil.openlfdirstate(ui, repo)
155 155 for f in remove:
156 156 if not after:
157 157 # If this is being called by addremove, notify the user that we
158 158 # are removing the file.
159 159 if getattr(repo, "_isaddremove", False):
160 160 ui.status(_('removing %s\n') % f)
161 161 if os.path.exists(repo.wjoin(f)):
162 162 util.unlinkpath(repo.wjoin(f))
163 163 lfdirstate.remove(f)
164 164 lfdirstate.write()
165 165 forget = [lfutil.standin(f) for f in forget]
166 166 remove = [lfutil.standin(f) for f in remove]
167 167 lfutil.repoforget(repo, forget)
168 168 # If this is being called by addremove, let the original addremove
169 169 # function handle this.
170 170 if not getattr(repo, "_isaddremove", False):
171 171 lfutil.reporemove(repo, remove, unlink=True)
172 172 finally:
173 173 wlock.release()
174 174
175 175 # For overriding mercurial.hgweb.webcommands so that largefiles will
176 176 # appear at their right place in the manifests.
177 177 def decodepath(orig, path):
178 178 return lfutil.splitstandin(path) or path
179 179
180 180 # -- Wrappers: modify existing commands --------------------------------
181 181
182 182 # Add works by going through the files that the user wanted to add and
183 183 # checking if they should be added as largefiles. Then it makes a new
184 184 # matcher which matches only the normal files and runs the original
185 185 # version of add.
186 186 def overrideadd(orig, ui, repo, *pats, **opts):
187 187 normal = opts.pop('normal')
188 188 if normal:
189 189 if opts.get('large'):
190 190 raise util.Abort(_('--normal cannot be used with --large'))
191 191 return orig(ui, repo, *pats, **opts)
192 192 bad = addlargefiles(ui, repo, *pats, **opts)
193 193 installnormalfilesmatchfn(repo[None].manifest())
194 194 result = orig(ui, repo, *pats, **opts)
195 195 restorematchfn()
196 196
197 197 return (result == 1 or bad) and 1 or 0
198 198
199 199 def overrideremove(orig, ui, repo, *pats, **opts):
200 200 installnormalfilesmatchfn(repo[None].manifest())
201 201 orig(ui, repo, *pats, **opts)
202 202 restorematchfn()
203 203 removelargefiles(ui, repo, *pats, **opts)
204 204
205 205 def overridestatusfn(orig, repo, rev2, **opts):
206 206 try:
207 207 repo._repo.lfstatus = True
208 208 return orig(repo, rev2, **opts)
209 209 finally:
210 210 repo._repo.lfstatus = False
211 211
212 212 def overridestatus(orig, ui, repo, *pats, **opts):
213 213 try:
214 214 repo.lfstatus = True
215 215 return orig(ui, repo, *pats, **opts)
216 216 finally:
217 217 repo.lfstatus = False
218 218
219 219 def overridedirty(orig, repo, ignoreupdate=False):
220 220 try:
221 221 repo._repo.lfstatus = True
222 222 return orig(repo, ignoreupdate)
223 223 finally:
224 224 repo._repo.lfstatus = False
225 225
226 226 def overridelog(orig, ui, repo, *pats, **opts):
227 227 try:
228 228 repo.lfstatus = True
229 229 orig(ui, repo, *pats, **opts)
230 230 finally:
231 231 repo.lfstatus = False
232 232
233 233 def overrideverify(orig, ui, repo, *pats, **opts):
234 234 large = opts.pop('large', False)
235 235 all = opts.pop('lfa', False)
236 236 contents = opts.pop('lfc', False)
237 237
238 238 result = orig(ui, repo, *pats, **opts)
239 239 if large:
240 240 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
241 241 return result
242 242
243 243 # Override needs to refresh standins so that update's normal merge
244 244 # will go through properly. Then the other update hook (overriding repo.update)
245 245 # will get the new files. Filemerge is also overriden so that the merge
246 246 # will merge standins correctly.
247 247 def overrideupdate(orig, ui, repo, *pats, **opts):
248 248 lfdirstate = lfutil.openlfdirstate(ui, repo)
249 249 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
250 250 False, False)
251 251 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
252 252
253 253 # Need to lock between the standins getting updated and their
254 254 # largefiles getting updated
255 255 wlock = repo.wlock()
256 256 try:
257 257 if opts['check']:
258 258 mod = len(modified) > 0
259 259 for lfile in unsure:
260 260 standin = lfutil.standin(lfile)
261 261 if repo['.'][standin].data().strip() != \
262 262 lfutil.hashfile(repo.wjoin(lfile)):
263 263 mod = True
264 264 else:
265 265 lfdirstate.normal(lfile)
266 266 lfdirstate.write()
267 267 if mod:
268 268 raise util.Abort(_('uncommitted local changes'))
269 269 # XXX handle removed differently
270 270 if not opts['clean']:
271 271 for lfile in unsure + modified + added:
272 272 lfutil.updatestandin(repo, lfutil.standin(lfile))
273 273 finally:
274 274 wlock.release()
275 275 return orig(ui, repo, *pats, **opts)
276 276
277 277 # Before starting the manifest merge, merge.updates will call
278 278 # _checkunknown to check if there are any files in the merged-in
279 279 # changeset that collide with unknown files in the working copy.
280 280 #
281 281 # The largefiles are seen as unknown, so this prevents us from merging
282 282 # in a file 'foo' if we already have a largefile with the same name.
283 283 #
284 284 # The overridden function filters the unknown files by removing any
285 285 # largefiles. This makes the merge proceed and we can then handle this
286 286 # case further in the overridden manifestmerge function below.
287 287 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
288 288 if lfutil.standin(f) in wctx:
289 289 return False
290 290 return origfn(repo, wctx, mctx, f)
291 291
292 292 # The manifest merge handles conflicts on the manifest level. We want
293 293 # to handle changes in largefile-ness of files at this level too.
294 294 #
295 295 # The strategy is to run the original manifestmerge and then process
296 296 # the action list it outputs. There are two cases we need to deal with:
297 297 #
298 298 # 1. Normal file in p1, largefile in p2. Here the largefile is
299 299 # detected via its standin file, which will enter the working copy
300 300 # with a "get" action. It is not "merge" since the standin is all
301 301 # Mercurial is concerned with at this level -- the link to the
302 302 # existing normal file is not relevant here.
303 303 #
304 304 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
305 305 # since the largefile will be present in the working copy and
306 306 # different from the normal file in p2. Mercurial therefore
307 307 # triggers a merge action.
308 308 #
309 309 # In both cases, we prompt the user and emit new actions to either
310 310 # remove the standin (if the normal file was kept) or to remove the
311 311 # normal file and get the standin (if the largefile was kept). The
312 312 # default prompt answer is to use the largefile version since it was
313 313 # presumably changed on purpose.
314 314 #
315 315 # Finally, the merge.applyupdates function will then take care of
316 316 # writing the files into the working copy and lfcommands.updatelfiles
317 317 # will update the largefiles.
318 318 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
319 319 actions = origfn(repo, p1, p2, pa, overwrite, partial)
320 320 processed = []
321 321
322 322 for action in actions:
323 323 if overwrite:
324 324 processed.append(action)
325 325 continue
326 326 f, m = action[:2]
327 327
328 328 choices = (_('&Largefile'), _('&Normal file'))
329 329 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
330 330 # Case 1: normal file in the working copy, largefile in
331 331 # the second parent
332 332 lfile = lfutil.splitstandin(f)
333 333 standin = f
334 334 msg = _('%s has been turned into a largefile\n'
335 335 'use (l)argefile or keep as (n)ormal file?') % lfile
336 336 if repo.ui.promptchoice(msg, choices, 0) == 0:
337 337 processed.append((lfile, "r"))
338 338 processed.append((standin, "g", p2.flags(standin)))
339 339 else:
340 340 processed.append((standin, "r"))
341 341 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
342 342 # Case 2: largefile in the working copy, normal file in
343 343 # the second parent
344 344 standin = lfutil.standin(f)
345 345 lfile = f
346 346 msg = _('%s has been turned into a normal file\n'
347 347 'keep as (l)argefile or use (n)ormal file?') % lfile
348 348 if repo.ui.promptchoice(msg, choices, 0) == 0:
349 349 processed.append((lfile, "r"))
350 350 else:
351 351 processed.append((standin, "r"))
352 352 processed.append((lfile, "g", p2.flags(lfile)))
353 353 else:
354 354 processed.append(action)
355 355
356 356 return processed
357 357
358 358 # Override filemerge to prompt the user about how they wish to merge
359 359 # largefiles. This will handle identical edits, and copy/rename +
360 360 # edit without prompting the user.
361 361 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
362 362 # Use better variable names here. Because this is a wrapper we cannot
363 363 # change the variable names in the function declaration.
364 364 fcdest, fcother, fcancestor = fcd, fco, fca
365 365 if not lfutil.isstandin(orig):
366 366 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
367 367 else:
368 368 if not fcother.cmp(fcdest): # files identical?
369 369 return None
370 370
371 371 # backwards, use working dir parent as ancestor
372 372 if fcancestor == fcother:
373 373 fcancestor = fcdest.parents()[0]
374 374
375 375 if orig != fcother.path():
376 376 repo.ui.status(_('merging %s and %s to %s\n')
377 377 % (lfutil.splitstandin(orig),
378 378 lfutil.splitstandin(fcother.path()),
379 379 lfutil.splitstandin(fcdest.path())))
380 380 else:
381 381 repo.ui.status(_('merging %s\n')
382 382 % lfutil.splitstandin(fcdest.path()))
383 383
384 384 if fcancestor.path() != fcother.path() and fcother.data() == \
385 385 fcancestor.data():
386 386 return 0
387 387 if fcancestor.path() != fcdest.path() and fcdest.data() == \
388 388 fcancestor.data():
389 389 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
390 390 return 0
391 391
392 392 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
393 393 'keep (l)ocal or take (o)ther?') %
394 394 lfutil.splitstandin(orig),
395 395 (_('&Local'), _('&Other')), 0) == 0:
396 396 return 0
397 397 else:
398 398 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
399 399 return 0
400 400
401 401 # Copy first changes the matchers to match standins instead of
402 402 # largefiles. Then it overrides util.copyfile in that function it
403 403 # checks if the destination largefile already exists. It also keeps a
404 404 # list of copied files so that the largefiles can be copied and the
405 405 # dirstate updated.
406 406 def overridecopy(orig, ui, repo, pats, opts, rename=False):
407 407 # doesn't remove largefile on rename
408 408 if len(pats) < 2:
409 409 # this isn't legal, let the original function deal with it
410 410 return orig(ui, repo, pats, opts, rename)
411 411
412 412 def makestandin(relpath):
413 413 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
414 414 return os.path.join(repo.wjoin(lfutil.standin(path)))
415 415
416 416 fullpats = scmutil.expandpats(pats)
417 417 dest = fullpats[-1]
418 418
419 419 if os.path.isdir(dest):
420 420 if not os.path.isdir(makestandin(dest)):
421 421 os.makedirs(makestandin(dest))
422 422 # This could copy both lfiles and normal files in one command,
423 423 # but we don't want to do that. First replace their matcher to
424 424 # only match normal files and run it, then replace it to just
425 425 # match largefiles and run it again.
426 426 nonormalfiles = False
427 427 nolfiles = False
428 428 try:
429 429 try:
430 430 installnormalfilesmatchfn(repo[None].manifest())
431 431 result = orig(ui, repo, pats, opts, rename)
432 432 except util.Abort, e:
433 433 if str(e) != 'no files to copy':
434 434 raise e
435 435 else:
436 436 nonormalfiles = True
437 437 result = 0
438 438 finally:
439 439 restorematchfn()
440 440
441 441 # The first rename can cause our current working directory to be removed.
442 442 # In that case there is nothing left to copy/rename so just quit.
443 443 try:
444 444 repo.getcwd()
445 445 except OSError:
446 446 return result
447 447
448 448 try:
449 449 try:
450 450 # When we call orig below it creates the standins but we don't add
451 451 # them to the dir state until later so lock during that time.
452 452 wlock = repo.wlock()
453 453
454 454 manifest = repo[None].manifest()
455 455 oldmatch = None # for the closure
456 456 def overridematch(ctx, pats=[], opts={}, globbed=False,
457 457 default='relpath'):
458 458 newpats = []
459 459 # The patterns were previously mangled to add the standin
460 460 # directory; we need to remove that now
461 461 for pat in pats:
462 462 if match_.patkind(pat) is None and lfutil.shortname in pat:
463 463 newpats.append(pat.replace(lfutil.shortname, ''))
464 464 else:
465 465 newpats.append(pat)
466 466 match = oldmatch(ctx, newpats, opts, globbed, default)
467 467 m = copy.copy(match)
468 468 lfile = lambda f: lfutil.standin(f) in manifest
469 469 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
470 470 m._fmap = set(m._files)
471 471 origmatchfn = m.matchfn
472 472 m.matchfn = lambda f: (lfutil.isstandin(f) and
473 473 (f in manifest) and
474 474 origmatchfn(lfutil.splitstandin(f)) or
475 475 None)
476 476 return m
477 477 oldmatch = installmatchfn(overridematch)
478 478 listpats = []
479 479 for pat in pats:
480 480 if match_.patkind(pat) is not None:
481 481 listpats.append(pat)
482 482 else:
483 483 listpats.append(makestandin(pat))
484 484
485 485 try:
486 486 origcopyfile = util.copyfile
487 487 copiedfiles = []
488 488 def overridecopyfile(src, dest):
489 489 if (lfutil.shortname in src and
490 490 dest.startswith(repo.wjoin(lfutil.shortname))):
491 491 destlfile = dest.replace(lfutil.shortname, '')
492 492 if not opts['force'] and os.path.exists(destlfile):
493 493 raise IOError('',
494 494 _('destination largefile already exists'))
495 495 copiedfiles.append((src, dest))
496 496 origcopyfile(src, dest)
497 497
498 498 util.copyfile = overridecopyfile
499 499 result += orig(ui, repo, listpats, opts, rename)
500 500 finally:
501 501 util.copyfile = origcopyfile
502 502
503 503 lfdirstate = lfutil.openlfdirstate(ui, repo)
504 504 for (src, dest) in copiedfiles:
505 505 if (lfutil.shortname in src and
506 506 dest.startswith(repo.wjoin(lfutil.shortname))):
507 507 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
508 508 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
509 509 destlfiledir = os.path.dirname(destlfile) or '.'
510 510 if not os.path.isdir(destlfiledir):
511 511 os.makedirs(destlfiledir)
512 512 if rename:
513 513 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
514 514 lfdirstate.remove(srclfile)
515 515 else:
516 516 util.copyfile(srclfile, destlfile)
517 517 lfdirstate.add(destlfile)
518 518 lfdirstate.write()
519 519 except util.Abort, e:
520 520 if str(e) != 'no files to copy':
521 521 raise e
522 522 else:
523 523 nolfiles = True
524 524 finally:
525 525 restorematchfn()
526 526 wlock.release()
527 527
528 528 if nolfiles and nonormalfiles:
529 529 raise util.Abort(_('no files to copy'))
530 530
531 531 return result
532 532
533 533 # When the user calls revert, we have to be careful to not revert any
534 534 # changes to other largefiles accidentally. This means we have to keep
535 535 # track of the largefiles that are being reverted so we only pull down
536 536 # the necessary largefiles.
537 537 #
538 538 # Standins are only updated (to match the hash of largefiles) before
539 539 # commits. Update the standins then run the original revert, changing
540 540 # the matcher to hit standins instead of largefiles. Based on the
541 541 # resulting standins update the largefiles. Then return the standins
542 542 # to their proper state
543 543 def overriderevert(orig, ui, repo, *pats, **opts):
544 544 # Because we put the standins in a bad state (by updating them)
545 545 # and then return them to a correct state we need to lock to
546 546 # prevent others from changing them in their incorrect state.
547 547 wlock = repo.wlock()
548 548 try:
549 549 lfdirstate = lfutil.openlfdirstate(ui, repo)
550 550 (modified, added, removed, missing, unknown, ignored, clean) = \
551 551 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
552 552 for lfile in modified:
553 553 lfutil.updatestandin(repo, lfutil.standin(lfile))
554 554 for lfile in missing:
555 555 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
556 556 os.unlink(repo.wjoin(lfutil.standin(lfile)))
557 557
558 558 try:
559 559 ctx = repo[opts.get('rev')]
560 560 oldmatch = None # for the closure
561 561 def overridematch(ctx, pats=[], opts={}, globbed=False,
562 562 default='relpath'):
563 563 match = oldmatch(ctx, pats, opts, globbed, default)
564 564 m = copy.copy(match)
565 565 def tostandin(f):
566 566 if lfutil.standin(f) in ctx:
567 567 return lfutil.standin(f)
568 568 elif lfutil.standin(f) in repo[None]:
569 569 return None
570 570 return f
571 571 m._files = [tostandin(f) for f in m._files]
572 572 m._files = [f for f in m._files if f is not None]
573 573 m._fmap = set(m._files)
574 574 origmatchfn = m.matchfn
575 575 def matchfn(f):
576 576 if lfutil.isstandin(f):
577 577 # We need to keep track of what largefiles are being
578 578 # matched so we know which ones to update later --
579 579 # otherwise we accidentally revert changes to other
580 580 # largefiles. This is repo-specific, so duckpunch the
581 581 # repo object to keep the list of largefiles for us
582 582 # later.
583 583 if origmatchfn(lfutil.splitstandin(f)) and \
584 584 (f in repo[None] or f in ctx):
585 585 lfileslist = getattr(repo, '_lfilestoupdate', [])
586 586 lfileslist.append(lfutil.splitstandin(f))
587 587 repo._lfilestoupdate = lfileslist
588 588 return True
589 589 else:
590 590 return False
591 591 return origmatchfn(f)
592 592 m.matchfn = matchfn
593 593 return m
594 594 oldmatch = installmatchfn(overridematch)
595 595 scmutil.match
596 596 matches = overridematch(repo[None], pats, opts)
597 597 orig(ui, repo, *pats, **opts)
598 598 finally:
599 599 restorematchfn()
600 600 lfileslist = getattr(repo, '_lfilestoupdate', [])
601 601 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
602 602 printmessage=False)
603 603
604 604 # empty out the largefiles list so we start fresh next time
605 605 repo._lfilestoupdate = []
606 606 for lfile in modified:
607 607 if lfile in lfileslist:
608 608 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
609 609 in repo['.']:
610 610 lfutil.writestandin(repo, lfutil.standin(lfile),
611 611 repo['.'][lfile].data().strip(),
612 612 'x' in repo['.'][lfile].flags())
613 613 lfdirstate = lfutil.openlfdirstate(ui, repo)
614 614 for lfile in added:
615 615 standin = lfutil.standin(lfile)
616 616 if standin not in ctx and (standin in matches or opts.get('all')):
617 617 if lfile in lfdirstate:
618 618 lfdirstate.drop(lfile)
619 619 util.unlinkpath(repo.wjoin(standin))
620 620 lfdirstate.write()
621 621 finally:
622 622 wlock.release()
623 623
624 624 def hgupdate(orig, repo, node):
625 625 # Only call updatelfiles the standins that have changed to save time
626 626 oldstandins = lfutil.getstandinsstate(repo)
627 627 result = orig(repo, node)
628 628 newstandins = lfutil.getstandinsstate(repo)
629 629 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
630 630 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, printmessage=True)
631 631 return result
632 632
633 633 def hgclean(orig, repo, node, show_stats=True):
634 634 result = orig(repo, node, show_stats)
635 635 lfcommands.updatelfiles(repo.ui, repo)
636 636 return result
637 637
638 638 def hgmerge(orig, repo, node, force=None, remind=True):
639 639 # Mark the repo as being in the middle of a merge, so that
640 640 # updatelfiles() will know that it needs to trust the standins in
641 641 # the working copy, not in the standins in the current node
642 642 repo._ismerging = True
643 643 try:
644 644 result = orig(repo, node, force, remind)
645 645 lfcommands.updatelfiles(repo.ui, repo)
646 646 finally:
647 647 repo._ismerging = False
648 648 return result
649 649
650 650 # When we rebase a repository with remotely changed largefiles, we need to
651 651 # take some extra care so that the largefiles are correctly updated in the
652 652 # working copy
653 653 def overridepull(orig, ui, repo, source=None, **opts):
654 654 if opts.get('rebase', False):
655 655 repo._isrebasing = True
656 656 try:
657 657 if opts.get('update'):
658 658 del opts['update']
659 659 ui.debug('--update and --rebase are not compatible, ignoring '
660 660 'the update flag\n')
661 661 del opts['rebase']
662 662 cmdutil.bailifchanged(repo)
663 663 revsprepull = len(repo)
664 664 origpostincoming = commands.postincoming
665 665 def _dummy(*args, **kwargs):
666 666 pass
667 667 commands.postincoming = _dummy
668 668 repo.lfpullsource = source
669 669 if not source:
670 670 source = 'default'
671 671 try:
672 672 result = commands.pull(ui, repo, source, **opts)
673 673 finally:
674 674 commands.postincoming = origpostincoming
675 675 revspostpull = len(repo)
676 676 if revspostpull > revsprepull:
677 677 result = result or rebase.rebase(ui, repo)
678 678 finally:
679 679 repo._isrebasing = False
680 680 else:
681 681 repo.lfpullsource = source
682 682 if not source:
683 683 source = 'default'
684 684 oldheads = lfutil.getcurrentheads(repo)
685 685 result = orig(ui, repo, source, **opts)
686 686 # If we do not have the new largefiles for any new heads we pulled, we
687 687 # will run into a problem later if we try to merge or rebase with one of
688 688 # these heads, so cache the largefiles now direclty into the system
689 689 # cache.
690 690 ui.status(_("caching new largefiles\n"))
691 691 numcached = 0
692 692 heads = lfutil.getcurrentheads(repo)
693 693 newheads = set(heads).difference(set(oldheads))
694 694 for head in newheads:
695 695 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
696 696 numcached += len(cached)
697 697 ui.status(_("%d largefiles cached\n") % numcached)
698 698 return result
699 699
700 700 def overrideclone(orig, ui, source, dest=None, **opts):
701 701 result = hg.clone(ui, opts, source, dest,
702 702 pull=opts.get('pull'),
703 703 stream=opts.get('uncompressed'),
704 704 rev=opts.get('rev'),
705 705 update=True, # required for successful walkchangerevs
706 706 branch=opts.get('branch'))
707 707 if result is None:
708 708 return True
709 totalsuccess = 0
710 totalmissing = 0
711 709 if opts.get('all_largefiles'):
712 710 sourcerepo, destrepo = result
713 matchfn = scmutil.match(destrepo[None],
714 [destrepo.wjoin(lfutil.shortname)], {})
715 def prepare(ctx, fns):
716 pass
717 for ctx in cmdutil.walkchangerevs(destrepo, matchfn, {'rev' : None},
718 prepare):
719 success, missing = lfcommands.cachelfiles(ui, destrepo, ctx.node())
720 totalsuccess += len(success)
721 totalmissing += len(missing)
722 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
723 if totalmissing > 0:
724 ui.status(_("%d largefiles failed to download\n") % totalmissing)
725 return totalmissing != 0
711 success, missing = lfcommands.downloadlfiles(ui, destrepo, None)
712 return missing != 0
713 return result is None
726 714
727 715 def overriderebase(orig, ui, repo, **opts):
728 716 repo._isrebasing = True
729 717 try:
730 718 orig(ui, repo, **opts)
731 719 finally:
732 720 repo._isrebasing = False
733 721
734 722 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
735 723 prefix=None, mtime=None, subrepos=None):
736 724 # No need to lock because we are only reading history and
737 725 # largefile caches, neither of which are modified.
738 726 lfcommands.cachelfiles(repo.ui, repo, node)
739 727
740 728 if kind not in archival.archivers:
741 729 raise util.Abort(_("unknown archive type '%s'") % kind)
742 730
743 731 ctx = repo[node]
744 732
745 733 if kind == 'files':
746 734 if prefix:
747 735 raise util.Abort(
748 736 _('cannot give prefix when archiving to files'))
749 737 else:
750 738 prefix = archival.tidyprefix(dest, kind, prefix)
751 739
752 740 def write(name, mode, islink, getdata):
753 741 if matchfn and not matchfn(name):
754 742 return
755 743 data = getdata()
756 744 if decode:
757 745 data = repo.wwritedata(name, data)
758 746 archiver.addfile(prefix + name, mode, islink, data)
759 747
760 748 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
761 749
762 750 if repo.ui.configbool("ui", "archivemeta", True):
763 751 def metadata():
764 752 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
765 753 hex(repo.changelog.node(0)), hex(node), ctx.branch())
766 754
767 755 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
768 756 if repo.tagtype(t) == 'global')
769 757 if not tags:
770 758 repo.ui.pushbuffer()
771 759 opts = {'template': '{latesttag}\n{latesttagdistance}',
772 760 'style': '', 'patch': None, 'git': None}
773 761 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
774 762 ltags, dist = repo.ui.popbuffer().split('\n')
775 763 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
776 764 tags += 'latesttagdistance: %s\n' % dist
777 765
778 766 return base + tags
779 767
780 768 write('.hg_archival.txt', 0644, False, metadata)
781 769
782 770 for f in ctx:
783 771 ff = ctx.flags(f)
784 772 getdata = ctx[f].data
785 773 if lfutil.isstandin(f):
786 774 path = lfutil.findfile(repo, getdata().strip())
787 775 if path is None:
788 776 raise util.Abort(
789 777 _('largefile %s not found in repo store or system cache')
790 778 % lfutil.splitstandin(f))
791 779 f = lfutil.splitstandin(f)
792 780
793 781 def getdatafn():
794 782 fd = None
795 783 try:
796 784 fd = open(path, 'rb')
797 785 return fd.read()
798 786 finally:
799 787 if fd:
800 788 fd.close()
801 789
802 790 getdata = getdatafn
803 791 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
804 792
805 793 if subrepos:
806 794 for subpath in ctx.substate:
807 795 sub = ctx.sub(subpath)
808 796 sub.archive(repo.ui, archiver, prefix)
809 797
810 798 archiver.done()
811 799
812 800 def hgsubrepoarchive(orig, repo, ui, archiver, prefix):
813 801 rev = repo._state[1]
814 802 ctx = repo._repo[rev]
815 803
816 804 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
817 805
818 806 def write(name, mode, islink, getdata):
819 807 if lfutil.isstandin(name):
820 808 return
821 809 data = getdata()
822 810
823 811 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
824 812
825 813 for f in ctx:
826 814 ff = ctx.flags(f)
827 815 getdata = ctx[f].data
828 816 if lfutil.isstandin(f):
829 817 path = lfutil.findfile(repo._repo, getdata().strip())
830 818 if path is None:
831 819 raise util.Abort(
832 820 _('largefile %s not found in repo store or system cache')
833 821 % lfutil.splitstandin(f))
834 822 f = lfutil.splitstandin(f)
835 823
836 824 def getdatafn():
837 825 fd = None
838 826 try:
839 827 fd = open(os.path.join(prefix, path), 'rb')
840 828 return fd.read()
841 829 finally:
842 830 if fd:
843 831 fd.close()
844 832
845 833 getdata = getdatafn
846 834
847 835 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
848 836
849 837 for subpath in ctx.substate:
850 838 sub = ctx.sub(subpath)
851 839 sub.archive(repo.ui, archiver, prefix)
852 840
853 841 # If a largefile is modified, the change is not reflected in its
854 842 # standin until a commit. cmdutil.bailifchanged() raises an exception
855 843 # if the repo has uncommitted changes. Wrap it to also check if
856 844 # largefiles were changed. This is used by bisect and backout.
857 845 def overridebailifchanged(orig, repo):
858 846 orig(repo)
859 847 repo.lfstatus = True
860 848 modified, added, removed, deleted = repo.status()[:4]
861 849 repo.lfstatus = False
862 850 if modified or added or removed or deleted:
863 851 raise util.Abort(_('outstanding uncommitted changes'))
864 852
865 853 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
866 854 def overridefetch(orig, ui, repo, *pats, **opts):
867 855 repo.lfstatus = True
868 856 modified, added, removed, deleted = repo.status()[:4]
869 857 repo.lfstatus = False
870 858 if modified or added or removed or deleted:
871 859 raise util.Abort(_('outstanding uncommitted changes'))
872 860 return orig(ui, repo, *pats, **opts)
873 861
874 862 def overrideforget(orig, ui, repo, *pats, **opts):
875 863 installnormalfilesmatchfn(repo[None].manifest())
876 864 orig(ui, repo, *pats, **opts)
877 865 restorematchfn()
878 866 m = scmutil.match(repo[None], pats, opts)
879 867
880 868 try:
881 869 repo.lfstatus = True
882 870 s = repo.status(match=m, clean=True)
883 871 finally:
884 872 repo.lfstatus = False
885 873 forget = sorted(s[0] + s[1] + s[3] + s[6])
886 874 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
887 875
888 876 for f in forget:
889 877 if lfutil.standin(f) not in repo.dirstate and not \
890 878 os.path.isdir(m.rel(lfutil.standin(f))):
891 879 ui.warn(_('not removing %s: file is already untracked\n')
892 880 % m.rel(f))
893 881
894 882 for f in forget:
895 883 if ui.verbose or not m.exact(f):
896 884 ui.status(_('removing %s\n') % m.rel(f))
897 885
898 886 # Need to lock because standin files are deleted then removed from the
899 887 # repository and we could race inbetween.
900 888 wlock = repo.wlock()
901 889 try:
902 890 lfdirstate = lfutil.openlfdirstate(ui, repo)
903 891 for f in forget:
904 892 if lfdirstate[f] == 'a':
905 893 lfdirstate.drop(f)
906 894 else:
907 895 lfdirstate.remove(f)
908 896 lfdirstate.write()
909 897 lfutil.reporemove(repo, [lfutil.standin(f) for f in forget],
910 898 unlink=True)
911 899 finally:
912 900 wlock.release()
913 901
914 902 def getoutgoinglfiles(ui, repo, dest=None, **opts):
915 903 dest = ui.expandpath(dest or 'default-push', dest or 'default')
916 904 dest, branches = hg.parseurl(dest, opts.get('branch'))
917 905 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
918 906 if revs:
919 907 revs = [repo.lookup(rev) for rev in revs]
920 908
921 909 remoteui = hg.remoteui
922 910
923 911 try:
924 912 remote = hg.repository(remoteui(repo, opts), dest)
925 913 except error.RepoError:
926 914 return None
927 915 o = lfutil.findoutgoing(repo, remote, False)
928 916 if not o:
929 917 return None
930 918 o = repo.changelog.nodesbetween(o, revs)[0]
931 919 if opts.get('newest_first'):
932 920 o.reverse()
933 921
934 922 toupload = set()
935 923 for n in o:
936 924 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
937 925 ctx = repo[n]
938 926 files = set(ctx.files())
939 927 if len(parents) == 2:
940 928 mc = ctx.manifest()
941 929 mp1 = ctx.parents()[0].manifest()
942 930 mp2 = ctx.parents()[1].manifest()
943 931 for f in mp1:
944 932 if f not in mc:
945 933 files.add(f)
946 934 for f in mp2:
947 935 if f not in mc:
948 936 files.add(f)
949 937 for f in mc:
950 938 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
951 939 files.add(f)
952 940 toupload = toupload.union(
953 941 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
954 942 return toupload
955 943
956 944 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
957 945 orig(ui, repo, dest, **opts)
958 946
959 947 if opts.pop('large', None):
960 948 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
961 949 if toupload is None:
962 950 ui.status(_('largefiles: No remote repo\n'))
963 951 else:
964 952 ui.status(_('largefiles to upload:\n'))
965 953 for file in toupload:
966 954 ui.status(lfutil.splitstandin(file) + '\n')
967 955 ui.status('\n')
968 956
969 957 def overridesummary(orig, ui, repo, *pats, **opts):
970 958 try:
971 959 repo.lfstatus = True
972 960 orig(ui, repo, *pats, **opts)
973 961 finally:
974 962 repo.lfstatus = False
975 963
976 964 if opts.pop('large', None):
977 965 toupload = getoutgoinglfiles(ui, repo, None, **opts)
978 966 if toupload is None:
979 967 ui.status(_('largefiles: No remote repo\n'))
980 968 else:
981 969 ui.status(_('largefiles: %d to upload\n') % len(toupload))
982 970
983 971 def overrideaddremove(orig, ui, repo, *pats, **opts):
984 972 if not lfutil.islfilesrepo(repo):
985 973 return orig(ui, repo, *pats, **opts)
986 974 # Get the list of missing largefiles so we can remove them
987 975 lfdirstate = lfutil.openlfdirstate(ui, repo)
988 976 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
989 977 False, False)
990 978 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
991 979
992 980 # Call into the normal remove code, but the removing of the standin, we want
993 981 # to have handled by original addremove. Monkey patching here makes sure
994 982 # we don't remove the standin in the largefiles code, preventing a very
995 983 # confused state later.
996 984 if missing:
997 985 repo._isaddremove = True
998 986 removelargefiles(ui, repo, *missing, **opts)
999 987 repo._isaddremove = False
1000 988 # Call into the normal add code, and any files that *should* be added as
1001 989 # largefiles will be
1002 990 addlargefiles(ui, repo, *pats, **opts)
1003 991 # Now that we've handled largefiles, hand off to the original addremove
1004 992 # function to take care of the rest. Make sure it doesn't do anything with
1005 993 # largefiles by installing a matcher that will ignore them.
1006 994 installnormalfilesmatchfn(repo[None].manifest())
1007 995 result = orig(ui, repo, *pats, **opts)
1008 996 restorematchfn()
1009 997 return result
1010 998
1011 999 # Calling purge with --all will cause the largefiles to be deleted.
1012 1000 # Override repo.status to prevent this from happening.
1013 1001 def overridepurge(orig, ui, repo, *dirs, **opts):
1014 1002 oldstatus = repo.status
1015 1003 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1016 1004 clean=False, unknown=False, listsubrepos=False):
1017 1005 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1018 1006 listsubrepos)
1019 1007 lfdirstate = lfutil.openlfdirstate(ui, repo)
1020 1008 modified, added, removed, deleted, unknown, ignored, clean = r
1021 1009 unknown = [f for f in unknown if lfdirstate[f] == '?']
1022 1010 ignored = [f for f in ignored if lfdirstate[f] == '?']
1023 1011 return modified, added, removed, deleted, unknown, ignored, clean
1024 1012 repo.status = overridestatus
1025 1013 orig(ui, repo, *dirs, **opts)
1026 1014 repo.status = oldstatus
1027 1015
1028 1016 def overriderollback(orig, ui, repo, **opts):
1029 1017 result = orig(ui, repo, **opts)
1030 1018 merge.update(repo, node=None, branchmerge=False, force=True,
1031 1019 partial=lfutil.isstandin)
1032 1020 wlock = repo.wlock()
1033 1021 try:
1034 1022 lfdirstate = lfutil.openlfdirstate(ui, repo)
1035 1023 lfiles = lfutil.listlfiles(repo)
1036 1024 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1037 1025 for file in lfiles:
1038 1026 if file in oldlfiles:
1039 1027 lfdirstate.normallookup(file)
1040 1028 else:
1041 1029 lfdirstate.add(file)
1042 1030 lfdirstate.write()
1043 1031 finally:
1044 1032 wlock.release()
1045 1033 return result
1046 1034
1047 1035 def overridetransplant(orig, ui, repo, *revs, **opts):
1048 1036 try:
1049 1037 oldstandins = lfutil.getstandinsstate(repo)
1050 1038 repo._istransplanting = True
1051 1039 result = orig(ui, repo, *revs, **opts)
1052 1040 newstandins = lfutil.getstandinsstate(repo)
1053 1041 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1054 1042 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1055 1043 printmessage=True)
1056 1044 finally:
1057 1045 repo._istransplanting = False
1058 1046 return result
1059 1047
1060 1048 def overridecat(orig, ui, repo, file1, *pats, **opts):
1061 1049 rev = opts.get('rev')
1062 1050 if not lfutil.standin(file1) in repo[rev]:
1063 1051 result = orig(ui, repo, file1, *pats, **opts)
1064 1052 return result
1065 1053 return lfcommands.catlfile(repo, file1, opts.get('rev'), opts.get('output'))
General Comments 0
You need to be logged in to leave comments. Login now