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