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