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