##// END OF EJS Templates
largefiles: report the source of copied/moved largefiles in status -C...
Matt Harbison -
r24230:23438bce default
parent child Browse files
Show More
@@ -1,1395 +1,1404 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, util, cmdutil, scmutil, match as match_, \
15 15 archival, pathutil, revset
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18
19 19 import lfutil
20 20 import lfcommands
21 21 import basestore
22 22
23 23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24 24
25 25 def composelargefilematcher(match, manifest):
26 26 '''create a matcher that matches only the largefiles in the original
27 27 matcher'''
28 28 m = copy.copy(match)
29 29 lfile = lambda f: lfutil.standin(f) in manifest
30 30 m._files = filter(lfile, m._files)
31 31 m._fmap = set(m._files)
32 32 m._always = False
33 33 origmatchfn = m.matchfn
34 34 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
35 35 return m
36 36
37 37 def composenormalfilematcher(match, manifest, exclude=None):
38 38 excluded = set()
39 39 if exclude is not None:
40 40 excluded.update(exclude)
41 41
42 42 m = copy.copy(match)
43 43 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
44 44 manifest or f in excluded)
45 45 m._files = filter(notlfile, m._files)
46 46 m._fmap = set(m._files)
47 47 m._always = False
48 48 origmatchfn = m.matchfn
49 49 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
50 50 return m
51 51
52 52 def installnormalfilesmatchfn(manifest):
53 53 '''installmatchfn with a matchfn that ignores all largefiles'''
54 54 def overridematch(ctx, pats=[], opts={}, globbed=False,
55 55 default='relpath'):
56 56 match = oldmatch(ctx, pats, opts, globbed, default)
57 57 return composenormalfilematcher(match, manifest)
58 58 oldmatch = installmatchfn(overridematch)
59 59
60 60 def installmatchfn(f):
61 61 '''monkey patch the scmutil module with a custom match function.
62 62 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
63 63 oldmatch = scmutil.match
64 64 setattr(f, 'oldmatch', oldmatch)
65 65 scmutil.match = f
66 66 return oldmatch
67 67
68 68 def restorematchfn():
69 69 '''restores scmutil.match to what it was before installmatchfn
70 70 was called. no-op if scmutil.match is its original function.
71 71
72 72 Note that n calls to installmatchfn will require n calls to
73 73 restore the original matchfn.'''
74 74 scmutil.match = getattr(scmutil.match, 'oldmatch')
75 75
76 76 def installmatchandpatsfn(f):
77 77 oldmatchandpats = scmutil.matchandpats
78 78 setattr(f, 'oldmatchandpats', oldmatchandpats)
79 79 scmutil.matchandpats = f
80 80 return oldmatchandpats
81 81
82 82 def restorematchandpatsfn():
83 83 '''restores scmutil.matchandpats to what it was before
84 84 installmatchandpatsfn was called. No-op if scmutil.matchandpats
85 85 is its original function.
86 86
87 87 Note that n calls to installmatchandpatsfn will require n calls
88 88 to restore the original matchfn.'''
89 89 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
90 90 scmutil.matchandpats)
91 91
92 92 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
93 93 large = opts.get('large')
94 94 lfsize = lfutil.getminsize(
95 95 ui, lfutil.islfilesrepo(repo), opts.get('lfsize'))
96 96
97 97 lfmatcher = None
98 98 if lfutil.islfilesrepo(repo):
99 99 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
100 100 if lfpats:
101 101 lfmatcher = match_.match(repo.root, '', list(lfpats))
102 102
103 103 lfnames = []
104 104 m = copy.copy(matcher)
105 105 m.bad = lambda x, y: None
106 106 wctx = repo[None]
107 107 for f in repo.walk(m):
108 108 exact = m.exact(f)
109 109 lfile = lfutil.standin(f) in wctx
110 110 nfile = f in wctx
111 111 exists = lfile or nfile
112 112
113 113 # addremove in core gets fancy with the name, add doesn't
114 114 if isaddremove:
115 115 name = m.uipath(f)
116 116 else:
117 117 name = m.rel(f)
118 118
119 119 # Don't warn the user when they attempt to add a normal tracked file.
120 120 # The normal add code will do that for us.
121 121 if exact and exists:
122 122 if lfile:
123 123 ui.warn(_('%s already a largefile\n') % name)
124 124 continue
125 125
126 126 if (exact or not exists) and not lfutil.isstandin(f):
127 127 # In case the file was removed previously, but not committed
128 128 # (issue3507)
129 129 if not repo.wvfs.exists(f):
130 130 continue
131 131
132 132 abovemin = (lfsize and
133 133 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
134 134 if large or abovemin or (lfmatcher and lfmatcher(f)):
135 135 lfnames.append(f)
136 136 if ui.verbose or not exact:
137 137 ui.status(_('adding %s as a largefile\n') % name)
138 138
139 139 bad = []
140 140
141 141 # Need to lock, otherwise there could be a race condition between
142 142 # when standins are created and added to the repo.
143 143 wlock = repo.wlock()
144 144 try:
145 145 if not opts.get('dry_run'):
146 146 standins = []
147 147 lfdirstate = lfutil.openlfdirstate(ui, repo)
148 148 for f in lfnames:
149 149 standinname = lfutil.standin(f)
150 150 lfutil.writestandin(repo, standinname, hash='',
151 151 executable=lfutil.getexecutable(repo.wjoin(f)))
152 152 standins.append(standinname)
153 153 if lfdirstate[f] == 'r':
154 154 lfdirstate.normallookup(f)
155 155 else:
156 156 lfdirstate.add(f)
157 157 lfdirstate.write()
158 158 bad += [lfutil.splitstandin(f)
159 159 for f in repo[None].add(standins)
160 160 if f in m.files()]
161 161
162 162 added = [f for f in lfnames if f not in bad]
163 163 finally:
164 164 wlock.release()
165 165 return added, bad
166 166
167 167 def removelargefiles(ui, repo, isaddremove, matcher, **opts):
168 168 after = opts.get('after')
169 169 m = composelargefilematcher(matcher, repo[None].manifest())
170 170 try:
171 171 repo.lfstatus = True
172 172 s = repo.status(match=m, clean=not isaddremove)
173 173 finally:
174 174 repo.lfstatus = False
175 175 manifest = repo[None].manifest()
176 176 modified, added, deleted, clean = [[f for f in list
177 177 if lfutil.standin(f) in manifest]
178 178 for list in (s.modified, s.added,
179 179 s.deleted, s.clean)]
180 180
181 181 def warn(files, msg):
182 182 for f in files:
183 183 ui.warn(msg % m.rel(f))
184 184 return int(len(files) > 0)
185 185
186 186 result = 0
187 187
188 188 if after:
189 189 remove = deleted
190 190 result = warn(modified + added + clean,
191 191 _('not removing %s: file still exists\n'))
192 192 else:
193 193 remove = deleted + clean
194 194 result = warn(modified, _('not removing %s: file is modified (use -f'
195 195 ' to force removal)\n'))
196 196 result = warn(added, _('not removing %s: file has been marked for add'
197 197 ' (use forget to undo)\n')) or result
198 198
199 199 # Need to lock because standin files are deleted then removed from the
200 200 # repository and we could race in-between.
201 201 wlock = repo.wlock()
202 202 try:
203 203 lfdirstate = lfutil.openlfdirstate(ui, repo)
204 204 for f in sorted(remove):
205 205 if ui.verbose or not m.exact(f):
206 206 # addremove in core gets fancy with the name, remove doesn't
207 207 if isaddremove:
208 208 name = m.uipath(f)
209 209 else:
210 210 name = m.rel(f)
211 211 ui.status(_('removing %s\n') % name)
212 212
213 213 if not opts.get('dry_run'):
214 214 if not after:
215 215 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
216 216
217 217 if opts.get('dry_run'):
218 218 return result
219 219
220 220 remove = [lfutil.standin(f) for f in remove]
221 221 # If this is being called by addremove, let the original addremove
222 222 # function handle this.
223 223 if not isaddremove:
224 224 for f in remove:
225 225 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
226 226 repo[None].forget(remove)
227 227
228 228 for f in remove:
229 229 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
230 230 False)
231 231
232 232 lfdirstate.write()
233 233 finally:
234 234 wlock.release()
235 235
236 236 return result
237 237
238 238 # For overriding mercurial.hgweb.webcommands so that largefiles will
239 239 # appear at their right place in the manifests.
240 240 def decodepath(orig, path):
241 241 return lfutil.splitstandin(path) or path
242 242
243 243 # -- Wrappers: modify existing commands --------------------------------
244 244
245 245 def overrideadd(orig, ui, repo, *pats, **opts):
246 246 if opts.get('normal') and opts.get('large'):
247 247 raise util.Abort(_('--normal cannot be used with --large'))
248 248 return orig(ui, repo, *pats, **opts)
249 249
250 250 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
251 251 # The --normal flag short circuits this override
252 252 if opts.get('normal'):
253 253 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
254 254
255 255 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
256 256 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
257 257 ladded)
258 258 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
259 259
260 260 bad.extend(f for f in lbad)
261 261 return bad
262 262
263 263 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos):
264 264 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
265 265 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos)
266 266 return removelargefiles(ui, repo, False, matcher, after=after,
267 267 force=force) or result
268 268
269 269 def overridestatusfn(orig, repo, rev2, **opts):
270 270 try:
271 271 repo._repo.lfstatus = True
272 272 return orig(repo, rev2, **opts)
273 273 finally:
274 274 repo._repo.lfstatus = False
275 275
276 276 def overridestatus(orig, ui, repo, *pats, **opts):
277 277 try:
278 278 repo.lfstatus = True
279 279 return orig(ui, repo, *pats, **opts)
280 280 finally:
281 281 repo.lfstatus = False
282 282
283 283 def overridedirty(orig, repo, ignoreupdate=False):
284 284 try:
285 285 repo._repo.lfstatus = True
286 286 return orig(repo, ignoreupdate)
287 287 finally:
288 288 repo._repo.lfstatus = False
289 289
290 290 def overridelog(orig, ui, repo, *pats, **opts):
291 291 def overridematchandpats(ctx, pats=[], opts={}, globbed=False,
292 292 default='relpath'):
293 293 """Matcher that merges root directory with .hglf, suitable for log.
294 294 It is still possible to match .hglf directly.
295 295 For any listed files run log on the standin too.
296 296 matchfn tries both the given filename and with .hglf stripped.
297 297 """
298 298 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default)
299 299 m, p = copy.copy(matchandpats)
300 300
301 301 if m.always():
302 302 # We want to match everything anyway, so there's no benefit trying
303 303 # to add standins.
304 304 return matchandpats
305 305
306 306 pats = set(p)
307 307
308 308 def fixpats(pat, tostandin=lfutil.standin):
309 309 kindpat = match_._patsplit(pat, None)
310 310
311 311 if kindpat[0] is not None:
312 312 return kindpat[0] + ':' + tostandin(kindpat[1])
313 313 return tostandin(kindpat[1])
314 314
315 315 if m._cwd:
316 316 hglf = lfutil.shortname
317 317 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
318 318
319 319 def tostandin(f):
320 320 # The file may already be a standin, so trucate the back
321 321 # prefix and test before mangling it. This avoids turning
322 322 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
323 323 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
324 324 return f
325 325
326 326 # An absolute path is from outside the repo, so truncate the
327 327 # path to the root before building the standin. Otherwise cwd
328 328 # is somewhere in the repo, relative to root, and needs to be
329 329 # prepended before building the standin.
330 330 if os.path.isabs(m._cwd):
331 331 f = f[len(back):]
332 332 else:
333 333 f = m._cwd + '/' + f
334 334 return back + lfutil.standin(f)
335 335
336 336 pats.update(fixpats(f, tostandin) for f in p)
337 337 else:
338 338 def tostandin(f):
339 339 if lfutil.splitstandin(f):
340 340 return f
341 341 return lfutil.standin(f)
342 342 pats.update(fixpats(f, tostandin) for f in p)
343 343
344 344 for i in range(0, len(m._files)):
345 345 # Don't add '.hglf' to m.files, since that is already covered by '.'
346 346 if m._files[i] == '.':
347 347 continue
348 348 standin = lfutil.standin(m._files[i])
349 349 # If the "standin" is a directory, append instead of replace to
350 350 # support naming a directory on the command line with only
351 351 # largefiles. The original directory is kept to support normal
352 352 # files.
353 353 if standin in repo[ctx.node()]:
354 354 m._files[i] = standin
355 355 elif m._files[i] not in repo[ctx.node()] \
356 356 and repo.wvfs.isdir(standin):
357 357 m._files.append(standin)
358 358
359 359 m._fmap = set(m._files)
360 360 m._always = False
361 361 origmatchfn = m.matchfn
362 362 def lfmatchfn(f):
363 363 lf = lfutil.splitstandin(f)
364 364 if lf is not None and origmatchfn(lf):
365 365 return True
366 366 r = origmatchfn(f)
367 367 return r
368 368 m.matchfn = lfmatchfn
369 369
370 370 ui.debug('updated patterns: %s\n' % sorted(pats))
371 371 return m, pats
372 372
373 373 # For hg log --patch, the match object is used in two different senses:
374 374 # (1) to determine what revisions should be printed out, and
375 375 # (2) to determine what files to print out diffs for.
376 376 # The magic matchandpats override should be used for case (1) but not for
377 377 # case (2).
378 378 def overridemakelogfilematcher(repo, pats, opts):
379 379 pctx = repo[None]
380 380 match, pats = oldmatchandpats(pctx, pats, opts)
381 381 return lambda rev: match
382 382
383 383 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
384 384 oldmakelogfilematcher = cmdutil._makenofollowlogfilematcher
385 385 setattr(cmdutil, '_makenofollowlogfilematcher', overridemakelogfilematcher)
386 386
387 387 try:
388 388 return orig(ui, repo, *pats, **opts)
389 389 finally:
390 390 restorematchandpatsfn()
391 391 setattr(cmdutil, '_makenofollowlogfilematcher', oldmakelogfilematcher)
392 392
393 393 def overrideverify(orig, ui, repo, *pats, **opts):
394 394 large = opts.pop('large', False)
395 395 all = opts.pop('lfa', False)
396 396 contents = opts.pop('lfc', False)
397 397
398 398 result = orig(ui, repo, *pats, **opts)
399 399 if large or all or contents:
400 400 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
401 401 return result
402 402
403 403 def overridedebugstate(orig, ui, repo, *pats, **opts):
404 404 large = opts.pop('large', False)
405 405 if large:
406 406 class fakerepo(object):
407 407 dirstate = lfutil.openlfdirstate(ui, repo)
408 408 orig(ui, fakerepo, *pats, **opts)
409 409 else:
410 410 orig(ui, repo, *pats, **opts)
411 411
412 412 # Override needs to refresh standins so that update's normal merge
413 413 # will go through properly. Then the other update hook (overriding repo.update)
414 414 # will get the new files. Filemerge is also overridden so that the merge
415 415 # will merge standins correctly.
416 416 def overrideupdate(orig, ui, repo, *pats, **opts):
417 417 # Need to lock between the standins getting updated and their
418 418 # largefiles getting updated
419 419 wlock = repo.wlock()
420 420 try:
421 421 if opts['check']:
422 422 lfdirstate = lfutil.openlfdirstate(ui, repo)
423 423 unsure, s = lfdirstate.status(
424 424 match_.always(repo.root, repo.getcwd()),
425 425 [], False, False, False)
426 426
427 427 mod = len(s.modified) > 0
428 428 for lfile in unsure:
429 429 standin = lfutil.standin(lfile)
430 430 if repo['.'][standin].data().strip() != \
431 431 lfutil.hashfile(repo.wjoin(lfile)):
432 432 mod = True
433 433 else:
434 434 lfdirstate.normal(lfile)
435 435 lfdirstate.write()
436 436 if mod:
437 437 raise util.Abort(_('uncommitted changes'))
438 438 return orig(ui, repo, *pats, **opts)
439 439 finally:
440 440 wlock.release()
441 441
442 442 # Before starting the manifest merge, merge.updates will call
443 443 # _checkunknownfile to check if there are any files in the merged-in
444 444 # changeset that collide with unknown files in the working copy.
445 445 #
446 446 # The largefiles are seen as unknown, so this prevents us from merging
447 447 # in a file 'foo' if we already have a largefile with the same name.
448 448 #
449 449 # The overridden function filters the unknown files by removing any
450 450 # largefiles. This makes the merge proceed and we can then handle this
451 451 # case further in the overridden calculateupdates function below.
452 452 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
453 453 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
454 454 return False
455 455 return origfn(repo, wctx, mctx, f, f2)
456 456
457 457 # The manifest merge handles conflicts on the manifest level. We want
458 458 # to handle changes in largefile-ness of files at this level too.
459 459 #
460 460 # The strategy is to run the original calculateupdates and then process
461 461 # the action list it outputs. There are two cases we need to deal with:
462 462 #
463 463 # 1. Normal file in p1, largefile in p2. Here the largefile is
464 464 # detected via its standin file, which will enter the working copy
465 465 # with a "get" action. It is not "merge" since the standin is all
466 466 # Mercurial is concerned with at this level -- the link to the
467 467 # existing normal file is not relevant here.
468 468 #
469 469 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
470 470 # since the largefile will be present in the working copy and
471 471 # different from the normal file in p2. Mercurial therefore
472 472 # triggers a merge action.
473 473 #
474 474 # In both cases, we prompt the user and emit new actions to either
475 475 # remove the standin (if the normal file was kept) or to remove the
476 476 # normal file and get the standin (if the largefile was kept). The
477 477 # default prompt answer is to use the largefile version since it was
478 478 # presumably changed on purpose.
479 479 #
480 480 # Finally, the merge.applyupdates function will then take care of
481 481 # writing the files into the working copy and lfcommands.updatelfiles
482 482 # will update the largefiles.
483 483 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
484 484 partial, acceptremote, followcopies):
485 485 overwrite = force and not branchmerge
486 486 actions, diverge, renamedelete = origfn(
487 487 repo, p1, p2, pas, branchmerge, force, partial, acceptremote,
488 488 followcopies)
489 489
490 490 if overwrite:
491 491 return actions, diverge, renamedelete
492 492
493 493 # Convert to dictionary with filename as key and action as value.
494 494 lfiles = set()
495 495 for f in actions:
496 496 splitstandin = f and lfutil.splitstandin(f)
497 497 if splitstandin in p1:
498 498 lfiles.add(splitstandin)
499 499 elif lfutil.standin(f) in p1:
500 500 lfiles.add(f)
501 501
502 502 for lfile in lfiles:
503 503 standin = lfutil.standin(lfile)
504 504 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
505 505 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
506 506 if sm in ('g', 'dc') and lm != 'r':
507 507 # Case 1: normal file in the working copy, largefile in
508 508 # the second parent
509 509 usermsg = _('remote turned local normal file %s into a largefile\n'
510 510 'use (l)argefile or keep (n)ormal file?'
511 511 '$$ &Largefile $$ &Normal file') % lfile
512 512 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
513 513 actions[lfile] = ('r', None, 'replaced by standin')
514 514 actions[standin] = ('g', sargs, 'replaces standin')
515 515 else: # keep local normal file
516 516 actions[lfile] = ('k', None, 'replaces standin')
517 517 if branchmerge:
518 518 actions[standin] = ('k', None, 'replaced by non-standin')
519 519 else:
520 520 actions[standin] = ('r', None, 'replaced by non-standin')
521 521 elif lm in ('g', 'dc') and sm != 'r':
522 522 # Case 2: largefile in the working copy, normal file in
523 523 # the second parent
524 524 usermsg = _('remote turned local largefile %s into a normal file\n'
525 525 'keep (l)argefile or use (n)ormal file?'
526 526 '$$ &Largefile $$ &Normal file') % lfile
527 527 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
528 528 if branchmerge:
529 529 # largefile can be restored from standin safely
530 530 actions[lfile] = ('k', None, 'replaced by standin')
531 531 actions[standin] = ('k', None, 'replaces standin')
532 532 else:
533 533 # "lfile" should be marked as "removed" without
534 534 # removal of itself
535 535 actions[lfile] = ('lfmr', None,
536 536 'forget non-standin largefile')
537 537
538 538 # linear-merge should treat this largefile as 're-added'
539 539 actions[standin] = ('a', None, 'keep standin')
540 540 else: # pick remote normal file
541 541 actions[lfile] = ('g', largs, 'replaces standin')
542 542 actions[standin] = ('r', None, 'replaced by non-standin')
543 543
544 544 return actions, diverge, renamedelete
545 545
546 546 def mergerecordupdates(orig, repo, actions, branchmerge):
547 547 if 'lfmr' in actions:
548 548 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
549 549 for lfile, args, msg in actions['lfmr']:
550 550 # this should be executed before 'orig', to execute 'remove'
551 551 # before all other actions
552 552 repo.dirstate.remove(lfile)
553 553 # make sure lfile doesn't get synclfdirstate'd as normal
554 554 lfdirstate.add(lfile)
555 555 lfdirstate.write()
556 556
557 557 return orig(repo, actions, branchmerge)
558 558
559 559
560 560 # Override filemerge to prompt the user about how they wish to merge
561 561 # largefiles. This will handle identical edits without prompting the user.
562 562 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None):
563 563 if not lfutil.isstandin(orig):
564 564 return origfn(repo, mynode, orig, fcd, fco, fca, labels=labels)
565 565
566 566 ahash = fca.data().strip().lower()
567 567 dhash = fcd.data().strip().lower()
568 568 ohash = fco.data().strip().lower()
569 569 if (ohash != ahash and
570 570 ohash != dhash and
571 571 (dhash == ahash or
572 572 repo.ui.promptchoice(
573 573 _('largefile %s has a merge conflict\nancestor was %s\n'
574 574 'keep (l)ocal %s or\ntake (o)ther %s?'
575 575 '$$ &Local $$ &Other') %
576 576 (lfutil.splitstandin(orig), ahash, dhash, ohash),
577 577 0) == 1)):
578 578 repo.wwrite(fcd.path(), fco.data(), fco.flags())
579 579 return 0
580 580
581 def copiespathcopies(orig, ctx1, ctx2):
582 copies = orig(ctx1, ctx2)
583 updated = {}
584
585 for k, v in copies.iteritems():
586 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
587
588 return updated
589
581 590 # Copy first changes the matchers to match standins instead of
582 591 # largefiles. Then it overrides util.copyfile in that function it
583 592 # checks if the destination largefile already exists. It also keeps a
584 593 # list of copied files so that the largefiles can be copied and the
585 594 # dirstate updated.
586 595 def overridecopy(orig, ui, repo, pats, opts, rename=False):
587 596 # doesn't remove largefile on rename
588 597 if len(pats) < 2:
589 598 # this isn't legal, let the original function deal with it
590 599 return orig(ui, repo, pats, opts, rename)
591 600
592 601 # This could copy both lfiles and normal files in one command,
593 602 # but we don't want to do that. First replace their matcher to
594 603 # only match normal files and run it, then replace it to just
595 604 # match largefiles and run it again.
596 605 nonormalfiles = False
597 606 nolfiles = False
598 607 installnormalfilesmatchfn(repo[None].manifest())
599 608 try:
600 609 try:
601 610 result = orig(ui, repo, pats, opts, rename)
602 611 except util.Abort, e:
603 612 if str(e) != _('no files to copy'):
604 613 raise e
605 614 else:
606 615 nonormalfiles = True
607 616 result = 0
608 617 finally:
609 618 restorematchfn()
610 619
611 620 # The first rename can cause our current working directory to be removed.
612 621 # In that case there is nothing left to copy/rename so just quit.
613 622 try:
614 623 repo.getcwd()
615 624 except OSError:
616 625 return result
617 626
618 627 def makestandin(relpath):
619 628 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
620 629 return os.path.join(repo.wjoin(lfutil.standin(path)))
621 630
622 631 fullpats = scmutil.expandpats(pats)
623 632 dest = fullpats[-1]
624 633
625 634 if os.path.isdir(dest):
626 635 if not os.path.isdir(makestandin(dest)):
627 636 os.makedirs(makestandin(dest))
628 637
629 638 try:
630 639 try:
631 640 # When we call orig below it creates the standins but we don't add
632 641 # them to the dir state until later so lock during that time.
633 642 wlock = repo.wlock()
634 643
635 644 manifest = repo[None].manifest()
636 645 def overridematch(ctx, pats=[], opts={}, globbed=False,
637 646 default='relpath'):
638 647 newpats = []
639 648 # The patterns were previously mangled to add the standin
640 649 # directory; we need to remove that now
641 650 for pat in pats:
642 651 if match_.patkind(pat) is None and lfutil.shortname in pat:
643 652 newpats.append(pat.replace(lfutil.shortname, ''))
644 653 else:
645 654 newpats.append(pat)
646 655 match = oldmatch(ctx, newpats, opts, globbed, default)
647 656 m = copy.copy(match)
648 657 lfile = lambda f: lfutil.standin(f) in manifest
649 658 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
650 659 m._fmap = set(m._files)
651 660 origmatchfn = m.matchfn
652 661 m.matchfn = lambda f: (lfutil.isstandin(f) and
653 662 (f in manifest) and
654 663 origmatchfn(lfutil.splitstandin(f)) or
655 664 None)
656 665 return m
657 666 oldmatch = installmatchfn(overridematch)
658 667 listpats = []
659 668 for pat in pats:
660 669 if match_.patkind(pat) is not None:
661 670 listpats.append(pat)
662 671 else:
663 672 listpats.append(makestandin(pat))
664 673
665 674 try:
666 675 origcopyfile = util.copyfile
667 676 copiedfiles = []
668 677 def overridecopyfile(src, dest):
669 678 if (lfutil.shortname in src and
670 679 dest.startswith(repo.wjoin(lfutil.shortname))):
671 680 destlfile = dest.replace(lfutil.shortname, '')
672 681 if not opts['force'] and os.path.exists(destlfile):
673 682 raise IOError('',
674 683 _('destination largefile already exists'))
675 684 copiedfiles.append((src, dest))
676 685 origcopyfile(src, dest)
677 686
678 687 util.copyfile = overridecopyfile
679 688 result += orig(ui, repo, listpats, opts, rename)
680 689 finally:
681 690 util.copyfile = origcopyfile
682 691
683 692 lfdirstate = lfutil.openlfdirstate(ui, repo)
684 693 for (src, dest) in copiedfiles:
685 694 if (lfutil.shortname in src and
686 695 dest.startswith(repo.wjoin(lfutil.shortname))):
687 696 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
688 697 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
689 698 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
690 699 if not os.path.isdir(destlfiledir):
691 700 os.makedirs(destlfiledir)
692 701 if rename:
693 702 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
694 703
695 704 # The file is gone, but this deletes any empty parent
696 705 # directories as a side-effect.
697 706 util.unlinkpath(repo.wjoin(srclfile), True)
698 707 lfdirstate.remove(srclfile)
699 708 else:
700 709 util.copyfile(repo.wjoin(srclfile),
701 710 repo.wjoin(destlfile))
702 711
703 712 lfdirstate.add(destlfile)
704 713 lfdirstate.write()
705 714 except util.Abort, e:
706 715 if str(e) != _('no files to copy'):
707 716 raise e
708 717 else:
709 718 nolfiles = True
710 719 finally:
711 720 restorematchfn()
712 721 wlock.release()
713 722
714 723 if nolfiles and nonormalfiles:
715 724 raise util.Abort(_('no files to copy'))
716 725
717 726 return result
718 727
719 728 # When the user calls revert, we have to be careful to not revert any
720 729 # changes to other largefiles accidentally. This means we have to keep
721 730 # track of the largefiles that are being reverted so we only pull down
722 731 # the necessary largefiles.
723 732 #
724 733 # Standins are only updated (to match the hash of largefiles) before
725 734 # commits. Update the standins then run the original revert, changing
726 735 # the matcher to hit standins instead of largefiles. Based on the
727 736 # resulting standins update the largefiles.
728 737 def overriderevert(orig, ui, repo, *pats, **opts):
729 738 # Because we put the standins in a bad state (by updating them)
730 739 # and then return them to a correct state we need to lock to
731 740 # prevent others from changing them in their incorrect state.
732 741 wlock = repo.wlock()
733 742 try:
734 743 lfdirstate = lfutil.openlfdirstate(ui, repo)
735 744 s = lfutil.lfdirstatestatus(lfdirstate, repo)
736 745 lfdirstate.write()
737 746 for lfile in s.modified:
738 747 lfutil.updatestandin(repo, lfutil.standin(lfile))
739 748 for lfile in s.deleted:
740 749 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
741 750 os.unlink(repo.wjoin(lfutil.standin(lfile)))
742 751
743 752 oldstandins = lfutil.getstandinsstate(repo)
744 753
745 754 def overridematch(ctx, pats=[], opts={}, globbed=False,
746 755 default='relpath'):
747 756 match = oldmatch(ctx, pats, opts, globbed, default)
748 757 m = copy.copy(match)
749 758
750 759 # revert supports recursing into subrepos, and though largefiles
751 760 # currently doesn't work correctly in that case, this match is
752 761 # called, so the lfdirstate above may not be the correct one for
753 762 # this invocation of match.
754 763 lfdirstate = lfutil.openlfdirstate(ctx._repo.ui, ctx._repo, False)
755 764
756 765 def tostandin(f):
757 766 if lfutil.standin(f) in ctx:
758 767 return lfutil.standin(f)
759 768 elif lfutil.standin(f) in repo[None] or lfdirstate[f] == 'r':
760 769 return None
761 770 return f
762 771 m._files = [tostandin(f) for f in m._files]
763 772 m._files = [f for f in m._files if f is not None]
764 773 m._fmap = set(m._files)
765 774 origmatchfn = m.matchfn
766 775 def matchfn(f):
767 776 if lfutil.isstandin(f):
768 777 return (origmatchfn(lfutil.splitstandin(f)) and
769 778 (f in repo[None] or f in ctx))
770 779 return origmatchfn(f)
771 780 m.matchfn = matchfn
772 781 return m
773 782 oldmatch = installmatchfn(overridematch)
774 783 try:
775 784 orig(ui, repo, *pats, **opts)
776 785 finally:
777 786 restorematchfn()
778 787
779 788 newstandins = lfutil.getstandinsstate(repo)
780 789 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
781 790 # lfdirstate should be 'normallookup'-ed for updated files,
782 791 # because reverting doesn't touch dirstate for 'normal' files
783 792 # when target revision is explicitly specified: in such case,
784 793 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
785 794 # of target (standin) file.
786 795 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
787 796 normallookup=True)
788 797
789 798 finally:
790 799 wlock.release()
791 800
792 801 # after pulling changesets, we need to take some extra care to get
793 802 # largefiles updated remotely
794 803 def overridepull(orig, ui, repo, source=None, **opts):
795 804 revsprepull = len(repo)
796 805 if not source:
797 806 source = 'default'
798 807 repo.lfpullsource = source
799 808 result = orig(ui, repo, source, **opts)
800 809 revspostpull = len(repo)
801 810 lfrevs = opts.get('lfrev', [])
802 811 if opts.get('all_largefiles'):
803 812 lfrevs.append('pulled()')
804 813 if lfrevs and revspostpull > revsprepull:
805 814 numcached = 0
806 815 repo.firstpulled = revsprepull # for pulled() revset expression
807 816 try:
808 817 for rev in scmutil.revrange(repo, lfrevs):
809 818 ui.note(_('pulling largefiles for revision %s\n') % rev)
810 819 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
811 820 numcached += len(cached)
812 821 finally:
813 822 del repo.firstpulled
814 823 ui.status(_("%d largefiles cached\n") % numcached)
815 824 return result
816 825
817 826 def pulledrevsetsymbol(repo, subset, x):
818 827 """``pulled()``
819 828 Changesets that just has been pulled.
820 829
821 830 Only available with largefiles from pull --lfrev expressions.
822 831
823 832 .. container:: verbose
824 833
825 834 Some examples:
826 835
827 836 - pull largefiles for all new changesets::
828 837
829 838 hg pull -lfrev "pulled()"
830 839
831 840 - pull largefiles for all new branch heads::
832 841
833 842 hg pull -lfrev "head(pulled()) and not closed()"
834 843
835 844 """
836 845
837 846 try:
838 847 firstpulled = repo.firstpulled
839 848 except AttributeError:
840 849 raise util.Abort(_("pulled() only available in --lfrev"))
841 850 return revset.baseset([r for r in subset if r >= firstpulled])
842 851
843 852 def overrideclone(orig, ui, source, dest=None, **opts):
844 853 d = dest
845 854 if d is None:
846 855 d = hg.defaultdest(source)
847 856 if opts.get('all_largefiles') and not hg.islocal(d):
848 857 raise util.Abort(_(
849 858 '--all-largefiles is incompatible with non-local destination %s') %
850 859 d)
851 860
852 861 return orig(ui, source, dest, **opts)
853 862
854 863 def hgclone(orig, ui, opts, *args, **kwargs):
855 864 result = orig(ui, opts, *args, **kwargs)
856 865
857 866 if result is not None:
858 867 sourcerepo, destrepo = result
859 868 repo = destrepo.local()
860 869
861 870 # If largefiles is required for this repo, permanently enable it locally
862 871 if 'largefiles' in repo.requirements:
863 872 fp = repo.vfs('hgrc', 'a', text=True)
864 873 try:
865 874 fp.write('\n[extensions]\nlargefiles=\n')
866 875 finally:
867 876 fp.close()
868 877
869 878 # Caching is implicitly limited to 'rev' option, since the dest repo was
870 879 # truncated at that point. The user may expect a download count with
871 880 # this option, so attempt whether or not this is a largefile repo.
872 881 if opts.get('all_largefiles'):
873 882 success, missing = lfcommands.downloadlfiles(ui, repo, None)
874 883
875 884 if missing != 0:
876 885 return None
877 886
878 887 return result
879 888
880 889 def overriderebase(orig, ui, repo, **opts):
881 890 if not util.safehasattr(repo, '_largefilesenabled'):
882 891 return orig(ui, repo, **opts)
883 892
884 893 resuming = opts.get('continue')
885 894 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
886 895 repo._lfstatuswriters.append(lambda *msg, **opts: None)
887 896 try:
888 897 return orig(ui, repo, **opts)
889 898 finally:
890 899 repo._lfstatuswriters.pop()
891 900 repo._lfcommithooks.pop()
892 901
893 902 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
894 903 prefix='', mtime=None, subrepos=None):
895 904 # No need to lock because we are only reading history and
896 905 # largefile caches, neither of which are modified.
897 906 lfcommands.cachelfiles(repo.ui, repo, node)
898 907
899 908 if kind not in archival.archivers:
900 909 raise util.Abort(_("unknown archive type '%s'") % kind)
901 910
902 911 ctx = repo[node]
903 912
904 913 if kind == 'files':
905 914 if prefix:
906 915 raise util.Abort(
907 916 _('cannot give prefix when archiving to files'))
908 917 else:
909 918 prefix = archival.tidyprefix(dest, kind, prefix)
910 919
911 920 def write(name, mode, islink, getdata):
912 921 if matchfn and not matchfn(name):
913 922 return
914 923 data = getdata()
915 924 if decode:
916 925 data = repo.wwritedata(name, data)
917 926 archiver.addfile(prefix + name, mode, islink, data)
918 927
919 928 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
920 929
921 930 if repo.ui.configbool("ui", "archivemeta", True):
922 931 def metadata():
923 932 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
924 933 hex(repo.changelog.node(0)), hex(node), ctx.branch())
925 934
926 935 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
927 936 if repo.tagtype(t) == 'global')
928 937 if not tags:
929 938 repo.ui.pushbuffer()
930 939 opts = {'template': '{latesttag}\n{latesttagdistance}',
931 940 'style': '', 'patch': None, 'git': None}
932 941 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
933 942 ltags, dist = repo.ui.popbuffer().split('\n')
934 943 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
935 944 tags += 'latesttagdistance: %s\n' % dist
936 945
937 946 return base + tags
938 947
939 948 write('.hg_archival.txt', 0644, False, metadata)
940 949
941 950 for f in ctx:
942 951 ff = ctx.flags(f)
943 952 getdata = ctx[f].data
944 953 if lfutil.isstandin(f):
945 954 path = lfutil.findfile(repo, getdata().strip())
946 955 if path is None:
947 956 raise util.Abort(
948 957 _('largefile %s not found in repo store or system cache')
949 958 % lfutil.splitstandin(f))
950 959 f = lfutil.splitstandin(f)
951 960
952 961 def getdatafn():
953 962 fd = None
954 963 try:
955 964 fd = open(path, 'rb')
956 965 return fd.read()
957 966 finally:
958 967 if fd:
959 968 fd.close()
960 969
961 970 getdata = getdatafn
962 971 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
963 972
964 973 if subrepos:
965 974 for subpath in sorted(ctx.substate):
966 975 sub = ctx.sub(subpath)
967 976 submatch = match_.narrowmatcher(subpath, matchfn)
968 977 sub.archive(archiver, prefix, submatch)
969 978
970 979 archiver.done()
971 980
972 981 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None):
973 982 repo._get(repo._state + ('hg',))
974 983 rev = repo._state[1]
975 984 ctx = repo._repo[rev]
976 985
977 986 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
978 987
979 988 def write(name, mode, islink, getdata):
980 989 # At this point, the standin has been replaced with the largefile name,
981 990 # so the normal matcher works here without the lfutil variants.
982 991 if match and not match(f):
983 992 return
984 993 data = getdata()
985 994
986 995 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
987 996
988 997 for f in ctx:
989 998 ff = ctx.flags(f)
990 999 getdata = ctx[f].data
991 1000 if lfutil.isstandin(f):
992 1001 path = lfutil.findfile(repo._repo, getdata().strip())
993 1002 if path is None:
994 1003 raise util.Abort(
995 1004 _('largefile %s not found in repo store or system cache')
996 1005 % lfutil.splitstandin(f))
997 1006 f = lfutil.splitstandin(f)
998 1007
999 1008 def getdatafn():
1000 1009 fd = None
1001 1010 try:
1002 1011 fd = open(os.path.join(prefix, path), 'rb')
1003 1012 return fd.read()
1004 1013 finally:
1005 1014 if fd:
1006 1015 fd.close()
1007 1016
1008 1017 getdata = getdatafn
1009 1018
1010 1019 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
1011 1020
1012 1021 for subpath in sorted(ctx.substate):
1013 1022 sub = ctx.sub(subpath)
1014 1023 submatch = match_.narrowmatcher(subpath, match)
1015 1024 sub.archive(archiver, os.path.join(prefix, repo._path) + '/', submatch)
1016 1025
1017 1026 # If a largefile is modified, the change is not reflected in its
1018 1027 # standin until a commit. cmdutil.bailifchanged() raises an exception
1019 1028 # if the repo has uncommitted changes. Wrap it to also check if
1020 1029 # largefiles were changed. This is used by bisect, backout and fetch.
1021 1030 def overridebailifchanged(orig, repo):
1022 1031 orig(repo)
1023 1032 repo.lfstatus = True
1024 1033 s = repo.status()
1025 1034 repo.lfstatus = False
1026 1035 if s.modified or s.added or s.removed or s.deleted:
1027 1036 raise util.Abort(_('uncommitted changes'))
1028 1037
1029 1038 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly):
1030 1039 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1031 1040 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly)
1032 1041 m = composelargefilematcher(match, repo[None].manifest())
1033 1042
1034 1043 try:
1035 1044 repo.lfstatus = True
1036 1045 s = repo.status(match=m, clean=True)
1037 1046 finally:
1038 1047 repo.lfstatus = False
1039 1048 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1040 1049 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
1041 1050
1042 1051 for f in forget:
1043 1052 if lfutil.standin(f) not in repo.dirstate and not \
1044 1053 repo.wvfs.isdir(lfutil.standin(f)):
1045 1054 ui.warn(_('not removing %s: file is already untracked\n')
1046 1055 % m.rel(f))
1047 1056 bad.append(f)
1048 1057
1049 1058 for f in forget:
1050 1059 if ui.verbose or not m.exact(f):
1051 1060 ui.status(_('removing %s\n') % m.rel(f))
1052 1061
1053 1062 # Need to lock because standin files are deleted then removed from the
1054 1063 # repository and we could race in-between.
1055 1064 wlock = repo.wlock()
1056 1065 try:
1057 1066 lfdirstate = lfutil.openlfdirstate(ui, repo)
1058 1067 for f in forget:
1059 1068 if lfdirstate[f] == 'a':
1060 1069 lfdirstate.drop(f)
1061 1070 else:
1062 1071 lfdirstate.remove(f)
1063 1072 lfdirstate.write()
1064 1073 standins = [lfutil.standin(f) for f in forget]
1065 1074 for f in standins:
1066 1075 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1067 1076 rejected = repo[None].forget(standins)
1068 1077 finally:
1069 1078 wlock.release()
1070 1079
1071 1080 bad.extend(f for f in rejected if f in m.files())
1072 1081 forgot.extend(f for f in forget if f not in rejected)
1073 1082 return bad, forgot
1074 1083
1075 1084 def _getoutgoings(repo, other, missing, addfunc):
1076 1085 """get pairs of filename and largefile hash in outgoing revisions
1077 1086 in 'missing'.
1078 1087
1079 1088 largefiles already existing on 'other' repository are ignored.
1080 1089
1081 1090 'addfunc' is invoked with each unique pairs of filename and
1082 1091 largefile hash value.
1083 1092 """
1084 1093 knowns = set()
1085 1094 lfhashes = set()
1086 1095 def dedup(fn, lfhash):
1087 1096 k = (fn, lfhash)
1088 1097 if k not in knowns:
1089 1098 knowns.add(k)
1090 1099 lfhashes.add(lfhash)
1091 1100 lfutil.getlfilestoupload(repo, missing, dedup)
1092 1101 if lfhashes:
1093 1102 lfexists = basestore._openstore(repo, other).exists(lfhashes)
1094 1103 for fn, lfhash in knowns:
1095 1104 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1096 1105 addfunc(fn, lfhash)
1097 1106
1098 1107 def outgoinghook(ui, repo, other, opts, missing):
1099 1108 if opts.pop('large', None):
1100 1109 lfhashes = set()
1101 1110 if ui.debugflag:
1102 1111 toupload = {}
1103 1112 def addfunc(fn, lfhash):
1104 1113 if fn not in toupload:
1105 1114 toupload[fn] = []
1106 1115 toupload[fn].append(lfhash)
1107 1116 lfhashes.add(lfhash)
1108 1117 def showhashes(fn):
1109 1118 for lfhash in sorted(toupload[fn]):
1110 1119 ui.debug(' %s\n' % (lfhash))
1111 1120 else:
1112 1121 toupload = set()
1113 1122 def addfunc(fn, lfhash):
1114 1123 toupload.add(fn)
1115 1124 lfhashes.add(lfhash)
1116 1125 def showhashes(fn):
1117 1126 pass
1118 1127 _getoutgoings(repo, other, missing, addfunc)
1119 1128
1120 1129 if not toupload:
1121 1130 ui.status(_('largefiles: no files to upload\n'))
1122 1131 else:
1123 1132 ui.status(_('largefiles to upload (%d entities):\n')
1124 1133 % (len(lfhashes)))
1125 1134 for file in sorted(toupload):
1126 1135 ui.status(lfutil.splitstandin(file) + '\n')
1127 1136 showhashes(file)
1128 1137 ui.status('\n')
1129 1138
1130 1139 def summaryremotehook(ui, repo, opts, changes):
1131 1140 largeopt = opts.get('large', False)
1132 1141 if changes is None:
1133 1142 if largeopt:
1134 1143 return (False, True) # only outgoing check is needed
1135 1144 else:
1136 1145 return (False, False)
1137 1146 elif largeopt:
1138 1147 url, branch, peer, outgoing = changes[1]
1139 1148 if peer is None:
1140 1149 # i18n: column positioning for "hg summary"
1141 1150 ui.status(_('largefiles: (no remote repo)\n'))
1142 1151 return
1143 1152
1144 1153 toupload = set()
1145 1154 lfhashes = set()
1146 1155 def addfunc(fn, lfhash):
1147 1156 toupload.add(fn)
1148 1157 lfhashes.add(lfhash)
1149 1158 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1150 1159
1151 1160 if not toupload:
1152 1161 # i18n: column positioning for "hg summary"
1153 1162 ui.status(_('largefiles: (no files to upload)\n'))
1154 1163 else:
1155 1164 # i18n: column positioning for "hg summary"
1156 1165 ui.status(_('largefiles: %d entities for %d files to upload\n')
1157 1166 % (len(lfhashes), len(toupload)))
1158 1167
1159 1168 def overridesummary(orig, ui, repo, *pats, **opts):
1160 1169 try:
1161 1170 repo.lfstatus = True
1162 1171 orig(ui, repo, *pats, **opts)
1163 1172 finally:
1164 1173 repo.lfstatus = False
1165 1174
1166 1175 def scmutiladdremove(orig, repo, matcher, prefix, opts={}, dry_run=None,
1167 1176 similarity=None):
1168 1177 if not lfutil.islfilesrepo(repo):
1169 1178 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1170 1179 # Get the list of missing largefiles so we can remove them
1171 1180 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1172 1181 unsure, s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
1173 1182 False, False, False)
1174 1183
1175 1184 # Call into the normal remove code, but the removing of the standin, we want
1176 1185 # to have handled by original addremove. Monkey patching here makes sure
1177 1186 # we don't remove the standin in the largefiles code, preventing a very
1178 1187 # confused state later.
1179 1188 if s.deleted:
1180 1189 m = copy.copy(matcher)
1181 1190
1182 1191 # The m._files and m._map attributes are not changed to the deleted list
1183 1192 # because that affects the m.exact() test, which in turn governs whether
1184 1193 # or not the file name is printed, and how. Simply limit the original
1185 1194 # matches to those in the deleted status list.
1186 1195 matchfn = m.matchfn
1187 1196 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1188 1197
1189 1198 removelargefiles(repo.ui, repo, True, m, **opts)
1190 1199 # Call into the normal add code, and any files that *should* be added as
1191 1200 # largefiles will be
1192 1201 added, bad = addlargefiles(repo.ui, repo, True, matcher, **opts)
1193 1202 # Now that we've handled largefiles, hand off to the original addremove
1194 1203 # function to take care of the rest. Make sure it doesn't do anything with
1195 1204 # largefiles by passing a matcher that will ignore them.
1196 1205 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1197 1206 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1198 1207
1199 1208 # Calling purge with --all will cause the largefiles to be deleted.
1200 1209 # Override repo.status to prevent this from happening.
1201 1210 def overridepurge(orig, ui, repo, *dirs, **opts):
1202 1211 # XXX Monkey patching a repoview will not work. The assigned attribute will
1203 1212 # be set on the unfiltered repo, but we will only lookup attributes in the
1204 1213 # unfiltered repo if the lookup in the repoview object itself fails. As the
1205 1214 # monkey patched method exists on the repoview class the lookup will not
1206 1215 # fail. As a result, the original version will shadow the monkey patched
1207 1216 # one, defeating the monkey patch.
1208 1217 #
1209 1218 # As a work around we use an unfiltered repo here. We should do something
1210 1219 # cleaner instead.
1211 1220 repo = repo.unfiltered()
1212 1221 oldstatus = repo.status
1213 1222 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1214 1223 clean=False, unknown=False, listsubrepos=False):
1215 1224 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1216 1225 listsubrepos)
1217 1226 lfdirstate = lfutil.openlfdirstate(ui, repo)
1218 1227 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1219 1228 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1220 1229 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1221 1230 unknown, ignored, r.clean)
1222 1231 repo.status = overridestatus
1223 1232 orig(ui, repo, *dirs, **opts)
1224 1233 repo.status = oldstatus
1225 1234 def overriderollback(orig, ui, repo, **opts):
1226 1235 wlock = repo.wlock()
1227 1236 try:
1228 1237 before = repo.dirstate.parents()
1229 1238 orphans = set(f for f in repo.dirstate
1230 1239 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1231 1240 result = orig(ui, repo, **opts)
1232 1241 after = repo.dirstate.parents()
1233 1242 if before == after:
1234 1243 return result # no need to restore standins
1235 1244
1236 1245 pctx = repo['.']
1237 1246 for f in repo.dirstate:
1238 1247 if lfutil.isstandin(f):
1239 1248 orphans.discard(f)
1240 1249 if repo.dirstate[f] == 'r':
1241 1250 repo.wvfs.unlinkpath(f, ignoremissing=True)
1242 1251 elif f in pctx:
1243 1252 fctx = pctx[f]
1244 1253 repo.wwrite(f, fctx.data(), fctx.flags())
1245 1254 else:
1246 1255 # content of standin is not so important in 'a',
1247 1256 # 'm' or 'n' (coming from the 2nd parent) cases
1248 1257 lfutil.writestandin(repo, f, '', False)
1249 1258 for standin in orphans:
1250 1259 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1251 1260
1252 1261 lfdirstate = lfutil.openlfdirstate(ui, repo)
1253 1262 orphans = set(lfdirstate)
1254 1263 lfiles = lfutil.listlfiles(repo)
1255 1264 for file in lfiles:
1256 1265 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1257 1266 orphans.discard(file)
1258 1267 for lfile in orphans:
1259 1268 lfdirstate.drop(lfile)
1260 1269 lfdirstate.write()
1261 1270 finally:
1262 1271 wlock.release()
1263 1272 return result
1264 1273
1265 1274 def overridetransplant(orig, ui, repo, *revs, **opts):
1266 1275 resuming = opts.get('continue')
1267 1276 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1268 1277 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1269 1278 try:
1270 1279 result = orig(ui, repo, *revs, **opts)
1271 1280 finally:
1272 1281 repo._lfstatuswriters.pop()
1273 1282 repo._lfcommithooks.pop()
1274 1283 return result
1275 1284
1276 1285 def overridecat(orig, ui, repo, file1, *pats, **opts):
1277 1286 ctx = scmutil.revsingle(repo, opts.get('rev'))
1278 1287 err = 1
1279 1288 notbad = set()
1280 1289 m = scmutil.match(ctx, (file1,) + pats, opts)
1281 1290 origmatchfn = m.matchfn
1282 1291 def lfmatchfn(f):
1283 1292 if origmatchfn(f):
1284 1293 return True
1285 1294 lf = lfutil.splitstandin(f)
1286 1295 if lf is None:
1287 1296 return False
1288 1297 notbad.add(lf)
1289 1298 return origmatchfn(lf)
1290 1299 m.matchfn = lfmatchfn
1291 1300 origbadfn = m.bad
1292 1301 def lfbadfn(f, msg):
1293 1302 if not f in notbad:
1294 1303 origbadfn(f, msg)
1295 1304 m.bad = lfbadfn
1296 1305 for f in ctx.walk(m):
1297 1306 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1298 1307 pathname=f)
1299 1308 lf = lfutil.splitstandin(f)
1300 1309 if lf is None or origmatchfn(f):
1301 1310 # duplicating unreachable code from commands.cat
1302 1311 data = ctx[f].data()
1303 1312 if opts.get('decode'):
1304 1313 data = repo.wwritedata(f, data)
1305 1314 fp.write(data)
1306 1315 else:
1307 1316 hash = lfutil.readstandin(repo, lf, ctx.rev())
1308 1317 if not lfutil.inusercache(repo.ui, hash):
1309 1318 store = basestore._openstore(repo)
1310 1319 success, missing = store.get([(lf, hash)])
1311 1320 if len(success) != 1:
1312 1321 raise util.Abort(
1313 1322 _('largefile %s is not in cache and could not be '
1314 1323 'downloaded') % lf)
1315 1324 path = lfutil.usercachepath(repo.ui, hash)
1316 1325 fpin = open(path, "rb")
1317 1326 for chunk in util.filechunkiter(fpin, 128 * 1024):
1318 1327 fp.write(chunk)
1319 1328 fpin.close()
1320 1329 fp.close()
1321 1330 err = 0
1322 1331 return err
1323 1332
1324 1333 def mergeupdate(orig, repo, node, branchmerge, force, partial,
1325 1334 *args, **kwargs):
1326 1335 wlock = repo.wlock()
1327 1336 try:
1328 1337 # branch | | |
1329 1338 # merge | force | partial | action
1330 1339 # -------+-------+---------+--------------
1331 1340 # x | x | x | linear-merge
1332 1341 # o | x | x | branch-merge
1333 1342 # x | o | x | overwrite (as clean update)
1334 1343 # o | o | x | force-branch-merge (*1)
1335 1344 # x | x | o | (*)
1336 1345 # o | x | o | (*)
1337 1346 # x | o | o | overwrite (as revert)
1338 1347 # o | o | o | (*)
1339 1348 #
1340 1349 # (*) don't care
1341 1350 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1342 1351
1343 1352 linearmerge = not branchmerge and not force and not partial
1344 1353
1345 1354 if linearmerge or (branchmerge and force and not partial):
1346 1355 # update standins for linear-merge or force-branch-merge,
1347 1356 # because largefiles in the working directory may be modified
1348 1357 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1349 1358 unsure, s = lfdirstate.status(match_.always(repo.root,
1350 1359 repo.getcwd()),
1351 1360 [], False, False, False)
1352 1361 pctx = repo['.']
1353 1362 for lfile in unsure + s.modified:
1354 1363 lfileabs = repo.wvfs.join(lfile)
1355 1364 if not os.path.exists(lfileabs):
1356 1365 continue
1357 1366 lfhash = lfutil.hashrepofile(repo, lfile)
1358 1367 standin = lfutil.standin(lfile)
1359 1368 lfutil.writestandin(repo, standin, lfhash,
1360 1369 lfutil.getexecutable(lfileabs))
1361 1370 if (standin in pctx and
1362 1371 lfhash == lfutil.readstandin(repo, lfile, '.')):
1363 1372 lfdirstate.normal(lfile)
1364 1373 for lfile in s.added:
1365 1374 lfutil.updatestandin(repo, lfutil.standin(lfile))
1366 1375 lfdirstate.write()
1367 1376
1368 1377 if linearmerge:
1369 1378 # Only call updatelfiles on the standins that have changed
1370 1379 # to save time
1371 1380 oldstandins = lfutil.getstandinsstate(repo)
1372 1381
1373 1382 result = orig(repo, node, branchmerge, force, partial, *args, **kwargs)
1374 1383
1375 1384 filelist = None
1376 1385 if linearmerge:
1377 1386 newstandins = lfutil.getstandinsstate(repo)
1378 1387 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1379 1388
1380 1389 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1381 1390 normallookup=partial, checked=linearmerge)
1382 1391
1383 1392 return result
1384 1393 finally:
1385 1394 wlock.release()
1386 1395
1387 1396 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1388 1397 result = orig(repo, files, *args, **kwargs)
1389 1398
1390 1399 filelist = [lfutil.splitstandin(f) for f in files if lfutil.isstandin(f)]
1391 1400 if filelist:
1392 1401 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1393 1402 printmessage=False, normallookup=True)
1394 1403
1395 1404 return result
@@ -1,172 +1,174 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 '''setup for largefiles extension: uisetup'''
10 10
11 11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
12 httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo
12 httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo, copies
13 13 from mercurial.i18n import _
14 14 from mercurial.hgweb import hgweb_mod, webcommands
15 15
16 16 import overrides
17 17 import proto
18 18
19 19 def uisetup(ui):
20 20 # Disable auto-status for some commands which assume that all
21 21 # files in the result are under Mercurial's control
22 22
23 23 entry = extensions.wrapcommand(commands.table, 'add',
24 24 overrides.overrideadd)
25 25 addopt = [('', 'large', None, _('add as largefile')),
26 26 ('', 'normal', None, _('add as normal file')),
27 27 ('', 'lfsize', '', _('add all files above this size '
28 28 '(in megabytes) as largefiles '
29 29 '(default: 10)'))]
30 30 entry[1].extend(addopt)
31 31
32 32 # The scmutil function is called both by the (trivial) addremove command,
33 33 # and in the process of handling commit -A (issue3542)
34 34 entry = extensions.wrapfunction(scmutil, 'addremove',
35 35 overrides.scmutiladdremove)
36 36 extensions.wrapfunction(cmdutil, 'add', overrides.cmdutiladd)
37 37 extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
38 38 extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
39 39
40 extensions.wrapfunction(copies, 'pathcopies', overrides.copiespathcopies)
41
40 42 # Subrepos call status function
41 43 entry = extensions.wrapcommand(commands.table, 'status',
42 44 overrides.overridestatus)
43 45 entry = extensions.wrapfunction(subrepo.hgsubrepo, 'status',
44 46 overrides.overridestatusfn)
45 47
46 48 entry = extensions.wrapcommand(commands.table, 'log',
47 49 overrides.overridelog)
48 50 entry = extensions.wrapcommand(commands.table, 'rollback',
49 51 overrides.overriderollback)
50 52 entry = extensions.wrapcommand(commands.table, 'verify',
51 53 overrides.overrideverify)
52 54
53 55 verifyopt = [('', 'large', None,
54 56 _('verify that all largefiles in current revision exists')),
55 57 ('', 'lfa', None,
56 58 _('verify largefiles in all revisions, not just current')),
57 59 ('', 'lfc', None,
58 60 _('verify local largefile contents, not just existence'))]
59 61 entry[1].extend(verifyopt)
60 62
61 63 entry = extensions.wrapcommand(commands.table, 'debugstate',
62 64 overrides.overridedebugstate)
63 65 debugstateopt = [('', 'large', None, _('display largefiles dirstate'))]
64 66 entry[1].extend(debugstateopt)
65 67
66 68 outgoing = lambda orgfunc, *arg, **kwargs: orgfunc(*arg, **kwargs)
67 69 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
68 70 outgoingopt = [('', 'large', None, _('display outgoing largefiles'))]
69 71 entry[1].extend(outgoingopt)
70 72 cmdutil.outgoinghooks.add('largefiles', overrides.outgoinghook)
71 73 entry = extensions.wrapcommand(commands.table, 'summary',
72 74 overrides.overridesummary)
73 75 summaryopt = [('', 'large', None, _('display outgoing largefiles'))]
74 76 entry[1].extend(summaryopt)
75 77 cmdutil.summaryremotehooks.add('largefiles', overrides.summaryremotehook)
76 78
77 79 entry = extensions.wrapcommand(commands.table, 'update',
78 80 overrides.overrideupdate)
79 81 entry = extensions.wrapcommand(commands.table, 'pull',
80 82 overrides.overridepull)
81 83 pullopt = [('', 'all-largefiles', None,
82 84 _('download all pulled versions of largefiles (DEPRECATED)')),
83 85 ('', 'lfrev', [],
84 86 _('download largefiles for these revisions'), _('REV'))]
85 87 entry[1].extend(pullopt)
86 88 revset.symbols['pulled'] = overrides.pulledrevsetsymbol
87 89
88 90 entry = extensions.wrapcommand(commands.table, 'clone',
89 91 overrides.overrideclone)
90 92 cloneopt = [('', 'all-largefiles', None,
91 93 _('download all versions of all largefiles'))]
92 94 entry[1].extend(cloneopt)
93 95 entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
94 96
95 97 entry = extensions.wrapcommand(commands.table, 'cat',
96 98 overrides.overridecat)
97 99 entry = extensions.wrapfunction(merge, '_checkunknownfile',
98 100 overrides.overridecheckunknownfile)
99 101 entry = extensions.wrapfunction(merge, 'calculateupdates',
100 102 overrides.overridecalculateupdates)
101 103 entry = extensions.wrapfunction(merge, 'recordupdates',
102 104 overrides.mergerecordupdates)
103 105 entry = extensions.wrapfunction(merge, 'update',
104 106 overrides.mergeupdate)
105 107 entry = extensions.wrapfunction(filemerge, 'filemerge',
106 108 overrides.overridefilemerge)
107 109 entry = extensions.wrapfunction(cmdutil, 'copy',
108 110 overrides.overridecopy)
109 111
110 112 # Summary calls dirty on the subrepos
111 113 entry = extensions.wrapfunction(subrepo.hgsubrepo, 'dirty',
112 114 overrides.overridedirty)
113 115
114 116 # Backout calls revert so we need to override both the command and the
115 117 # function
116 118 entry = extensions.wrapcommand(commands.table, 'revert',
117 119 overrides.overriderevert)
118 120 entry = extensions.wrapfunction(commands, 'revert',
119 121 overrides.overriderevert)
120 122
121 123 extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
122 124 extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
123 125 overrides.hgsubrepoarchive)
124 126 extensions.wrapfunction(cmdutil, 'bailifchanged',
125 127 overrides.overridebailifchanged)
126 128
127 129 extensions.wrapfunction(scmutil, 'marktouched',
128 130 overrides.scmutilmarktouched)
129 131
130 132 # create the new wireproto commands ...
131 133 wireproto.commands['putlfile'] = (proto.putlfile, 'sha')
132 134 wireproto.commands['getlfile'] = (proto.getlfile, 'sha')
133 135 wireproto.commands['statlfile'] = (proto.statlfile, 'sha')
134 136
135 137 # ... and wrap some existing ones
136 138 wireproto.commands['capabilities'] = (proto.capabilities, '')
137 139 wireproto.commands['heads'] = (proto.heads, '')
138 140 wireproto.commands['lheads'] = (wireproto.heads, '')
139 141
140 142 # make putlfile behave the same as push and {get,stat}lfile behave
141 143 # the same as pull w.r.t. permissions checks
142 144 hgweb_mod.perms['putlfile'] = 'push'
143 145 hgweb_mod.perms['getlfile'] = 'pull'
144 146 hgweb_mod.perms['statlfile'] = 'pull'
145 147
146 148 extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath)
147 149
148 150 # the hello wireproto command uses wireproto.capabilities, so it won't see
149 151 # our largefiles capability unless we replace the actual function as well.
150 152 proto.capabilitiesorig = wireproto.capabilities
151 153 wireproto.capabilities = proto.capabilities
152 154
153 155 # can't do this in reposetup because it needs to have happened before
154 156 # wirerepo.__init__ is called
155 157 proto.ssholdcallstream = sshpeer.sshpeer._callstream
156 158 proto.httpoldcallstream = httppeer.httppeer._callstream
157 159 sshpeer.sshpeer._callstream = proto.sshrepocallstream
158 160 httppeer.httppeer._callstream = proto.httprepocallstream
159 161
160 162 # override some extensions' stuff as well
161 163 for name, module in extensions.extensions():
162 164 if name == 'purge':
163 165 extensions.wrapcommand(getattr(module, 'cmdtable'), 'purge',
164 166 overrides.overridepurge)
165 167 if name == 'rebase':
166 168 extensions.wrapcommand(getattr(module, 'cmdtable'), 'rebase',
167 169 overrides.overriderebase)
168 170 extensions.wrapfunction(module, 'rebase',
169 171 overrides.overriderebase)
170 172 if name == 'transplant':
171 173 extensions.wrapcommand(getattr(module, 'cmdtable'), 'transplant',
172 174 overrides.overridetransplant)
@@ -1,422 +1,442 b''
1 1 Preparing the subrepository 'sub2'
2 2
3 3 $ hg init sub2
4 4 $ echo sub2 > sub2/sub2
5 5 $ hg add -R sub2
6 6 adding sub2/sub2 (glob)
7 7 $ hg commit -R sub2 -m "sub2 import"
8 8
9 9 Preparing the 'sub1' repo which depends on the subrepo 'sub2'
10 10
11 11 $ hg init sub1
12 12 $ echo sub1 > sub1/sub1
13 13 $ echo "sub2 = ../sub2" > sub1/.hgsub
14 14 $ hg clone sub2 sub1/sub2
15 15 updating to branch default
16 16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 17 $ hg add -R sub1
18 18 adding sub1/.hgsub (glob)
19 19 adding sub1/sub1 (glob)
20 20 $ hg commit -R sub1 -m "sub1 import"
21 21
22 22 Preparing the 'main' repo which depends on the subrepo 'sub1'
23 23
24 24 $ hg init main
25 25 $ echo main > main/main
26 26 $ echo "sub1 = ../sub1" > main/.hgsub
27 27 $ hg clone sub1 main/sub1
28 28 updating to branch default
29 29 cloning subrepo sub2 from $TESTTMP/sub2
30 30 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 31 $ hg add -R main
32 32 adding main/.hgsub (glob)
33 33 adding main/main (glob)
34 34 $ hg commit -R main -m "main import"
35 35
36 36 Cleaning both repositories, just as a clone -U
37 37
38 38 $ hg up -C -R sub2 null
39 39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 40 $ hg up -C -R sub1 null
41 41 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
42 42 $ hg up -C -R main null
43 43 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
44 44 $ rm -rf main/sub1
45 45 $ rm -rf sub1/sub2
46 46
47 47 Clone main
48 48
49 49 $ hg --config extensions.largefiles= clone main cloned
50 50 updating to branch default
51 51 cloning subrepo sub1 from $TESTTMP/sub1
52 52 cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
53 53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54
55 55 Largefiles is NOT enabled in the clone if the source repo doesn't require it
56 56 $ cat cloned/.hg/hgrc
57 57 # example repository config (see "hg help config" for more info)
58 58 [paths]
59 59 default = $TESTTMP/main (glob)
60 60
61 61 # path aliases to other clones of this repo in URLs or filesystem paths
62 62 # (see "hg help config.paths" for more info)
63 63 #
64 64 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
65 65 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
66 66 # my-clone = /home/jdoe/jdoes-clone
67 67
68 68 [ui]
69 69 # name and email (local to this repository, optional), e.g.
70 70 # username = Jane Doe <jdoe@example.com>
71 71
72 72 Checking cloned repo ids
73 73
74 74 $ printf "cloned " ; hg id -R cloned
75 75 cloned 7f491f53a367 tip
76 76 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
77 77 cloned/sub1 fc3b4ce2696f tip
78 78 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
79 79 cloned/sub1/sub2 c57a0840e3ba tip
80 80
81 81 debugsub output for main and sub1
82 82
83 83 $ hg debugsub -R cloned
84 84 path sub1
85 85 source ../sub1
86 86 revision fc3b4ce2696f7741438c79207583768f2ce6b0dd
87 87 $ hg debugsub -R cloned/sub1
88 88 path sub2
89 89 source ../sub2
90 90 revision c57a0840e3badd667ef3c3ef65471609acb2ba3c
91 91
92 92 Modifying deeply nested 'sub2'
93 93
94 94 $ echo modified > cloned/sub1/sub2/sub2
95 95 $ hg commit --subrepos -m "deep nested modif should trigger a commit" -R cloned
96 96 committing subrepository sub1
97 97 committing subrepository sub1/sub2 (glob)
98 98
99 99 Checking modified node ids
100 100
101 101 $ printf "cloned " ; hg id -R cloned
102 102 cloned ffe6649062fe tip
103 103 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
104 104 cloned/sub1 2ecb03bf44a9 tip
105 105 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
106 106 cloned/sub1/sub2 53dd3430bcaf tip
107 107
108 108 debugsub output for main and sub1
109 109
110 110 $ hg debugsub -R cloned
111 111 path sub1
112 112 source ../sub1
113 113 revision 2ecb03bf44a94e749e8669481dd9069526ce7cb9
114 114 $ hg debugsub -R cloned/sub1
115 115 path sub2
116 116 source ../sub2
117 117 revision 53dd3430bcaf5ab4a7c48262bcad6d441f510487
118 118
119 119 Check that deep archiving works
120 120
121 121 $ cd cloned
122 122 $ echo 'test' > sub1/sub2/test.txt
123 123 $ hg --config extensions.largefiles=! add sub1/sub2/test.txt
124 124 $ mkdir sub1/sub2/folder
125 125 $ echo 'subfolder' > sub1/sub2/folder/test.txt
126 126 $ hg ci -ASm "add test.txt"
127 127 adding sub1/sub2/folder/test.txt
128 128 committing subrepository sub1
129 129 committing subrepository sub1/sub2 (glob)
130 130
131 131 .. but first take a detour through some deep removal testing
132 132
133 133 $ hg remove -S -I 're:.*.txt' .
134 134 removing sub1/sub2/folder/test.txt (glob)
135 135 removing sub1/sub2/test.txt (glob)
136 136 $ hg status -S
137 137 R sub1/sub2/folder/test.txt
138 138 R sub1/sub2/test.txt
139 139 $ hg update -Cq
140 140 $ hg remove -I 're:.*.txt' sub1
141 141 $ hg status -S
142 142 $ hg remove sub1/sub2/folder/test.txt
143 143 $ hg remove sub1/.hgsubstate
144 144 $ hg status -S
145 145 R sub1/.hgsubstate
146 146 R sub1/sub2/folder/test.txt
147 147 $ hg update -Cq
148 148 $ touch sub1/foo
149 149 $ hg forget sub1/sub2/folder/test.txt
150 150 $ rm sub1/sub2/test.txt
151 151
152 152 Test relative path printing + subrepos
153 153 $ mkdir -p foo/bar
154 154 $ cd foo
155 155 $ touch bar/abc
156 156 $ hg addremove -S ..
157 157 adding ../sub1/sub2/folder/test.txt (glob)
158 158 removing ../sub1/sub2/test.txt (glob)
159 159 adding ../sub1/foo (glob)
160 160 adding bar/abc (glob)
161 161 $ cd ..
162 162 $ hg status -S
163 163 A foo/bar/abc
164 164 A sub1/foo
165 165 R sub1/sub2/test.txt
166 166 $ hg update -Cq
167 167 $ touch sub1/sub2/folder/bar
168 168 $ hg addremove sub1/sub2
169 169 adding sub1/sub2/folder/bar (glob)
170 170 $ hg status -S
171 171 A sub1/sub2/folder/bar
172 172 ? foo/bar/abc
173 173 ? sub1/foo
174 174 $ hg update -Cq
175 175 $ hg addremove sub1
176 176 adding sub1/sub2/folder/bar (glob)
177 177 adding sub1/foo (glob)
178 178 $ hg update -Cq
179 179 $ rm sub1/sub2/folder/test.txt
180 180 $ rm sub1/sub2/test.txt
181 181 $ hg ci -ASm "remove test.txt"
182 182 adding sub1/sub2/folder/bar
183 183 removing sub1/sub2/folder/test.txt
184 184 removing sub1/sub2/test.txt
185 185 adding sub1/foo
186 186 adding foo/bar/abc
187 187 committing subrepository sub1
188 188 committing subrepository sub1/sub2 (glob)
189 189 $ hg rollback -q
190 190 $ hg up -Cq
191 191
192 192 $ hg --config extensions.largefiles=! archive -S ../archive_all
193 193 $ find ../archive_all | sort
194 194 ../archive_all
195 195 ../archive_all/.hg_archival.txt
196 196 ../archive_all/.hgsub
197 197 ../archive_all/.hgsubstate
198 198 ../archive_all/main
199 199 ../archive_all/sub1
200 200 ../archive_all/sub1/.hgsub
201 201 ../archive_all/sub1/.hgsubstate
202 202 ../archive_all/sub1/sub1
203 203 ../archive_all/sub1/sub2
204 204 ../archive_all/sub1/sub2/folder
205 205 ../archive_all/sub1/sub2/folder/test.txt
206 206 ../archive_all/sub1/sub2/sub2
207 207 ../archive_all/sub1/sub2/test.txt
208 208
209 209 Check that archive -X works in deep subrepos
210 210
211 211 $ hg --config extensions.largefiles=! archive -S -X '**test*' ../archive_exclude
212 212 $ find ../archive_exclude | sort
213 213 ../archive_exclude
214 214 ../archive_exclude/.hg_archival.txt
215 215 ../archive_exclude/.hgsub
216 216 ../archive_exclude/.hgsubstate
217 217 ../archive_exclude/main
218 218 ../archive_exclude/sub1
219 219 ../archive_exclude/sub1/.hgsub
220 220 ../archive_exclude/sub1/.hgsubstate
221 221 ../archive_exclude/sub1/sub1
222 222 ../archive_exclude/sub1/sub2
223 223 ../archive_exclude/sub1/sub2/sub2
224 224
225 225 $ hg --config extensions.largefiles=! archive -S -I '**test*' ../archive_include
226 226 $ find ../archive_include | sort
227 227 ../archive_include
228 228 ../archive_include/sub1
229 229 ../archive_include/sub1/sub2
230 230 ../archive_include/sub1/sub2/folder
231 231 ../archive_include/sub1/sub2/folder/test.txt
232 232 ../archive_include/sub1/sub2/test.txt
233 233
234 234 Check that deep archive works with largefiles (which overrides hgsubrepo impl)
235 235 This also tests the repo.ui regression in 43fb170a23bd, and that lf subrepo
236 236 subrepos are archived properly.
237 237 Note that add --large through a subrepo currently adds the file as a normal file
238 238
239 239 $ echo "large" > sub1/sub2/large.bin
240 240 $ hg --config extensions.largefiles= add --large -R sub1/sub2 sub1/sub2/large.bin
241 241 $ echo "large" > large.bin
242 242 $ hg --config extensions.largefiles= add --large large.bin
243 243 $ hg --config extensions.largefiles= ci -S -m "add large files"
244 244 committing subrepository sub1
245 245 committing subrepository sub1/sub2 (glob)
246 246
247 247 $ hg --config extensions.largefiles= archive -S ../archive_lf
248 248 $ find ../archive_lf | sort
249 249 ../archive_lf
250 250 ../archive_lf/.hg_archival.txt
251 251 ../archive_lf/.hgsub
252 252 ../archive_lf/.hgsubstate
253 253 ../archive_lf/large.bin
254 254 ../archive_lf/main
255 255 ../archive_lf/sub1
256 256 ../archive_lf/sub1/.hgsub
257 257 ../archive_lf/sub1/.hgsubstate
258 258 ../archive_lf/sub1/sub1
259 259 ../archive_lf/sub1/sub2
260 260 ../archive_lf/sub1/sub2/folder
261 261 ../archive_lf/sub1/sub2/folder/test.txt
262 262 ../archive_lf/sub1/sub2/large.bin
263 263 ../archive_lf/sub1/sub2/sub2
264 264 ../archive_lf/sub1/sub2/test.txt
265 265 $ rm -rf ../archive_lf
266 266
267 267 Exclude large files from main and sub-sub repo
268 268
269 269 $ hg --config extensions.largefiles= archive -S -X '**.bin' ../archive_lf
270 270 $ find ../archive_lf | sort
271 271 ../archive_lf
272 272 ../archive_lf/.hg_archival.txt
273 273 ../archive_lf/.hgsub
274 274 ../archive_lf/.hgsubstate
275 275 ../archive_lf/main
276 276 ../archive_lf/sub1
277 277 ../archive_lf/sub1/.hgsub
278 278 ../archive_lf/sub1/.hgsubstate
279 279 ../archive_lf/sub1/sub1
280 280 ../archive_lf/sub1/sub2
281 281 ../archive_lf/sub1/sub2/folder
282 282 ../archive_lf/sub1/sub2/folder/test.txt
283 283 ../archive_lf/sub1/sub2/sub2
284 284 ../archive_lf/sub1/sub2/test.txt
285 285 $ rm -rf ../archive_lf
286 286
287 287 Exclude normal files from main and sub-sub repo
288 288
289 289 $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf
290 290 $ find ../archive_lf | sort
291 291 ../archive_lf
292 292 ../archive_lf/.hgsub
293 293 ../archive_lf/.hgsubstate
294 294 ../archive_lf/large.bin
295 295 ../archive_lf/main
296 296 ../archive_lf/sub1
297 297 ../archive_lf/sub1/.hgsub
298 298 ../archive_lf/sub1/.hgsubstate
299 299 ../archive_lf/sub1/sub1
300 300 ../archive_lf/sub1/sub2
301 301 ../archive_lf/sub1/sub2/large.bin
302 302 ../archive_lf/sub1/sub2/sub2
303 303 $ rm -rf ../archive_lf
304 304
305 305 Include normal files from within a largefiles subrepo
306 306
307 307 $ hg --config extensions.largefiles= archive -S -I '**.txt' ../archive_lf
308 308 $ find ../archive_lf | sort
309 309 ../archive_lf
310 310 ../archive_lf/.hg_archival.txt
311 311 ../archive_lf/sub1
312 312 ../archive_lf/sub1/sub2
313 313 ../archive_lf/sub1/sub2/folder
314 314 ../archive_lf/sub1/sub2/folder/test.txt
315 315 ../archive_lf/sub1/sub2/test.txt
316 316 $ rm -rf ../archive_lf
317 317
318 318 Include large files from within a largefiles subrepo
319 319
320 320 $ hg --config extensions.largefiles= archive -S -I '**.bin' ../archive_lf
321 321 $ find ../archive_lf | sort
322 322 ../archive_lf
323 323 ../archive_lf/large.bin
324 324 ../archive_lf/sub1
325 325 ../archive_lf/sub1/sub2
326 326 ../archive_lf/sub1/sub2/large.bin
327 327 $ rm -rf ../archive_lf
328 328
329 329 Find an exact largefile match in a largefiles subrepo
330 330
331 331 $ hg --config extensions.largefiles= archive -S -I 'sub1/sub2/large.bin' ../archive_lf
332 332 $ find ../archive_lf | sort
333 333 ../archive_lf
334 334 ../archive_lf/sub1
335 335 ../archive_lf/sub1/sub2
336 336 ../archive_lf/sub1/sub2/large.bin
337 337 $ rm -rf ../archive_lf
338 338
339 339 The local repo enables largefiles if a largefiles repo is cloned
340 340 $ hg showconfig extensions
341 341 abort: repository requires features unknown to this Mercurial: largefiles!
342 342 (see http://mercurial.selenic.com/wiki/MissingRequirement for more information)
343 343 [255]
344 344 $ hg --config extensions.largefiles= clone -qU . ../lfclone
345 345 $ cat ../lfclone/.hg/hgrc
346 346 # example repository config (see "hg help config" for more info)
347 347 [paths]
348 348 default = $TESTTMP/cloned (glob)
349 349
350 350 # path aliases to other clones of this repo in URLs or filesystem paths
351 351 # (see "hg help config.paths" for more info)
352 352 #
353 353 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
354 354 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
355 355 # my-clone = /home/jdoe/jdoes-clone
356 356
357 357 [ui]
358 358 # name and email (local to this repository, optional), e.g.
359 359 # username = Jane Doe <jdoe@example.com>
360 360
361 361 [extensions]
362 362 largefiles=
363 363
364 364 Find an exact match to a standin (should archive nothing)
365 365 $ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
366 366 $ find ../archive_lf 2> /dev/null | sort
367 367
368 368 $ cat >> $HGRCPATH <<EOF
369 369 > [extensions]
370 370 > largefiles=
371 371 > [largefiles]
372 372 > patterns=glob:**.dat
373 373 > EOF
374 374
375 375 Test forget through a deep subrepo with the largefiles extension, both a
376 376 largefile and a normal file. Then a largefile that hasn't been committed yet.
377 377 $ touch sub1/sub2/untracked.txt
378 378 $ touch sub1/sub2/large.dat
379 379 $ hg forget sub1/sub2/large.bin sub1/sub2/test.txt sub1/sub2/untracked.txt
380 380 not removing sub1/sub2/untracked.txt: file is already untracked (glob)
381 381 [1]
382 382 $ hg add --large --dry-run -v sub1/sub2/untracked.txt
383 383 adding sub1/sub2/untracked.txt as a largefile (glob)
384 384 $ hg add --large -v sub1/sub2/untracked.txt
385 385 adding sub1/sub2/untracked.txt as a largefile (glob)
386 386 $ hg add --normal -v sub1/sub2/large.dat
387 387 adding sub1/sub2/large.dat (glob)
388 388 $ hg forget -v sub1/sub2/untracked.txt
389 389 removing sub1/sub2/untracked.txt (glob)
390 390 $ hg status -S
391 391 A sub1/sub2/large.dat
392 392 R sub1/sub2/large.bin
393 393 R sub1/sub2/test.txt
394 394 ? foo/bar/abc
395 395 ? sub1/sub2/untracked.txt
396 396 $ hg add sub1/sub2
397 397 $ hg ci -Sqm 'forget testing'
398 398
399 399 Test issue4330: commit a directory where only normal files have changed
400 400 $ touch foo/bar/large.dat
401 401 $ hg add --large foo/bar/large.dat
402 402 $ hg ci -m 'add foo/bar/large.dat'
403 403 $ touch a.txt
404 404 $ touch a.dat
405 405 $ hg add -v foo/bar/abc a.txt a.dat
406 406 adding a.dat as a largefile
407 407 adding a.txt
408 408 adding foo/bar/abc (glob)
409 409 $ hg ci -m 'dir commit with only normal file deltas' foo/bar
410 410 $ hg status
411 411 A a.dat
412 412 A a.txt
413 413
414 414 Test a directory commit with a changed largefile and a changed normal file
415 415 $ echo changed > foo/bar/large.dat
416 416 $ echo changed > foo/bar/abc
417 417 $ hg ci -m 'dir commit with normal and lf file deltas' foo
418 418 $ hg status
419 419 A a.dat
420 420 A a.txt
421 421
422 $ hg ci -m "add a.*"
423 $ hg mv a.dat b.dat
424 $ hg mv foo/bar/abc foo/bar/def
425 $ hg status -C
426 A b.dat
427 a.dat
428 A foo/bar/def
429 foo/bar/abc
430 R a.dat
431 R foo/bar/abc
432
433 $ hg ci -m "move large and normal"
434 $ hg status -C --rev '.^' --rev .
435 A b.dat
436 a.dat
437 A foo/bar/def
438 foo/bar/abc
439 R a.dat
440 R foo/bar/abc
441
422 442 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now