##// END OF EJS Templates
largefiles: use revisions as a ui.progress unit...
av6 -
r28464:6e346902 default
parent child Browse files
Show More
@@ -1,544 +1,544 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, errno
12 12 import shutil
13 13
14 14 from mercurial import util, match as match_, hg, node, context, error, \
15 15 cmdutil, scmutil, commands
16 16 from mercurial.i18n import _
17 17 from mercurial.lock import release
18 18
19 19 from hgext.convert import convcmd
20 20 from hgext.convert import filemap
21 21
22 22 import lfutil
23 23 import basestore
24 24
25 25 # -- Commands ----------------------------------------------------------
26 26
27 27 cmdtable = {}
28 28 command = cmdutil.command(cmdtable)
29 29
30 30 @command('lfconvert',
31 31 [('s', 'size', '',
32 32 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
33 33 ('', 'to-normal', False,
34 34 _('convert from a largefiles repo to a normal repo')),
35 35 ],
36 36 _('hg lfconvert SOURCE DEST [FILE ...]'),
37 37 norepo=True,
38 38 inferrepo=True)
39 39 def lfconvert(ui, src, dest, *pats, **opts):
40 40 '''convert a normal repository to a largefiles repository
41 41
42 42 Convert repository SOURCE to a new repository DEST, identical to
43 43 SOURCE except that certain files will be converted as largefiles:
44 44 specifically, any file that matches any PATTERN *or* whose size is
45 45 above the minimum size threshold is converted as a largefile. The
46 46 size used to determine whether or not to track a file as a
47 47 largefile is the size of the first version of the file. The
48 48 minimum size can be specified either with --size or in
49 49 configuration as ``largefiles.size``.
50 50
51 51 After running this command you will need to make sure that
52 52 largefiles is enabled anywhere you intend to push the new
53 53 repository.
54 54
55 55 Use --to-normal to convert largefiles back to normal files; after
56 56 this, the DEST repository can be used without largefiles at all.'''
57 57
58 58 if opts['to_normal']:
59 59 tolfile = False
60 60 else:
61 61 tolfile = True
62 62 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
63 63
64 64 if not hg.islocal(src):
65 65 raise error.Abort(_('%s is not a local Mercurial repo') % src)
66 66 if not hg.islocal(dest):
67 67 raise error.Abort(_('%s is not a local Mercurial repo') % dest)
68 68
69 69 rsrc = hg.repository(ui, src)
70 70 ui.status(_('initializing destination %s\n') % dest)
71 71 rdst = hg.repository(ui, dest, create=True)
72 72
73 73 success = False
74 74 dstwlock = dstlock = None
75 75 try:
76 76 # Get a list of all changesets in the source. The easy way to do this
77 77 # is to simply walk the changelog, using changelog.nodesbetween().
78 78 # Take a look at mercurial/revlog.py:639 for more details.
79 79 # Use a generator instead of a list to decrease memory usage
80 80 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
81 81 rsrc.heads())[0])
82 82 revmap = {node.nullid: node.nullid}
83 83 if tolfile:
84 84 # Lock destination to prevent modification while it is converted to.
85 85 # Don't need to lock src because we are just reading from its
86 86 # history which can't change.
87 87 dstwlock = rdst.wlock()
88 88 dstlock = rdst.lock()
89 89
90 90 lfiles = set()
91 91 normalfiles = set()
92 92 if not pats:
93 93 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
94 94 if pats:
95 95 matcher = match_.match(rsrc.root, '', list(pats))
96 96 else:
97 97 matcher = None
98 98
99 99 lfiletohash = {}
100 100 for ctx in ctxs:
101 101 ui.progress(_('converting revisions'), ctx.rev(),
102 unit=_('revision'), total=rsrc['tip'].rev())
102 unit=_('revisions'), total=rsrc['tip'].rev())
103 103 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
104 104 lfiles, normalfiles, matcher, size, lfiletohash)
105 105 ui.progress(_('converting revisions'), None)
106 106
107 107 if os.path.exists(rdst.wjoin(lfutil.shortname)):
108 108 shutil.rmtree(rdst.wjoin(lfutil.shortname))
109 109
110 110 for f in lfiletohash.keys():
111 111 if os.path.isfile(rdst.wjoin(f)):
112 112 os.unlink(rdst.wjoin(f))
113 113 try:
114 114 os.removedirs(os.path.dirname(rdst.wjoin(f)))
115 115 except OSError:
116 116 pass
117 117
118 118 # If there were any files converted to largefiles, add largefiles
119 119 # to the destination repository's requirements.
120 120 if lfiles:
121 121 rdst.requirements.add('largefiles')
122 122 rdst._writerequirements()
123 123 else:
124 124 class lfsource(filemap.filemap_source):
125 125 def __init__(self, ui, source):
126 126 super(lfsource, self).__init__(ui, source, None)
127 127 self.filemapper.rename[lfutil.shortname] = '.'
128 128
129 129 def getfile(self, name, rev):
130 130 realname, realrev = rev
131 131 f = super(lfsource, self).getfile(name, rev)
132 132
133 133 if (not realname.startswith(lfutil.shortnameslash)
134 134 or f[0] is None):
135 135 return f
136 136
137 137 # Substitute in the largefile data for the hash
138 138 hash = f[0].strip()
139 139 path = lfutil.findfile(rsrc, hash)
140 140
141 141 if path is None:
142 142 raise error.Abort(_("missing largefile for '%s' in %s")
143 143 % (realname, realrev))
144 144 return util.readfile(path), f[1]
145 145
146 146 class converter(convcmd.converter):
147 147 def __init__(self, ui, source, dest, revmapfile, opts):
148 148 src = lfsource(ui, source)
149 149
150 150 super(converter, self).__init__(ui, src, dest, revmapfile,
151 151 opts)
152 152
153 153 found, missing = downloadlfiles(ui, rsrc)
154 154 if missing != 0:
155 155 raise error.Abort(_("all largefiles must be present locally"))
156 156
157 157 orig = convcmd.converter
158 158 convcmd.converter = converter
159 159
160 160 try:
161 161 convcmd.convert(ui, src, dest)
162 162 finally:
163 163 convcmd.converter = orig
164 164 success = True
165 165 finally:
166 166 if tolfile:
167 167 rdst.dirstate.clear()
168 168 release(dstlock, dstwlock)
169 169 if not success:
170 170 # we failed, remove the new directory
171 171 shutil.rmtree(rdst.root)
172 172
173 173 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
174 174 matcher, size, lfiletohash):
175 175 # Convert src parents to dst parents
176 176 parents = _convertparents(ctx, revmap)
177 177
178 178 # Generate list of changed files
179 179 files = _getchangedfiles(ctx, parents)
180 180
181 181 dstfiles = []
182 182 for f in files:
183 183 if f not in lfiles and f not in normalfiles:
184 184 islfile = _islfile(f, ctx, matcher, size)
185 185 # If this file was renamed or copied then copy
186 186 # the largefile-ness of its predecessor
187 187 if f in ctx.manifest():
188 188 fctx = ctx.filectx(f)
189 189 renamed = fctx.renamed()
190 190 renamedlfile = renamed and renamed[0] in lfiles
191 191 islfile |= renamedlfile
192 192 if 'l' in fctx.flags():
193 193 if renamedlfile:
194 194 raise error.Abort(
195 195 _('renamed/copied largefile %s becomes symlink')
196 196 % f)
197 197 islfile = False
198 198 if islfile:
199 199 lfiles.add(f)
200 200 else:
201 201 normalfiles.add(f)
202 202
203 203 if f in lfiles:
204 204 dstfiles.append(lfutil.standin(f))
205 205 # largefile in manifest if it has not been removed/renamed
206 206 if f in ctx.manifest():
207 207 fctx = ctx.filectx(f)
208 208 if 'l' in fctx.flags():
209 209 renamed = fctx.renamed()
210 210 if renamed and renamed[0] in lfiles:
211 211 raise error.Abort(_('largefile %s becomes symlink') % f)
212 212
213 213 # largefile was modified, update standins
214 214 m = util.sha1('')
215 215 m.update(ctx[f].data())
216 216 hash = m.hexdigest()
217 217 if f not in lfiletohash or lfiletohash[f] != hash:
218 218 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
219 219 executable = 'x' in ctx[f].flags()
220 220 lfutil.writestandin(rdst, lfutil.standin(f), hash,
221 221 executable)
222 222 lfiletohash[f] = hash
223 223 else:
224 224 # normal file
225 225 dstfiles.append(f)
226 226
227 227 def getfilectx(repo, memctx, f):
228 228 if lfutil.isstandin(f):
229 229 # if the file isn't in the manifest then it was removed
230 230 # or renamed, raise IOError to indicate this
231 231 srcfname = lfutil.splitstandin(f)
232 232 try:
233 233 fctx = ctx.filectx(srcfname)
234 234 except error.LookupError:
235 235 return None
236 236 renamed = fctx.renamed()
237 237 if renamed:
238 238 # standin is always a largefile because largefile-ness
239 239 # doesn't change after rename or copy
240 240 renamed = lfutil.standin(renamed[0])
241 241
242 242 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
243 243 'l' in fctx.flags(), 'x' in fctx.flags(),
244 244 renamed)
245 245 else:
246 246 return _getnormalcontext(repo, ctx, f, revmap)
247 247
248 248 # Commit
249 249 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
250 250
251 251 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
252 252 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
253 253 getfilectx, ctx.user(), ctx.date(), ctx.extra())
254 254 ret = rdst.commitctx(mctx)
255 255 lfutil.copyalltostore(rdst, ret)
256 256 rdst.setparents(ret)
257 257 revmap[ctx.node()] = rdst.changelog.tip()
258 258
259 259 # Generate list of changed files
260 260 def _getchangedfiles(ctx, parents):
261 261 files = set(ctx.files())
262 262 if node.nullid not in parents:
263 263 mc = ctx.manifest()
264 264 mp1 = ctx.parents()[0].manifest()
265 265 mp2 = ctx.parents()[1].manifest()
266 266 files |= (set(mp1) | set(mp2)) - set(mc)
267 267 for f in mc:
268 268 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
269 269 files.add(f)
270 270 return files
271 271
272 272 # Convert src parents to dst parents
273 273 def _convertparents(ctx, revmap):
274 274 parents = []
275 275 for p in ctx.parents():
276 276 parents.append(revmap[p.node()])
277 277 while len(parents) < 2:
278 278 parents.append(node.nullid)
279 279 return parents
280 280
281 281 # Get memfilectx for a normal file
282 282 def _getnormalcontext(repo, ctx, f, revmap):
283 283 try:
284 284 fctx = ctx.filectx(f)
285 285 except error.LookupError:
286 286 return None
287 287 renamed = fctx.renamed()
288 288 if renamed:
289 289 renamed = renamed[0]
290 290
291 291 data = fctx.data()
292 292 if f == '.hgtags':
293 293 data = _converttags (repo.ui, revmap, data)
294 294 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
295 295 'x' in fctx.flags(), renamed)
296 296
297 297 # Remap tag data using a revision map
298 298 def _converttags(ui, revmap, data):
299 299 newdata = []
300 300 for line in data.splitlines():
301 301 try:
302 302 id, name = line.split(' ', 1)
303 303 except ValueError:
304 304 ui.warn(_('skipping incorrectly formatted tag %s\n')
305 305 % line)
306 306 continue
307 307 try:
308 308 newid = node.bin(id)
309 309 except TypeError:
310 310 ui.warn(_('skipping incorrectly formatted id %s\n')
311 311 % id)
312 312 continue
313 313 try:
314 314 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
315 315 name))
316 316 except KeyError:
317 317 ui.warn(_('no mapping for id %s\n') % id)
318 318 continue
319 319 return ''.join(newdata)
320 320
321 321 def _islfile(file, ctx, matcher, size):
322 322 '''Return true if file should be considered a largefile, i.e.
323 323 matcher matches it or it is larger than size.'''
324 324 # never store special .hg* files as largefiles
325 325 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
326 326 return False
327 327 if matcher and matcher(file):
328 328 return True
329 329 try:
330 330 return ctx.filectx(file).size() >= size * 1024 * 1024
331 331 except error.LookupError:
332 332 return False
333 333
334 334 def uploadlfiles(ui, rsrc, rdst, files):
335 335 '''upload largefiles to the central store'''
336 336
337 337 if not files:
338 338 return
339 339
340 340 store = basestore._openstore(rsrc, rdst, put=True)
341 341
342 342 at = 0
343 343 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
344 344 retval = store.exists(files)
345 345 files = filter(lambda h: not retval[h], files)
346 346 ui.debug("%d largefiles need to be uploaded\n" % len(files))
347 347
348 348 for hash in files:
349 349 ui.progress(_('uploading largefiles'), at, unit=_('files'),
350 350 total=len(files))
351 351 source = lfutil.findfile(rsrc, hash)
352 352 if not source:
353 353 raise error.Abort(_('largefile %s missing from store'
354 354 ' (needs to be uploaded)') % hash)
355 355 # XXX check for errors here
356 356 store.put(source, hash)
357 357 at += 1
358 358 ui.progress(_('uploading largefiles'), None)
359 359
360 360 def verifylfiles(ui, repo, all=False, contents=False):
361 361 '''Verify that every largefile revision in the current changeset
362 362 exists in the central store. With --contents, also verify that
363 363 the contents of each local largefile file revision are correct (SHA-1 hash
364 364 matches the revision ID). With --all, check every changeset in
365 365 this repository.'''
366 366 if all:
367 367 revs = repo.revs('all()')
368 368 else:
369 369 revs = ['.']
370 370
371 371 store = basestore._openstore(repo)
372 372 return store.verify(revs, contents=contents)
373 373
374 374 def cachelfiles(ui, repo, node, filelist=None):
375 375 '''cachelfiles ensures that all largefiles needed by the specified revision
376 376 are present in the repository's largefile cache.
377 377
378 378 returns a tuple (cached, missing). cached is the list of files downloaded
379 379 by this operation; missing is the list of files that were needed but could
380 380 not be found.'''
381 381 lfiles = lfutil.listlfiles(repo, node)
382 382 if filelist:
383 383 lfiles = set(lfiles) & set(filelist)
384 384 toget = []
385 385
386 386 for lfile in lfiles:
387 387 try:
388 388 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
389 389 except IOError as err:
390 390 if err.errno == errno.ENOENT:
391 391 continue # node must be None and standin wasn't found in wctx
392 392 raise
393 393 if not lfutil.findfile(repo, expectedhash):
394 394 toget.append((lfile, expectedhash))
395 395
396 396 if toget:
397 397 store = basestore._openstore(repo)
398 398 ret = store.get(toget)
399 399 return ret
400 400
401 401 return ([], [])
402 402
403 403 def downloadlfiles(ui, repo, rev=None):
404 404 matchfn = scmutil.match(repo[None],
405 405 [repo.wjoin(lfutil.shortname)], {})
406 406 def prepare(ctx, fns):
407 407 pass
408 408 totalsuccess = 0
409 409 totalmissing = 0
410 410 if rev != []: # walkchangerevs on empty list would return all revs
411 411 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
412 412 prepare):
413 413 success, missing = cachelfiles(ui, repo, ctx.node())
414 414 totalsuccess += len(success)
415 415 totalmissing += len(missing)
416 416 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
417 417 if totalmissing > 0:
418 418 ui.status(_("%d largefiles failed to download\n") % totalmissing)
419 419 return totalsuccess, totalmissing
420 420
421 421 def updatelfiles(ui, repo, filelist=None, printmessage=None,
422 422 normallookup=False):
423 423 '''Update largefiles according to standins in the working directory
424 424
425 425 If ``printmessage`` is other than ``None``, it means "print (or
426 426 ignore, for false) message forcibly".
427 427 '''
428 428 statuswriter = lfutil.getstatuswriter(ui, repo, printmessage)
429 429 with repo.wlock():
430 430 lfdirstate = lfutil.openlfdirstate(ui, repo)
431 431 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
432 432
433 433 if filelist is not None:
434 434 filelist = set(filelist)
435 435 lfiles = [f for f in lfiles if f in filelist]
436 436
437 437 update = {}
438 438 updated, removed = 0, 0
439 439 for lfile in lfiles:
440 440 abslfile = repo.wjoin(lfile)
441 441 abslfileorig = scmutil.origpath(ui, repo, abslfile)
442 442 absstandin = repo.wjoin(lfutil.standin(lfile))
443 443 absstandinorig = scmutil.origpath(ui, repo, absstandin)
444 444 if os.path.exists(absstandin):
445 445 if (os.path.exists(absstandinorig) and
446 446 os.path.exists(abslfile)):
447 447 shutil.copyfile(abslfile, abslfileorig)
448 448 util.unlinkpath(absstandinorig)
449 449 expecthash = lfutil.readstandin(repo, lfile)
450 450 if expecthash != '':
451 451 if lfile not in repo[None]: # not switched to normal file
452 452 util.unlinkpath(abslfile, ignoremissing=True)
453 453 # use normallookup() to allocate an entry in largefiles
454 454 # dirstate to prevent lfilesrepo.status() from reporting
455 455 # missing files as removed.
456 456 lfdirstate.normallookup(lfile)
457 457 update[lfile] = expecthash
458 458 else:
459 459 # Remove lfiles for which the standin is deleted, unless the
460 460 # lfile is added to the repository again. This happens when a
461 461 # largefile is converted back to a normal file: the standin
462 462 # disappears, but a new (normal) file appears as the lfile.
463 463 if (os.path.exists(abslfile) and
464 464 repo.dirstate.normalize(lfile) not in repo[None]):
465 465 util.unlinkpath(abslfile)
466 466 removed += 1
467 467
468 468 # largefile processing might be slow and be interrupted - be prepared
469 469 lfdirstate.write()
470 470
471 471 if lfiles:
472 472 statuswriter(_('getting changed largefiles\n'))
473 473 cachelfiles(ui, repo, None, lfiles)
474 474
475 475 for lfile in lfiles:
476 476 update1 = 0
477 477
478 478 expecthash = update.get(lfile)
479 479 if expecthash:
480 480 if not lfutil.copyfromcache(repo, expecthash, lfile):
481 481 # failed ... but already removed and set to normallookup
482 482 continue
483 483 # Synchronize largefile dirstate to the last modified
484 484 # time of the file
485 485 lfdirstate.normal(lfile)
486 486 update1 = 1
487 487
488 488 # copy the state of largefile standin from the repository's
489 489 # dirstate to its state in the lfdirstate.
490 490 abslfile = repo.wjoin(lfile)
491 491 absstandin = repo.wjoin(lfutil.standin(lfile))
492 492 if os.path.exists(absstandin):
493 493 mode = os.stat(absstandin).st_mode
494 494 if mode != os.stat(abslfile).st_mode:
495 495 os.chmod(abslfile, mode)
496 496 update1 = 1
497 497
498 498 updated += update1
499 499
500 500 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
501 501
502 502 lfdirstate.write()
503 503 if lfiles:
504 504 statuswriter(_('%d largefiles updated, %d removed\n') % (updated,
505 505 removed))
506 506
507 507 @command('lfpull',
508 508 [('r', 'rev', [], _('pull largefiles for these revisions'))
509 509 ] + commands.remoteopts,
510 510 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
511 511 def lfpull(ui, repo, source="default", **opts):
512 512 """pull largefiles for the specified revisions from the specified source
513 513
514 514 Pull largefiles that are referenced from local changesets but missing
515 515 locally, pulling from a remote repository to the local cache.
516 516
517 517 If SOURCE is omitted, the 'default' path will be used.
518 518 See :hg:`help urls` for more information.
519 519
520 520 .. container:: verbose
521 521
522 522 Some examples:
523 523
524 524 - pull largefiles for all branch heads::
525 525
526 526 hg lfpull -r "head() and not closed()"
527 527
528 528 - pull largefiles on the default branch::
529 529
530 530 hg lfpull -r "branch(default)"
531 531 """
532 532 repo.lfpullsource = source
533 533
534 534 revs = opts.get('rev', [])
535 535 if not revs:
536 536 raise error.Abort(_('no revisions specified'))
537 537 revs = scmutil.revrange(repo, revs)
538 538
539 539 numcached = 0
540 540 for rev in revs:
541 541 ui.note(_('pulling largefiles for revision %s\n') % rev)
542 542 (cached, missing) = cachelfiles(ui, repo, rev)
543 543 numcached += len(cached)
544 544 ui.status(_("%d largefiles cached\n") % numcached)
@@ -1,639 +1,639 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 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import os
12 12 import platform
13 13 import stat
14 14 import copy
15 15
16 16 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
17 17 from mercurial.i18n import _
18 18 from mercurial import node, error
19 19
20 20 shortname = '.hglf'
21 21 shortnameslash = shortname + '/'
22 22 longname = 'largefiles'
23 23
24 24
25 25 # -- Private worker functions ------------------------------------------
26 26
27 27 def getminsize(ui, assumelfiles, opt, default=10):
28 28 lfsize = opt
29 29 if not lfsize and assumelfiles:
30 30 lfsize = ui.config(longname, 'minsize', default=default)
31 31 if lfsize:
32 32 try:
33 33 lfsize = float(lfsize)
34 34 except ValueError:
35 35 raise error.Abort(_('largefiles: size must be number (not %s)\n')
36 36 % lfsize)
37 37 if lfsize is None:
38 38 raise error.Abort(_('minimum size for largefiles must be specified'))
39 39 return lfsize
40 40
41 41 def link(src, dest):
42 42 util.makedirs(os.path.dirname(dest))
43 43 try:
44 44 util.oslink(src, dest)
45 45 except OSError:
46 46 # if hardlinks fail, fallback on atomic copy
47 47 dst = util.atomictempfile(dest)
48 48 for chunk in util.filechunkiter(open(src, 'rb')):
49 49 dst.write(chunk)
50 50 dst.close()
51 51 os.chmod(dest, os.stat(src).st_mode)
52 52
53 53 def usercachepath(ui, hash):
54 54 path = ui.configpath(longname, 'usercache', None)
55 55 if path:
56 56 path = os.path.join(path, hash)
57 57 else:
58 58 if os.name == 'nt':
59 59 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
60 60 if appdata:
61 61 path = os.path.join(appdata, longname, hash)
62 62 elif platform.system() == 'Darwin':
63 63 home = os.getenv('HOME')
64 64 if home:
65 65 path = os.path.join(home, 'Library', 'Caches',
66 66 longname, hash)
67 67 elif os.name == 'posix':
68 68 path = os.getenv('XDG_CACHE_HOME')
69 69 if path:
70 70 path = os.path.join(path, longname, hash)
71 71 else:
72 72 home = os.getenv('HOME')
73 73 if home:
74 74 path = os.path.join(home, '.cache', longname, hash)
75 75 else:
76 76 raise error.Abort(_('unknown operating system: %s\n') % os.name)
77 77 return path
78 78
79 79 def inusercache(ui, hash):
80 80 path = usercachepath(ui, hash)
81 81 return path and os.path.exists(path)
82 82
83 83 def findfile(repo, hash):
84 84 path, exists = findstorepath(repo, hash)
85 85 if exists:
86 86 repo.ui.note(_('found %s in store\n') % hash)
87 87 return path
88 88 elif inusercache(repo.ui, hash):
89 89 repo.ui.note(_('found %s in system cache\n') % hash)
90 90 path = storepath(repo, hash)
91 91 link(usercachepath(repo.ui, hash), path)
92 92 return path
93 93 return None
94 94
95 95 class largefilesdirstate(dirstate.dirstate):
96 96 def __getitem__(self, key):
97 97 return super(largefilesdirstate, self).__getitem__(unixpath(key))
98 98 def normal(self, f):
99 99 return super(largefilesdirstate, self).normal(unixpath(f))
100 100 def remove(self, f):
101 101 return super(largefilesdirstate, self).remove(unixpath(f))
102 102 def add(self, f):
103 103 return super(largefilesdirstate, self).add(unixpath(f))
104 104 def drop(self, f):
105 105 return super(largefilesdirstate, self).drop(unixpath(f))
106 106 def forget(self, f):
107 107 return super(largefilesdirstate, self).forget(unixpath(f))
108 108 def normallookup(self, f):
109 109 return super(largefilesdirstate, self).normallookup(unixpath(f))
110 110 def _ignore(self, f):
111 111 return False
112 112 def write(self, tr=False):
113 113 # (1) disable PENDING mode always
114 114 # (lfdirstate isn't yet managed as a part of the transaction)
115 115 # (2) avoid develwarn 'use dirstate.write with ....'
116 116 super(largefilesdirstate, self).write(None)
117 117
118 118 def openlfdirstate(ui, repo, create=True):
119 119 '''
120 120 Return a dirstate object that tracks largefiles: i.e. its root is
121 121 the repo root, but it is saved in .hg/largefiles/dirstate.
122 122 '''
123 123 lfstoredir = repo.join(longname)
124 124 opener = scmutil.opener(lfstoredir)
125 125 lfdirstate = largefilesdirstate(opener, ui, repo.root,
126 126 repo.dirstate._validate)
127 127
128 128 # If the largefiles dirstate does not exist, populate and create
129 129 # it. This ensures that we create it on the first meaningful
130 130 # largefiles operation in a new clone.
131 131 if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')):
132 132 matcher = getstandinmatcher(repo)
133 133 standins = repo.dirstate.walk(matcher, [], False, False)
134 134
135 135 if len(standins) > 0:
136 136 util.makedirs(lfstoredir)
137 137
138 138 for standin in standins:
139 139 lfile = splitstandin(standin)
140 140 lfdirstate.normallookup(lfile)
141 141 return lfdirstate
142 142
143 143 def lfdirstatestatus(lfdirstate, repo):
144 144 wctx = repo['.']
145 145 match = match_.always(repo.root, repo.getcwd())
146 146 unsure, s = lfdirstate.status(match, [], False, False, False)
147 147 modified, clean = s.modified, s.clean
148 148 for lfile in unsure:
149 149 try:
150 150 fctx = wctx[standin(lfile)]
151 151 except LookupError:
152 152 fctx = None
153 153 if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
154 154 modified.append(lfile)
155 155 else:
156 156 clean.append(lfile)
157 157 lfdirstate.normal(lfile)
158 158 return s
159 159
160 160 def listlfiles(repo, rev=None, matcher=None):
161 161 '''return a list of largefiles in the working copy or the
162 162 specified changeset'''
163 163
164 164 if matcher is None:
165 165 matcher = getstandinmatcher(repo)
166 166
167 167 # ignore unknown files in working directory
168 168 return [splitstandin(f)
169 169 for f in repo[rev].walk(matcher)
170 170 if rev is not None or repo.dirstate[f] != '?']
171 171
172 172 def instore(repo, hash, forcelocal=False):
173 173 return os.path.exists(storepath(repo, hash, forcelocal))
174 174
175 175 def storepath(repo, hash, forcelocal=False):
176 176 if not forcelocal and repo.shared():
177 177 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
178 178 return repo.join(longname, hash)
179 179
180 180 def findstorepath(repo, hash):
181 181 '''Search through the local store path(s) to find the file for the given
182 182 hash. If the file is not found, its path in the primary store is returned.
183 183 The return value is a tuple of (path, exists(path)).
184 184 '''
185 185 # For shared repos, the primary store is in the share source. But for
186 186 # backward compatibility, force a lookup in the local store if it wasn't
187 187 # found in the share source.
188 188 path = storepath(repo, hash, False)
189 189
190 190 if instore(repo, hash):
191 191 return (path, True)
192 192 elif repo.shared() and instore(repo, hash, True):
193 193 return storepath(repo, hash, True)
194 194
195 195 return (path, False)
196 196
197 197 def copyfromcache(repo, hash, filename):
198 198 '''Copy the specified largefile from the repo or system cache to
199 199 filename in the repository. Return true on success or false if the
200 200 file was not found in either cache (which should not happened:
201 201 this is meant to be called only after ensuring that the needed
202 202 largefile exists in the cache).'''
203 203 path = findfile(repo, hash)
204 204 if path is None:
205 205 return False
206 206 util.makedirs(os.path.dirname(repo.wjoin(filename)))
207 207 # The write may fail before the file is fully written, but we
208 208 # don't use atomic writes in the working copy.
209 209 dest = repo.wjoin(filename)
210 210 with open(path, 'rb') as srcfd:
211 211 with open(dest, 'wb') as destfd:
212 212 gothash = copyandhash(srcfd, destfd)
213 213 if gothash != hash:
214 214 repo.ui.warn(_('%s: data corruption in %s with hash %s\n')
215 215 % (filename, path, gothash))
216 216 util.unlink(dest)
217 217 return False
218 218 return True
219 219
220 220 def copytostore(repo, rev, file, uploaded=False):
221 221 hash = readstandin(repo, file, rev)
222 222 if instore(repo, hash):
223 223 return
224 224 absfile = repo.wjoin(file)
225 225 if os.path.exists(absfile):
226 226 copytostoreabsolute(repo, absfile, hash)
227 227 else:
228 228 repo.ui.warn(_("%s: largefile %s not available from local store\n") %
229 229 (file, hash))
230 230
231 231 def copyalltostore(repo, node):
232 232 '''Copy all largefiles in a given revision to the store'''
233 233
234 234 ctx = repo[node]
235 235 for filename in ctx.files():
236 236 if isstandin(filename) and filename in ctx.manifest():
237 237 realfile = splitstandin(filename)
238 238 copytostore(repo, ctx.node(), realfile)
239 239
240 240
241 241 def copytostoreabsolute(repo, file, hash):
242 242 if inusercache(repo.ui, hash):
243 243 link(usercachepath(repo.ui, hash), storepath(repo, hash))
244 244 else:
245 245 util.makedirs(os.path.dirname(storepath(repo, hash)))
246 246 dst = util.atomictempfile(storepath(repo, hash),
247 247 createmode=repo.store.createmode)
248 248 for chunk in util.filechunkiter(open(file, 'rb')):
249 249 dst.write(chunk)
250 250 dst.close()
251 251 linktousercache(repo, hash)
252 252
253 253 def linktousercache(repo, hash):
254 254 path = usercachepath(repo.ui, hash)
255 255 if path:
256 256 link(storepath(repo, hash), path)
257 257
258 258 def getstandinmatcher(repo, rmatcher=None):
259 259 '''Return a match object that applies rmatcher to the standin directory'''
260 260 standindir = repo.wjoin(shortname)
261 261
262 262 # no warnings about missing files or directories
263 263 badfn = lambda f, msg: None
264 264
265 265 if rmatcher and not rmatcher.always():
266 266 pats = [os.path.join(standindir, pat) for pat in rmatcher.files()]
267 267 if not pats:
268 268 pats = [standindir]
269 269 match = scmutil.match(repo[None], pats, badfn=badfn)
270 270 # if pats is empty, it would incorrectly always match, so clear _always
271 271 match._always = False
272 272 else:
273 273 # no patterns: relative to repo root
274 274 match = scmutil.match(repo[None], [standindir], badfn=badfn)
275 275 return match
276 276
277 277 def composestandinmatcher(repo, rmatcher):
278 278 '''Return a matcher that accepts standins corresponding to the
279 279 files accepted by rmatcher. Pass the list of files in the matcher
280 280 as the paths specified by the user.'''
281 281 smatcher = getstandinmatcher(repo, rmatcher)
282 282 isstandin = smatcher.matchfn
283 283 def composedmatchfn(f):
284 284 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
285 285 smatcher.matchfn = composedmatchfn
286 286
287 287 return smatcher
288 288
289 289 def standin(filename):
290 290 '''Return the repo-relative path to the standin for the specified big
291 291 file.'''
292 292 # Notes:
293 293 # 1) Some callers want an absolute path, but for instance addlargefiles
294 294 # needs it repo-relative so it can be passed to repo[None].add(). So
295 295 # leave it up to the caller to use repo.wjoin() to get an absolute path.
296 296 # 2) Join with '/' because that's what dirstate always uses, even on
297 297 # Windows. Change existing separator to '/' first in case we are
298 298 # passed filenames from an external source (like the command line).
299 299 return shortnameslash + util.pconvert(filename)
300 300
301 301 def isstandin(filename):
302 302 '''Return true if filename is a big file standin. filename must be
303 303 in Mercurial's internal form (slash-separated).'''
304 304 return filename.startswith(shortnameslash)
305 305
306 306 def splitstandin(filename):
307 307 # Split on / because that's what dirstate always uses, even on Windows.
308 308 # Change local separator to / first just in case we are passed filenames
309 309 # from an external source (like the command line).
310 310 bits = util.pconvert(filename).split('/', 1)
311 311 if len(bits) == 2 and bits[0] == shortname:
312 312 return bits[1]
313 313 else:
314 314 return None
315 315
316 316 def updatestandin(repo, standin):
317 317 file = repo.wjoin(splitstandin(standin))
318 318 if os.path.exists(file):
319 319 hash = hashfile(file)
320 320 executable = getexecutable(file)
321 321 writestandin(repo, standin, hash, executable)
322 322 else:
323 323 raise error.Abort(_('%s: file not found!') % splitstandin(standin))
324 324
325 325 def readstandin(repo, filename, node=None):
326 326 '''read hex hash from standin for filename at given node, or working
327 327 directory if no node is given'''
328 328 return repo[node][standin(filename)].data().strip()
329 329
330 330 def writestandin(repo, standin, hash, executable):
331 331 '''write hash to <repo.root>/<standin>'''
332 332 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
333 333
334 334 def copyandhash(instream, outfile):
335 335 '''Read bytes from instream (iterable) and write them to outfile,
336 336 computing the SHA-1 hash of the data along the way. Return the hash.'''
337 337 hasher = util.sha1('')
338 338 for data in instream:
339 339 hasher.update(data)
340 340 outfile.write(data)
341 341 return hasher.hexdigest()
342 342
343 343 def hashrepofile(repo, file):
344 344 return hashfile(repo.wjoin(file))
345 345
346 346 def hashfile(file):
347 347 if not os.path.exists(file):
348 348 return ''
349 349 hasher = util.sha1('')
350 350 fd = open(file, 'rb')
351 351 for data in util.filechunkiter(fd, 128 * 1024):
352 352 hasher.update(data)
353 353 fd.close()
354 354 return hasher.hexdigest()
355 355
356 356 def getexecutable(filename):
357 357 mode = os.stat(filename).st_mode
358 358 return ((mode & stat.S_IXUSR) and
359 359 (mode & stat.S_IXGRP) and
360 360 (mode & stat.S_IXOTH))
361 361
362 362 def urljoin(first, second, *arg):
363 363 def join(left, right):
364 364 if not left.endswith('/'):
365 365 left += '/'
366 366 if right.startswith('/'):
367 367 right = right[1:]
368 368 return left + right
369 369
370 370 url = join(first, second)
371 371 for a in arg:
372 372 url = join(url, a)
373 373 return url
374 374
375 375 def hexsha1(data):
376 376 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
377 377 object data"""
378 378 h = util.sha1()
379 379 for chunk in util.filechunkiter(data):
380 380 h.update(chunk)
381 381 return h.hexdigest()
382 382
383 383 def httpsendfile(ui, filename):
384 384 return httpconnection.httpsendfile(ui, filename, 'rb')
385 385
386 386 def unixpath(path):
387 387 '''Return a version of path normalized for use with the lfdirstate.'''
388 388 return util.pconvert(os.path.normpath(path))
389 389
390 390 def islfilesrepo(repo):
391 391 if ('largefiles' in repo.requirements and
392 392 any(shortnameslash in f[0] for f in repo.store.datafiles())):
393 393 return True
394 394
395 395 return any(openlfdirstate(repo.ui, repo, False))
396 396
397 397 class storeprotonotcapable(Exception):
398 398 def __init__(self, storetypes):
399 399 self.storetypes = storetypes
400 400
401 401 def getstandinsstate(repo):
402 402 standins = []
403 403 matcher = getstandinmatcher(repo)
404 404 for standin in repo.dirstate.walk(matcher, [], False, False):
405 405 lfile = splitstandin(standin)
406 406 try:
407 407 hash = readstandin(repo, lfile)
408 408 except IOError:
409 409 hash = None
410 410 standins.append((lfile, hash))
411 411 return standins
412 412
413 413 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
414 414 lfstandin = standin(lfile)
415 415 if lfstandin in repo.dirstate:
416 416 stat = repo.dirstate._map[lfstandin]
417 417 state, mtime = stat[0], stat[3]
418 418 else:
419 419 state, mtime = '?', -1
420 420 if state == 'n':
421 421 if (normallookup or mtime < 0 or
422 422 not os.path.exists(repo.wjoin(lfile))):
423 423 # state 'n' doesn't ensure 'clean' in this case
424 424 lfdirstate.normallookup(lfile)
425 425 else:
426 426 lfdirstate.normal(lfile)
427 427 elif state == 'm':
428 428 lfdirstate.normallookup(lfile)
429 429 elif state == 'r':
430 430 lfdirstate.remove(lfile)
431 431 elif state == 'a':
432 432 lfdirstate.add(lfile)
433 433 elif state == '?':
434 434 lfdirstate.drop(lfile)
435 435
436 436 def markcommitted(orig, ctx, node):
437 437 repo = ctx.repo()
438 438
439 439 orig(node)
440 440
441 441 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
442 442 # because files coming from the 2nd parent are omitted in the latter.
443 443 #
444 444 # The former should be used to get targets of "synclfdirstate",
445 445 # because such files:
446 446 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
447 447 # - have to be marked as "n" after commit, but
448 448 # - aren't listed in "repo[node].files()"
449 449
450 450 lfdirstate = openlfdirstate(repo.ui, repo)
451 451 for f in ctx.files():
452 452 if isstandin(f):
453 453 lfile = splitstandin(f)
454 454 synclfdirstate(repo, lfdirstate, lfile, False)
455 455 lfdirstate.write()
456 456
457 457 # As part of committing, copy all of the largefiles into the cache.
458 458 copyalltostore(repo, node)
459 459
460 460 def getlfilestoupdate(oldstandins, newstandins):
461 461 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
462 462 filelist = []
463 463 for f in changedstandins:
464 464 if f[0] not in filelist:
465 465 filelist.append(f[0])
466 466 return filelist
467 467
468 468 def getlfilestoupload(repo, missing, addfunc):
469 469 for i, n in enumerate(missing):
470 470 repo.ui.progress(_('finding outgoing largefiles'), i,
471 unit=_('revision'), total=len(missing))
471 unit=_('revisions'), total=len(missing))
472 472 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
473 473
474 474 oldlfstatus = repo.lfstatus
475 475 repo.lfstatus = False
476 476 try:
477 477 ctx = repo[n]
478 478 finally:
479 479 repo.lfstatus = oldlfstatus
480 480
481 481 files = set(ctx.files())
482 482 if len(parents) == 2:
483 483 mc = ctx.manifest()
484 484 mp1 = ctx.parents()[0].manifest()
485 485 mp2 = ctx.parents()[1].manifest()
486 486 for f in mp1:
487 487 if f not in mc:
488 488 files.add(f)
489 489 for f in mp2:
490 490 if f not in mc:
491 491 files.add(f)
492 492 for f in mc:
493 493 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
494 494 files.add(f)
495 495 for fn in files:
496 496 if isstandin(fn) and fn in ctx:
497 497 addfunc(fn, ctx[fn].data().strip())
498 498 repo.ui.progress(_('finding outgoing largefiles'), None)
499 499
500 500 def updatestandinsbymatch(repo, match):
501 501 '''Update standins in the working directory according to specified match
502 502
503 503 This returns (possibly modified) ``match`` object to be used for
504 504 subsequent commit process.
505 505 '''
506 506
507 507 ui = repo.ui
508 508
509 509 # Case 1: user calls commit with no specific files or
510 510 # include/exclude patterns: refresh and commit all files that
511 511 # are "dirty".
512 512 if match is None or match.always():
513 513 # Spend a bit of time here to get a list of files we know
514 514 # are modified so we can compare only against those.
515 515 # It can cost a lot of time (several seconds)
516 516 # otherwise to update all standins if the largefiles are
517 517 # large.
518 518 lfdirstate = openlfdirstate(ui, repo)
519 519 dirtymatch = match_.always(repo.root, repo.getcwd())
520 520 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
521 521 False)
522 522 modifiedfiles = unsure + s.modified + s.added + s.removed
523 523 lfiles = listlfiles(repo)
524 524 # this only loops through largefiles that exist (not
525 525 # removed/renamed)
526 526 for lfile in lfiles:
527 527 if lfile in modifiedfiles:
528 528 if os.path.exists(
529 529 repo.wjoin(standin(lfile))):
530 530 # this handles the case where a rebase is being
531 531 # performed and the working copy is not updated
532 532 # yet.
533 533 if os.path.exists(repo.wjoin(lfile)):
534 534 updatestandin(repo,
535 535 standin(lfile))
536 536
537 537 return match
538 538
539 539 lfiles = listlfiles(repo)
540 540 match._files = repo._subdirlfs(match.files(), lfiles)
541 541
542 542 # Case 2: user calls commit with specified patterns: refresh
543 543 # any matching big files.
544 544 smatcher = composestandinmatcher(repo, match)
545 545 standins = repo.dirstate.walk(smatcher, [], False, False)
546 546
547 547 # No matching big files: get out of the way and pass control to
548 548 # the usual commit() method.
549 549 if not standins:
550 550 return match
551 551
552 552 # Refresh all matching big files. It's possible that the
553 553 # commit will end up failing, in which case the big files will
554 554 # stay refreshed. No harm done: the user modified them and
555 555 # asked to commit them, so sooner or later we're going to
556 556 # refresh the standins. Might as well leave them refreshed.
557 557 lfdirstate = openlfdirstate(ui, repo)
558 558 for fstandin in standins:
559 559 lfile = splitstandin(fstandin)
560 560 if lfdirstate[lfile] != 'r':
561 561 updatestandin(repo, fstandin)
562 562
563 563 # Cook up a new matcher that only matches regular files or
564 564 # standins corresponding to the big files requested by the
565 565 # user. Have to modify _files to prevent commit() from
566 566 # complaining "not tracked" for big files.
567 567 match = copy.copy(match)
568 568 origmatchfn = match.matchfn
569 569
570 570 # Check both the list of largefiles and the list of
571 571 # standins because if a largefile was removed, it
572 572 # won't be in the list of largefiles at this point
573 573 match._files += sorted(standins)
574 574
575 575 actualfiles = []
576 576 for f in match._files:
577 577 fstandin = standin(f)
578 578
579 579 # For largefiles, only one of the normal and standin should be
580 580 # committed (except if one of them is a remove). In the case of a
581 581 # standin removal, drop the normal file if it is unknown to dirstate.
582 582 # Thus, skip plain largefile names but keep the standin.
583 583 if f in lfiles or fstandin in standins:
584 584 if repo.dirstate[fstandin] != 'r':
585 585 if repo.dirstate[f] != 'r':
586 586 continue
587 587 elif repo.dirstate[f] == '?':
588 588 continue
589 589
590 590 actualfiles.append(f)
591 591 match._files = actualfiles
592 592
593 593 def matchfn(f):
594 594 if origmatchfn(f):
595 595 return f not in lfiles
596 596 else:
597 597 return f in standins
598 598
599 599 match.matchfn = matchfn
600 600
601 601 return match
602 602
603 603 class automatedcommithook(object):
604 604 '''Stateful hook to update standins at the 1st commit of resuming
605 605
606 606 For efficiency, updating standins in the working directory should
607 607 be avoided while automated committing (like rebase, transplant and
608 608 so on), because they should be updated before committing.
609 609
610 610 But the 1st commit of resuming automated committing (e.g. ``rebase
611 611 --continue``) should update them, because largefiles may be
612 612 modified manually.
613 613 '''
614 614 def __init__(self, resuming):
615 615 self.resuming = resuming
616 616
617 617 def __call__(self, repo, match):
618 618 if self.resuming:
619 619 self.resuming = False # avoids updating at subsequent commits
620 620 return updatestandinsbymatch(repo, match)
621 621 else:
622 622 return match
623 623
624 624 def getstatuswriter(ui, repo, forcibly=None):
625 625 '''Return the function to write largefiles specific status out
626 626
627 627 If ``forcibly`` is ``None``, this returns the last element of
628 628 ``repo._lfstatuswriters`` as "default" writer function.
629 629
630 630 Otherwise, this returns the function to always write out (or
631 631 ignore if ``not forcibly``) status.
632 632 '''
633 633 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
634 634 return repo._lfstatuswriters[-1]
635 635 else:
636 636 if forcibly:
637 637 return ui.status # forcibly WRITE OUT
638 638 else:
639 639 return lambda *msg, **opts: None # forcibly IGNORE
@@ -1,1101 +1,1101 b''
1 1 This file contains testcases that tend to be related to special cases or less
2 2 common commands affecting largefile.
3 3
4 4 Each sections should be independent of each others.
5 5
6 6 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
7 7 $ mkdir "${USERCACHE}"
8 8 $ cat >> $HGRCPATH <<EOF
9 9 > [extensions]
10 10 > largefiles=
11 11 > purge=
12 12 > rebase=
13 13 > transplant=
14 14 > [phases]
15 15 > publish=False
16 16 > [largefiles]
17 17 > minsize=2
18 18 > patterns=glob:**.dat
19 19 > usercache=${USERCACHE}
20 20 > [hooks]
21 21 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
22 22 > EOF
23 23
24 24
25 25
26 26 Test copies and moves from a directory other than root (issue3516)
27 27 =========================================================================
28 28
29 29 $ hg init lf_cpmv
30 30 $ cd lf_cpmv
31 31 $ mkdir dira
32 32 $ mkdir dira/dirb
33 33 $ touch dira/dirb/largefile
34 34 $ hg add --large dira/dirb/largefile
35 35 $ hg commit -m "added"
36 36 Invoking status precommit hook
37 37 A dira/dirb/largefile
38 38 $ cd dira
39 39 $ hg cp dirb/largefile foo/largefile
40 40
41 41 TODO: Ideally, this should mention the largefile, not the standin
42 42 $ hg log -T '{rev}\n' --stat 'set:clean()'
43 43 0
44 44 .hglf/dira/dirb/largefile | 1 +
45 45 1 files changed, 1 insertions(+), 0 deletions(-)
46 46
47 47 $ hg ci -m "deep copy"
48 48 Invoking status precommit hook
49 49 A dira/foo/largefile
50 50 $ find . | sort
51 51 .
52 52 ./dirb
53 53 ./dirb/largefile
54 54 ./foo
55 55 ./foo/largefile
56 56 $ hg mv foo/largefile baz/largefile
57 57 $ hg ci -m "moved"
58 58 Invoking status precommit hook
59 59 A dira/baz/largefile
60 60 R dira/foo/largefile
61 61 $ find . | sort
62 62 .
63 63 ./baz
64 64 ./baz/largefile
65 65 ./dirb
66 66 ./dirb/largefile
67 67 $ cd ..
68 68 $ hg mv dira dirc
69 69 moving .hglf/dira/baz/largefile to .hglf/dirc/baz/largefile (glob)
70 70 moving .hglf/dira/dirb/largefile to .hglf/dirc/dirb/largefile (glob)
71 71 $ find * | sort
72 72 dirc
73 73 dirc/baz
74 74 dirc/baz/largefile
75 75 dirc/dirb
76 76 dirc/dirb/largefile
77 77
78 78 $ hg clone -q . ../fetch
79 79 $ hg --config extensions.fetch= fetch ../fetch
80 80 abort: uncommitted changes
81 81 [255]
82 82 $ hg up -qC
83 83 $ cd ..
84 84
85 85 Clone a local repository owned by another user
86 86 ===================================================
87 87
88 88 #if unix-permissions
89 89
90 90 We have to simulate that here by setting $HOME and removing write permissions
91 91 $ ORIGHOME="$HOME"
92 92 $ mkdir alice
93 93 $ HOME="`pwd`/alice"
94 94 $ cd alice
95 95 $ hg init pubrepo
96 96 $ cd pubrepo
97 97 $ dd if=/dev/zero bs=1k count=11k > a-large-file 2> /dev/null
98 98 $ hg add --large a-large-file
99 99 $ hg commit -m "Add a large file"
100 100 Invoking status precommit hook
101 101 A a-large-file
102 102 $ cd ..
103 103 $ chmod -R a-w pubrepo
104 104 $ cd ..
105 105 $ mkdir bob
106 106 $ HOME="`pwd`/bob"
107 107 $ cd bob
108 108 $ hg clone --pull ../alice/pubrepo pubrepo
109 109 requesting all changes
110 110 adding changesets
111 111 adding manifests
112 112 adding file changes
113 113 added 1 changesets with 1 changes to 1 files
114 114 updating to branch default
115 115 getting changed largefiles
116 116 1 largefiles updated, 0 removed
117 117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118 $ cd ..
119 119 $ chmod -R u+w alice/pubrepo
120 120 $ HOME="$ORIGHOME"
121 121
122 122 #endif
123 123
124 124
125 125 Symlink to a large largefile should behave the same as a symlink to a normal file
126 126 =====================================================================================
127 127
128 128 #if symlink
129 129
130 130 $ hg init largesymlink
131 131 $ cd largesymlink
132 132 $ dd if=/dev/zero bs=1k count=10k of=largefile 2>/dev/null
133 133 $ hg add --large largefile
134 134 $ hg commit -m "commit a large file"
135 135 Invoking status precommit hook
136 136 A largefile
137 137 $ ln -s largefile largelink
138 138 $ hg add largelink
139 139 $ hg commit -m "commit a large symlink"
140 140 Invoking status precommit hook
141 141 A largelink
142 142 $ rm -f largelink
143 143 $ hg up >/dev/null
144 144 $ test -f largelink
145 145 [1]
146 146 $ test -L largelink
147 147 [1]
148 148 $ rm -f largelink # make next part of the test independent of the previous
149 149 $ hg up -C >/dev/null
150 150 $ test -f largelink
151 151 $ test -L largelink
152 152 $ cd ..
153 153
154 154 #endif
155 155
156 156
157 157 test for pattern matching on 'hg status':
158 158 ==============================================
159 159
160 160
161 161 to boost performance, largefiles checks whether specified patterns are
162 162 related to largefiles in working directory (NOT to STANDIN) or not.
163 163
164 164 $ hg init statusmatch
165 165 $ cd statusmatch
166 166
167 167 $ mkdir -p a/b/c/d
168 168 $ echo normal > a/b/c/d/e.normal.txt
169 169 $ hg add a/b/c/d/e.normal.txt
170 170 $ echo large > a/b/c/d/e.large.txt
171 171 $ hg add --large a/b/c/d/e.large.txt
172 172 $ mkdir -p a/b/c/x
173 173 $ echo normal > a/b/c/x/y.normal.txt
174 174 $ hg add a/b/c/x/y.normal.txt
175 175 $ hg commit -m 'add files'
176 176 Invoking status precommit hook
177 177 A a/b/c/d/e.large.txt
178 178 A a/b/c/d/e.normal.txt
179 179 A a/b/c/x/y.normal.txt
180 180
181 181 (1) no pattern: no performance boost
182 182 $ hg status -A
183 183 C a/b/c/d/e.large.txt
184 184 C a/b/c/d/e.normal.txt
185 185 C a/b/c/x/y.normal.txt
186 186
187 187 (2) pattern not related to largefiles: performance boost
188 188 $ hg status -A a/b/c/x
189 189 C a/b/c/x/y.normal.txt
190 190
191 191 (3) pattern related to largefiles: no performance boost
192 192 $ hg status -A a/b/c/d
193 193 C a/b/c/d/e.large.txt
194 194 C a/b/c/d/e.normal.txt
195 195
196 196 (4) pattern related to STANDIN (not to largefiles): performance boost
197 197 $ hg status -A .hglf/a
198 198 C .hglf/a/b/c/d/e.large.txt
199 199
200 200 (5) mixed case: no performance boost
201 201 $ hg status -A a/b/c/x a/b/c/d
202 202 C a/b/c/d/e.large.txt
203 203 C a/b/c/d/e.normal.txt
204 204 C a/b/c/x/y.normal.txt
205 205
206 206 verify that largefiles doesn't break filesets
207 207
208 208 $ hg log --rev . --exclude "set:binary()"
209 209 changeset: 0:41bd42f10efa
210 210 tag: tip
211 211 user: test
212 212 date: Thu Jan 01 00:00:00 1970 +0000
213 213 summary: add files
214 214
215 215 verify that large files in subrepos handled properly
216 216 $ hg init subrepo
217 217 $ echo "subrepo = subrepo" > .hgsub
218 218 $ hg add .hgsub
219 219 $ hg ci -m "add subrepo"
220 220 Invoking status precommit hook
221 221 A .hgsub
222 222 ? .hgsubstate
223 223 $ echo "rev 1" > subrepo/large.txt
224 224 $ hg add --large subrepo/large.txt
225 225 $ hg sum
226 226 parent: 1:8ee150ea2e9c tip
227 227 add subrepo
228 228 branch: default
229 229 commit: 1 subrepos
230 230 update: (current)
231 231 phases: 2 draft
232 232 $ hg st
233 233 $ hg st -S
234 234 A subrepo/large.txt
235 235 $ hg ci -S -m "commit top repo"
236 236 committing subrepository subrepo
237 237 Invoking status precommit hook
238 238 A large.txt
239 239 Invoking status precommit hook
240 240 M .hgsubstate
241 241 # No differences
242 242 $ hg st -S
243 243 $ hg sum
244 244 parent: 2:ce4cd0c527a6 tip
245 245 commit top repo
246 246 branch: default
247 247 commit: (clean)
248 248 update: (current)
249 249 phases: 3 draft
250 250 $ echo "rev 2" > subrepo/large.txt
251 251 $ hg st -S
252 252 M subrepo/large.txt
253 253 $ hg sum
254 254 parent: 2:ce4cd0c527a6 tip
255 255 commit top repo
256 256 branch: default
257 257 commit: 1 subrepos
258 258 update: (current)
259 259 phases: 3 draft
260 260 $ hg ci -m "this commit should fail without -S"
261 261 abort: uncommitted changes in subrepository 'subrepo'
262 262 (use --subrepos for recursive commit)
263 263 [255]
264 264
265 265 Add a normal file to the subrepo, then test archiving
266 266
267 267 $ echo 'normal file' > subrepo/normal.txt
268 268 $ touch large.dat
269 269 $ mv subrepo/large.txt subrepo/renamed-large.txt
270 270 $ hg addremove -S --dry-run
271 271 adding large.dat as a largefile
272 272 removing subrepo/large.txt
273 273 adding subrepo/normal.txt
274 274 adding subrepo/renamed-large.txt
275 275 $ hg status -S
276 276 ! subrepo/large.txt
277 277 ? large.dat
278 278 ? subrepo/normal.txt
279 279 ? subrepo/renamed-large.txt
280 280
281 281 $ hg addremove --dry-run subrepo
282 282 removing subrepo/large.txt (glob)
283 283 adding subrepo/normal.txt (glob)
284 284 adding subrepo/renamed-large.txt (glob)
285 285 $ hg status -S
286 286 ! subrepo/large.txt
287 287 ? large.dat
288 288 ? subrepo/normal.txt
289 289 ? subrepo/renamed-large.txt
290 290 $ cd ..
291 291
292 292 $ hg -R statusmatch addremove --dry-run statusmatch/subrepo
293 293 removing statusmatch/subrepo/large.txt (glob)
294 294 adding statusmatch/subrepo/normal.txt (glob)
295 295 adding statusmatch/subrepo/renamed-large.txt (glob)
296 296 $ hg -R statusmatch status -S
297 297 ! subrepo/large.txt
298 298 ? large.dat
299 299 ? subrepo/normal.txt
300 300 ? subrepo/renamed-large.txt
301 301
302 302 $ hg -R statusmatch addremove --dry-run -S
303 303 adding large.dat as a largefile
304 304 removing subrepo/large.txt
305 305 adding subrepo/normal.txt
306 306 adding subrepo/renamed-large.txt
307 307 $ cd statusmatch
308 308
309 309 $ mv subrepo/renamed-large.txt subrepo/large.txt
310 310 $ hg addremove subrepo
311 311 adding subrepo/normal.txt (glob)
312 312 $ hg forget subrepo/normal.txt
313 313
314 314 $ hg addremove -S
315 315 adding large.dat as a largefile
316 316 adding subrepo/normal.txt
317 317 $ rm large.dat
318 318
319 319 $ hg addremove subrepo
320 320 $ hg addremove -S
321 321 removing large.dat
322 322
323 323 Lock in subrepo, otherwise the change isn't archived
324 324
325 325 $ hg ci -S -m "add normal file to top level"
326 326 committing subrepository subrepo
327 327 Invoking status precommit hook
328 328 M large.txt
329 329 A normal.txt
330 330 Invoking status precommit hook
331 331 M .hgsubstate
332 332 $ hg archive -S ../lf_subrepo_archive
333 333 $ find ../lf_subrepo_archive | sort
334 334 ../lf_subrepo_archive
335 335 ../lf_subrepo_archive/.hg_archival.txt
336 336 ../lf_subrepo_archive/.hgsub
337 337 ../lf_subrepo_archive/.hgsubstate
338 338 ../lf_subrepo_archive/a
339 339 ../lf_subrepo_archive/a/b
340 340 ../lf_subrepo_archive/a/b/c
341 341 ../lf_subrepo_archive/a/b/c/d
342 342 ../lf_subrepo_archive/a/b/c/d/e.large.txt
343 343 ../lf_subrepo_archive/a/b/c/d/e.normal.txt
344 344 ../lf_subrepo_archive/a/b/c/x
345 345 ../lf_subrepo_archive/a/b/c/x/y.normal.txt
346 346 ../lf_subrepo_archive/subrepo
347 347 ../lf_subrepo_archive/subrepo/large.txt
348 348 ../lf_subrepo_archive/subrepo/normal.txt
349 349 $ cat ../lf_subrepo_archive/.hg_archival.txt
350 350 repo: 41bd42f10efa43698cc02052ea0977771cba506d
351 351 node: d56a95e6522858bc08a724c4fe2bdee066d1c30b
352 352 branch: default
353 353 latesttag: null
354 354 latesttagdistance: 4
355 355 changessincelatesttag: 4
356 356
357 357 Test update with subrepos.
358 358
359 359 $ hg update 0
360 360 getting changed largefiles
361 361 0 largefiles updated, 1 removed
362 362 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
363 363 $ hg status -S
364 364 $ hg update tip
365 365 getting changed largefiles
366 366 1 largefiles updated, 0 removed
367 367 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
368 368 $ hg status -S
369 369 # modify a large file
370 370 $ echo "modified" > subrepo/large.txt
371 371 $ hg st -S
372 372 M subrepo/large.txt
373 373 # update -C should revert the change.
374 374 $ hg update -C
375 375 getting changed largefiles
376 376 1 largefiles updated, 0 removed
377 377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 378 $ hg status -S
379 379
380 380 $ hg forget -v subrepo/large.txt
381 381 removing subrepo/large.txt (glob)
382 382
383 383 Test reverting a forgotten file
384 384 $ hg revert -R subrepo subrepo/large.txt
385 385 $ hg status -SA subrepo/large.txt
386 386 C subrepo/large.txt
387 387
388 388 $ hg rm -v subrepo/large.txt
389 389 removing subrepo/large.txt (glob)
390 390 $ hg revert -R subrepo subrepo/large.txt
391 391 $ rm subrepo/large.txt
392 392 $ hg addremove -S
393 393 removing subrepo/large.txt
394 394 $ hg st -S
395 395 R subrepo/large.txt
396 396
397 397 Test archiving a revision that references a subrepo that is not yet
398 398 cloned (see test-subrepo-recursion.t):
399 399
400 400 $ hg clone -U . ../empty
401 401 $ cd ../empty
402 402 $ hg archive --subrepos -r tip ../archive.tar.gz
403 403 cloning subrepo subrepo from $TESTTMP/statusmatch/subrepo
404 404 $ cd ..
405 405
406 406
407 407
408 408
409 409
410 410
411 411 Test addremove, forget and others
412 412 ==============================================
413 413
414 414 Test that addremove picks up largefiles prior to the initial commit (issue3541)
415 415
416 416 $ hg init addrm2
417 417 $ cd addrm2
418 418 $ touch large.dat
419 419 $ touch large2.dat
420 420 $ touch normal
421 421 $ hg add --large large.dat
422 422 $ hg addremove -v
423 423 adding large2.dat as a largefile
424 424 adding normal
425 425
426 426 Test that forgetting all largefiles reverts to islfilesrepo() == False
427 427 (addremove will add *.dat as normal files now)
428 428 $ hg forget large.dat
429 429 $ hg forget large2.dat
430 430 $ hg addremove -v
431 431 adding large.dat
432 432 adding large2.dat
433 433
434 434 Test commit's addremove option prior to the first commit
435 435 $ hg forget large.dat
436 436 $ hg forget large2.dat
437 437 $ hg add --large large.dat
438 438 $ hg ci -Am "commit"
439 439 adding large2.dat as a largefile
440 440 Invoking status precommit hook
441 441 A large.dat
442 442 A large2.dat
443 443 A normal
444 444 $ find .hglf | sort
445 445 .hglf
446 446 .hglf/large.dat
447 447 .hglf/large2.dat
448 448
449 449 Test actions on largefiles using relative paths from subdir
450 450
451 451 $ mkdir sub
452 452 $ cd sub
453 453 $ echo anotherlarge > anotherlarge
454 454 $ hg add --large anotherlarge
455 455 $ hg st
456 456 A sub/anotherlarge
457 457 $ hg st anotherlarge
458 458 A anotherlarge
459 459 $ hg commit -m anotherlarge anotherlarge
460 460 Invoking status precommit hook
461 461 A sub/anotherlarge
462 462 $ hg log anotherlarge
463 463 changeset: 1:9627a577c5e9
464 464 tag: tip
465 465 user: test
466 466 date: Thu Jan 01 00:00:00 1970 +0000
467 467 summary: anotherlarge
468 468
469 469 $ hg --debug log -T '{rev}: {desc}\n' ../sub/anotherlarge
470 470 updated patterns: ['../.hglf/sub/../sub/anotherlarge', '../sub/anotherlarge']
471 471 1: anotherlarge
472 472
473 473 $ hg log -G anotherlarge
474 474 @ changeset: 1:9627a577c5e9
475 475 | tag: tip
476 476 | user: test
477 477 | date: Thu Jan 01 00:00:00 1970 +0000
478 478 | summary: anotherlarge
479 479 |
480 480
481 481 $ hg log glob:another*
482 482 changeset: 1:9627a577c5e9
483 483 tag: tip
484 484 user: test
485 485 date: Thu Jan 01 00:00:00 1970 +0000
486 486 summary: anotherlarge
487 487
488 488 $ hg --debug log -T '{rev}: {desc}\n' -G glob:another*
489 489 updated patterns: ['glob:../.hglf/sub/another*', 'glob:another*']
490 490 @ 1: anotherlarge
491 491 |
492 492
493 493 #if no-msys
494 494 $ hg --debug log -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
495 495 updated patterns: ['glob:../.hglf/sub/another*']
496 496 1: anotherlarge
497 497
498 498 $ hg --debug log -G -T '{rev}: {desc}\n' 'glob:../.hglf/sub/another*' # no-msys
499 499 updated patterns: ['glob:../.hglf/sub/another*']
500 500 @ 1: anotherlarge
501 501 |
502 502 #endif
503 503
504 504 $ echo more >> anotherlarge
505 505 $ hg st .
506 506 M anotherlarge
507 507 $ hg cat anotherlarge
508 508 anotherlarge
509 509 $ hg revert anotherlarge
510 510 $ hg st
511 511 ? sub/anotherlarge.orig
512 512
513 513 Test orig files go where we want them
514 514 $ echo moremore >> anotherlarge
515 515 $ hg revert anotherlarge -v --config 'ui.origbackuppath=.hg/origbackups'
516 516 creating directory: $TESTTMP/addrm2/.hg/origbackups/.hglf/sub (glob)
517 517 saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge.orig (glob)
518 518 reverting ../.hglf/sub/anotherlarge (glob)
519 519 creating directory: $TESTTMP/addrm2/.hg/origbackups/sub (glob)
520 520 found 90c622cf65cebe75c5842f9136c459333faf392e in store
521 521 found 90c622cf65cebe75c5842f9136c459333faf392e in store
522 522 $ ls ../.hg/origbackups/sub
523 523 anotherlarge.orig
524 524 $ cd ..
525 525
526 526 Test glob logging from the root dir
527 527 $ hg log glob:**another*
528 528 changeset: 1:9627a577c5e9
529 529 tag: tip
530 530 user: test
531 531 date: Thu Jan 01 00:00:00 1970 +0000
532 532 summary: anotherlarge
533 533
534 534 $ hg log -G glob:**another*
535 535 @ changeset: 1:9627a577c5e9
536 536 | tag: tip
537 537 | user: test
538 538 | date: Thu Jan 01 00:00:00 1970 +0000
539 539 | summary: anotherlarge
540 540 |
541 541
542 542 $ cd ..
543 543
544 544 Log from outer space
545 545 $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/sub/anotherlarge'
546 546 updated patterns: ['addrm2/.hglf/sub/anotherlarge', 'addrm2/sub/anotherlarge']
547 547 1: anotherlarge
548 548 $ hg --debug log -R addrm2 -T '{rev}: {desc}\n' 'addrm2/.hglf/sub/anotherlarge'
549 549 updated patterns: ['addrm2/.hglf/sub/anotherlarge']
550 550 1: anotherlarge
551 551
552 552
553 553 Check error message while exchange
554 554 =========================================================
555 555
556 556 issue3651: summary/outgoing with largefiles shows "no remote repo"
557 557 unexpectedly
558 558
559 559 $ mkdir issue3651
560 560 $ cd issue3651
561 561
562 562 $ hg init src
563 563 $ echo a > src/a
564 564 $ hg -R src add --large src/a
565 565 $ hg -R src commit -m '#0'
566 566 Invoking status precommit hook
567 567 A a
568 568
569 569 check messages when no remote repository is specified:
570 570 "no remote repo" route for "hg outgoing --large" is not tested here,
571 571 because it can't be reproduced easily.
572 572
573 573 $ hg init clone1
574 574 $ hg -R clone1 -q pull src
575 575 $ hg -R clone1 -q update
576 576 $ hg -R clone1 paths | grep default
577 577 [1]
578 578
579 579 $ hg -R clone1 summary --large
580 580 parent: 0:fc0bd45326d3 tip
581 581 #0
582 582 branch: default
583 583 commit: (clean)
584 584 update: (current)
585 585 phases: 1 draft
586 586 largefiles: (no remote repo)
587 587
588 588 check messages when there is no files to upload:
589 589
590 590 $ hg -q clone src clone2
591 591 $ hg -R clone2 paths | grep default
592 592 default = $TESTTMP/issue3651/src (glob)
593 593
594 594 $ hg -R clone2 summary --large
595 595 parent: 0:fc0bd45326d3 tip
596 596 #0
597 597 branch: default
598 598 commit: (clean)
599 599 update: (current)
600 600 phases: 1 draft
601 601 largefiles: (no files to upload)
602 602 $ hg -R clone2 outgoing --large
603 603 comparing with $TESTTMP/issue3651/src (glob)
604 604 searching for changes
605 605 no changes found
606 606 largefiles: no files to upload
607 607 [1]
608 608
609 609 $ hg -R clone2 outgoing --large --graph --template "{rev}"
610 610 comparing with $TESTTMP/issue3651/src (glob)
611 611 searching for changes
612 612 no changes found
613 613 largefiles: no files to upload
614 614
615 615 check messages when there are files to upload:
616 616
617 617 $ echo b > clone2/b
618 618 $ hg -R clone2 add --large clone2/b
619 619 $ hg -R clone2 commit -m '#1'
620 620 Invoking status precommit hook
621 621 A b
622 622 $ hg -R clone2 summary --large
623 623 parent: 1:1acbe71ce432 tip
624 624 #1
625 625 branch: default
626 626 commit: (clean)
627 627 update: (current)
628 628 phases: 2 draft
629 629 largefiles: 1 entities for 1 files to upload
630 630 $ hg -R clone2 outgoing --large
631 631 comparing with $TESTTMP/issue3651/src (glob)
632 632 searching for changes
633 633 changeset: 1:1acbe71ce432
634 634 tag: tip
635 635 user: test
636 636 date: Thu Jan 01 00:00:00 1970 +0000
637 637 summary: #1
638 638
639 639 largefiles to upload (1 entities):
640 640 b
641 641
642 642 $ hg -R clone2 outgoing --large --graph --template "{rev}"
643 643 comparing with $TESTTMP/issue3651/src (glob)
644 644 searching for changes
645 645 @ 1
646 646
647 647 largefiles to upload (1 entities):
648 648 b
649 649
650 650
651 651 $ cp clone2/b clone2/b1
652 652 $ cp clone2/b clone2/b2
653 653 $ hg -R clone2 add --large clone2/b1 clone2/b2
654 654 $ hg -R clone2 commit -m '#2: add largefiles referring same entity'
655 655 Invoking status precommit hook
656 656 A b1
657 657 A b2
658 658 $ hg -R clone2 summary --large
659 659 parent: 2:6095d0695d70 tip
660 660 #2: add largefiles referring same entity
661 661 branch: default
662 662 commit: (clean)
663 663 update: (current)
664 664 phases: 3 draft
665 665 largefiles: 1 entities for 3 files to upload
666 666 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
667 667 comparing with $TESTTMP/issue3651/src (glob)
668 668 searching for changes
669 669 1:1acbe71ce432
670 670 2:6095d0695d70
671 671 largefiles to upload (1 entities):
672 672 b
673 673 b1
674 674 b2
675 675
676 676 $ hg -R clone2 cat -r 1 clone2/.hglf/b
677 677 89e6c98d92887913cadf06b2adb97f26cde4849b
678 678 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
679 679 comparing with $TESTTMP/issue3651/src (glob)
680 680 query 1; heads
681 681 searching for changes
682 682 all remote heads known locally
683 683 1:1acbe71ce432
684 684 2:6095d0695d70
685 finding outgoing largefiles: 0/2 revision (0.00%)
686 finding outgoing largefiles: 1/2 revision (50.00%)
685 finding outgoing largefiles: 0/2 revisions (0.00%)
686 finding outgoing largefiles: 1/2 revisions (50.00%)
687 687 largefiles to upload (1 entities):
688 688 b
689 689 89e6c98d92887913cadf06b2adb97f26cde4849b
690 690 b1
691 691 89e6c98d92887913cadf06b2adb97f26cde4849b
692 692 b2
693 693 89e6c98d92887913cadf06b2adb97f26cde4849b
694 694
695 695
696 696 $ echo bbb > clone2/b
697 697 $ hg -R clone2 commit -m '#3: add new largefile entity as existing file'
698 698 Invoking status precommit hook
699 699 M b
700 700 $ echo bbbb > clone2/b
701 701 $ hg -R clone2 commit -m '#4: add new largefile entity as existing file'
702 702 Invoking status precommit hook
703 703 M b
704 704 $ cp clone2/b1 clone2/b
705 705 $ hg -R clone2 commit -m '#5: refer existing largefile entity again'
706 706 Invoking status precommit hook
707 707 M b
708 708 $ hg -R clone2 summary --large
709 709 parent: 5:036794ea641c tip
710 710 #5: refer existing largefile entity again
711 711 branch: default
712 712 commit: (clean)
713 713 update: (current)
714 714 phases: 6 draft
715 715 largefiles: 3 entities for 3 files to upload
716 716 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
717 717 comparing with $TESTTMP/issue3651/src (glob)
718 718 searching for changes
719 719 1:1acbe71ce432
720 720 2:6095d0695d70
721 721 3:7983dce246cc
722 722 4:233f12ada4ae
723 723 5:036794ea641c
724 724 largefiles to upload (3 entities):
725 725 b
726 726 b1
727 727 b2
728 728
729 729 $ hg -R clone2 cat -r 3 clone2/.hglf/b
730 730 c801c9cfe94400963fcb683246217d5db77f9a9a
731 731 $ hg -R clone2 cat -r 4 clone2/.hglf/b
732 732 13f9ed0898e315bf59dc2973fec52037b6f441a2
733 733 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
734 734 comparing with $TESTTMP/issue3651/src (glob)
735 735 query 1; heads
736 736 searching for changes
737 737 all remote heads known locally
738 738 1:1acbe71ce432
739 739 2:6095d0695d70
740 740 3:7983dce246cc
741 741 4:233f12ada4ae
742 742 5:036794ea641c
743 finding outgoing largefiles: 0/5 revision (0.00%)
744 finding outgoing largefiles: 1/5 revision (20.00%)
745 finding outgoing largefiles: 2/5 revision (40.00%)
746 finding outgoing largefiles: 3/5 revision (60.00%)
747 finding outgoing largefiles: 4/5 revision (80.00%)
743 finding outgoing largefiles: 0/5 revisions (0.00%)
744 finding outgoing largefiles: 1/5 revisions (20.00%)
745 finding outgoing largefiles: 2/5 revisions (40.00%)
746 finding outgoing largefiles: 3/5 revisions (60.00%)
747 finding outgoing largefiles: 4/5 revisions (80.00%)
748 748 largefiles to upload (3 entities):
749 749 b
750 750 13f9ed0898e315bf59dc2973fec52037b6f441a2
751 751 89e6c98d92887913cadf06b2adb97f26cde4849b
752 752 c801c9cfe94400963fcb683246217d5db77f9a9a
753 753 b1
754 754 89e6c98d92887913cadf06b2adb97f26cde4849b
755 755 b2
756 756 89e6c98d92887913cadf06b2adb97f26cde4849b
757 757
758 758
759 759 Pushing revision #1 causes uploading entity 89e6c98d9288, which is
760 760 shared also by largefiles b1, b2 in revision #2 and b in revision #5.
761 761
762 762 Then, entity 89e6c98d9288 is not treated as "outgoing entity" at "hg
763 763 summary" and "hg outgoing", even though files in outgoing revision #2
764 764 and #5 refer it.
765 765
766 766 $ hg -R clone2 push -r 1 -q
767 767 $ hg -R clone2 summary --large
768 768 parent: 5:036794ea641c tip
769 769 #5: refer existing largefile entity again
770 770 branch: default
771 771 commit: (clean)
772 772 update: (current)
773 773 phases: 6 draft
774 774 largefiles: 2 entities for 1 files to upload
775 775 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
776 776 comparing with $TESTTMP/issue3651/src (glob)
777 777 searching for changes
778 778 2:6095d0695d70
779 779 3:7983dce246cc
780 780 4:233f12ada4ae
781 781 5:036794ea641c
782 782 largefiles to upload (2 entities):
783 783 b
784 784
785 785 $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
786 786 comparing with $TESTTMP/issue3651/src (glob)
787 787 query 1; heads
788 788 searching for changes
789 789 all remote heads known locally
790 790 2:6095d0695d70
791 791 3:7983dce246cc
792 792 4:233f12ada4ae
793 793 5:036794ea641c
794 finding outgoing largefiles: 0/4 revision (0.00%)
795 finding outgoing largefiles: 1/4 revision (25.00%)
796 finding outgoing largefiles: 2/4 revision (50.00%)
797 finding outgoing largefiles: 3/4 revision (75.00%)
794 finding outgoing largefiles: 0/4 revisions (0.00%)
795 finding outgoing largefiles: 1/4 revisions (25.00%)
796 finding outgoing largefiles: 2/4 revisions (50.00%)
797 finding outgoing largefiles: 3/4 revisions (75.00%)
798 798 largefiles to upload (2 entities):
799 799 b
800 800 13f9ed0898e315bf59dc2973fec52037b6f441a2
801 801 c801c9cfe94400963fcb683246217d5db77f9a9a
802 802
803 803
804 804 $ cd ..
805 805
806 806 merge action 'd' for 'local renamed directory to d2/g' which has no filename
807 807 ==================================================================================
808 808
809 809 $ hg init merge-action
810 810 $ cd merge-action
811 811 $ touch l
812 812 $ hg add --large l
813 813 $ mkdir d1
814 814 $ touch d1/f
815 815 $ hg ci -Aqm0
816 816 Invoking status precommit hook
817 817 A d1/f
818 818 A l
819 819 $ echo > d1/f
820 820 $ touch d1/g
821 821 $ hg ci -Aqm1
822 822 Invoking status precommit hook
823 823 M d1/f
824 824 A d1/g
825 825 $ hg up -qr0
826 826 $ hg mv d1 d2
827 827 moving d1/f to d2/f (glob)
828 828 $ hg ci -qm2
829 829 Invoking status precommit hook
830 830 A d2/f
831 831 R d1/f
832 832 $ hg merge
833 833 merging d2/f and d1/f to d2/f
834 834 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
835 835 (branch merge, don't forget to commit)
836 836 $ cd ..
837 837
838 838
839 839 Merge conflicts:
840 840 =====================
841 841
842 842 $ hg init merge
843 843 $ cd merge
844 844 $ echo 0 > f-different
845 845 $ echo 0 > f-same
846 846 $ echo 0 > f-unchanged-1
847 847 $ echo 0 > f-unchanged-2
848 848 $ hg add --large *
849 849 $ hg ci -m0
850 850 Invoking status precommit hook
851 851 A f-different
852 852 A f-same
853 853 A f-unchanged-1
854 854 A f-unchanged-2
855 855 $ echo tmp1 > f-unchanged-1
856 856 $ echo tmp1 > f-unchanged-2
857 857 $ echo tmp1 > f-same
858 858 $ hg ci -m1
859 859 Invoking status precommit hook
860 860 M f-same
861 861 M f-unchanged-1
862 862 M f-unchanged-2
863 863 $ echo 2 > f-different
864 864 $ echo 0 > f-unchanged-1
865 865 $ echo 1 > f-unchanged-2
866 866 $ echo 1 > f-same
867 867 $ hg ci -m2
868 868 Invoking status precommit hook
869 869 M f-different
870 870 M f-same
871 871 M f-unchanged-1
872 872 M f-unchanged-2
873 873 $ hg up -qr0
874 874 $ echo tmp2 > f-unchanged-1
875 875 $ echo tmp2 > f-unchanged-2
876 876 $ echo tmp2 > f-same
877 877 $ hg ci -m3
878 878 Invoking status precommit hook
879 879 M f-same
880 880 M f-unchanged-1
881 881 M f-unchanged-2
882 882 created new head
883 883 $ echo 1 > f-different
884 884 $ echo 1 > f-unchanged-1
885 885 $ echo 0 > f-unchanged-2
886 886 $ echo 1 > f-same
887 887 $ hg ci -m4
888 888 Invoking status precommit hook
889 889 M f-different
890 890 M f-same
891 891 M f-unchanged-1
892 892 M f-unchanged-2
893 893 $ hg merge
894 894 largefile f-different has a merge conflict
895 895 ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7
896 896 keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or
897 897 take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l
898 898 getting changed largefiles
899 899 1 largefiles updated, 0 removed
900 900 0 files updated, 4 files merged, 0 files removed, 0 files unresolved
901 901 (branch merge, don't forget to commit)
902 902 $ cat f-different
903 903 1
904 904 $ cat f-same
905 905 1
906 906 $ cat f-unchanged-1
907 907 1
908 908 $ cat f-unchanged-2
909 909 1
910 910 $ cd ..
911 911
912 912 Test largefile insulation (do not enabled a side effect
913 913 ========================================================
914 914
915 915 Check whether "largefiles" feature is supported only in repositories
916 916 enabling largefiles extension.
917 917
918 918 $ mkdir individualenabling
919 919 $ cd individualenabling
920 920
921 921 $ hg init enabledlocally
922 922 $ echo large > enabledlocally/large
923 923 $ hg -R enabledlocally add --large enabledlocally/large
924 924 $ hg -R enabledlocally commit -m '#0'
925 925 Invoking status precommit hook
926 926 A large
927 927
928 928 $ hg init notenabledlocally
929 929 $ echo large > notenabledlocally/large
930 930 $ hg -R notenabledlocally add --large notenabledlocally/large
931 931 $ hg -R notenabledlocally commit -m '#0'
932 932 Invoking status precommit hook
933 933 A large
934 934
935 935 $ cat >> $HGRCPATH <<EOF
936 936 > [extensions]
937 937 > # disable globally
938 938 > largefiles=!
939 939 > EOF
940 940 $ cat >> enabledlocally/.hg/hgrc <<EOF
941 941 > [extensions]
942 942 > # enable locally
943 943 > largefiles=
944 944 > EOF
945 945 $ hg -R enabledlocally root
946 946 $TESTTMP/individualenabling/enabledlocally (glob)
947 947 $ hg -R notenabledlocally root
948 948 abort: repository requires features unknown to this Mercurial: largefiles!
949 949 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
950 950 [255]
951 951
952 952 $ hg init push-dst
953 953 $ hg -R enabledlocally push push-dst
954 954 pushing to push-dst
955 955 abort: required features are not supported in the destination: largefiles
956 956 [255]
957 957
958 958 $ hg init pull-src
959 959 $ hg -R pull-src pull enabledlocally
960 960 pulling from enabledlocally
961 961 abort: required features are not supported in the destination: largefiles
962 962 [255]
963 963
964 964 $ hg clone enabledlocally clone-dst
965 965 abort: repository requires features unknown to this Mercurial: largefiles!
966 966 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
967 967 [255]
968 968 $ test -d clone-dst
969 969 [1]
970 970 $ hg clone --pull enabledlocally clone-pull-dst
971 971 abort: required features are not supported in the destination: largefiles
972 972 [255]
973 973 $ test -d clone-pull-dst
974 974 [1]
975 975
976 976 #if serve
977 977
978 978 Test largefiles specific peer setup, when largefiles is enabled
979 979 locally (issue4109)
980 980
981 981 $ hg showconfig extensions | grep largefiles
982 982 extensions.largefiles=!
983 983 $ mkdir -p $TESTTMP/individualenabling/usercache
984 984
985 985 $ hg serve -R enabledlocally -d -p $HGPORT --pid-file hg.pid
986 986 $ cat hg.pid >> $DAEMON_PIDS
987 987
988 988 $ hg init pull-dst
989 989 $ cat > pull-dst/.hg/hgrc <<EOF
990 990 > [extensions]
991 991 > # enable locally
992 992 > largefiles=
993 993 > [largefiles]
994 994 > # ignore system cache to force largefiles specific wire proto access
995 995 > usercache=$TESTTMP/individualenabling/usercache
996 996 > EOF
997 997 $ hg -R pull-dst -q pull -u http://localhost:$HGPORT
998 998
999 999 $ killdaemons.py
1000 1000 #endif
1001 1001
1002 1002 Test overridden functions work correctly even for repos disabling
1003 1003 largefiles (issue4547)
1004 1004
1005 1005 $ hg showconfig extensions | grep largefiles
1006 1006 extensions.largefiles=!
1007 1007
1008 1008 (test updating implied by clone)
1009 1009
1010 1010 $ hg init enabled-but-no-largefiles
1011 1011 $ echo normal1 > enabled-but-no-largefiles/normal1
1012 1012 $ hg -R enabled-but-no-largefiles add enabled-but-no-largefiles/normal1
1013 1013 $ hg -R enabled-but-no-largefiles commit -m '#0@enabled-but-no-largefiles'
1014 1014 Invoking status precommit hook
1015 1015 A normal1
1016 1016 $ cat >> enabled-but-no-largefiles/.hg/hgrc <<EOF
1017 1017 > [extensions]
1018 1018 > # enable locally
1019 1019 > largefiles=
1020 1020 > EOF
1021 1021 $ hg clone -q enabled-but-no-largefiles no-largefiles
1022 1022
1023 1023 $ echo normal2 > enabled-but-no-largefiles/normal2
1024 1024 $ hg -R enabled-but-no-largefiles add enabled-but-no-largefiles/normal2
1025 1025 $ hg -R enabled-but-no-largefiles commit -m '#1@enabled-but-no-largefiles'
1026 1026 Invoking status precommit hook
1027 1027 A normal2
1028 1028
1029 1029 $ echo normal3 > no-largefiles/normal3
1030 1030 $ hg -R no-largefiles add no-largefiles/normal3
1031 1031 $ hg -R no-largefiles commit -m '#1@no-largefiles'
1032 1032 Invoking status precommit hook
1033 1033 A normal3
1034 1034
1035 1035 $ hg -R no-largefiles -q pull --rebase
1036 1036 Invoking status precommit hook
1037 1037 A normal3
1038 1038
1039 1039 (test reverting)
1040 1040
1041 1041 $ hg init subrepo-root
1042 1042 $ cat >> subrepo-root/.hg/hgrc <<EOF
1043 1043 > [extensions]
1044 1044 > # enable locally
1045 1045 > largefiles=
1046 1046 > EOF
1047 1047 $ echo large > subrepo-root/large
1048 1048 $ hg -R subrepo-root add --large subrepo-root/large
1049 1049 $ hg clone -q no-largefiles subrepo-root/no-largefiles
1050 1050 $ cat > subrepo-root/.hgsub <<EOF
1051 1051 > no-largefiles = no-largefiles
1052 1052 > EOF
1053 1053 $ hg -R subrepo-root add subrepo-root/.hgsub
1054 1054 $ hg -R subrepo-root commit -m '#0'
1055 1055 Invoking status precommit hook
1056 1056 A .hgsub
1057 1057 A large
1058 1058 ? .hgsubstate
1059 1059 $ echo dirty >> subrepo-root/large
1060 1060 $ echo dirty >> subrepo-root/no-largefiles/normal1
1061 1061 $ hg -R subrepo-root status -S
1062 1062 M large
1063 1063 M no-largefiles/normal1
1064 1064 $ hg -R subrepo-root revert --all
1065 1065 reverting subrepo-root/.hglf/large (glob)
1066 1066 reverting subrepo no-largefiles
1067 1067 reverting subrepo-root/no-largefiles/normal1 (glob)
1068 1068
1069 1069 $ cd ..
1070 1070
1071 1071
1072 1072 Test "pull --rebase" when rebase is enabled before largefiles (issue3861)
1073 1073 =========================================================================
1074 1074
1075 1075 $ hg showconfig extensions | grep largefiles
1076 1076 extensions.largefiles=!
1077 1077
1078 1078 $ mkdir issue3861
1079 1079 $ cd issue3861
1080 1080 $ hg init src
1081 1081 $ hg clone -q src dst
1082 1082 $ echo a > src/a
1083 1083 $ hg -R src commit -Aqm "#0"
1084 1084 Invoking status precommit hook
1085 1085 A a
1086 1086
1087 1087 $ cat >> dst/.hg/hgrc <<EOF
1088 1088 > [extensions]
1089 1089 > largefiles=
1090 1090 > EOF
1091 1091 $ hg -R dst pull --rebase
1092 1092 pulling from $TESTTMP/issue3861/src (glob)
1093 1093 requesting all changes
1094 1094 adding changesets
1095 1095 adding manifests
1096 1096 adding file changes
1097 1097 added 1 changesets with 1 changes to 1 files
1098 1098 nothing to rebase - updating instead
1099 1099 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1100 1100
1101 1101 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now