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