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