##// END OF EJS Templates
largefiles: fix addremove when largefile is missing (issue3227)
Na'Tosha Bard -
r16731:dcfc70aa default
parent child Browse files
Show More
@@ -1,1065 +1,1067 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, commands, util, cmdutil, scmutil, match as match_, \
15 15 node, archival, error, merge
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18 from hgext import rebase
19 19
20 20 import lfutil
21 21 import lfcommands
22 22
23 23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24 24
25 25 def installnormalfilesmatchfn(manifest):
26 26 '''overrides scmutil.match so that the matcher it returns will ignore all
27 27 largefiles'''
28 28 oldmatch = None # for the closure
29 29 def overridematch(ctx, pats=[], opts={}, globbed=False,
30 30 default='relpath'):
31 31 match = oldmatch(ctx, pats, opts, globbed, default)
32 32 m = copy.copy(match)
33 33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
34 34 manifest)
35 35 m._files = filter(notlfile, m._files)
36 36 m._fmap = set(m._files)
37 37 origmatchfn = m.matchfn
38 38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
39 39 return m
40 40 oldmatch = installmatchfn(overridematch)
41 41
42 42 def installmatchfn(f):
43 43 oldmatch = scmutil.match
44 44 setattr(f, 'oldmatch', oldmatch)
45 45 scmutil.match = f
46 46 return oldmatch
47 47
48 48 def restorematchfn():
49 49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
50 50 was called. no-op if scmutil.match is its original function.
51 51
52 52 Note that n calls to installnormalfilesmatchfn will require n calls to
53 53 restore matchfn to reverse'''
54 54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
55 55
56 56 def addlargefiles(ui, repo, *pats, **opts):
57 57 large = opts.pop('large', None)
58 58 lfsize = lfutil.getminsize(
59 59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
60 60
61 61 lfmatcher = None
62 62 if lfutil.islfilesrepo(repo):
63 63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
64 64 if lfpats:
65 65 lfmatcher = match_.match(repo.root, '', list(lfpats))
66 66
67 67 lfnames = []
68 68 m = scmutil.match(repo[None], pats, opts)
69 69 m.bad = lambda x, y: None
70 70 wctx = repo[None]
71 71 for f in repo.walk(m):
72 72 exact = m.exact(f)
73 73 lfile = lfutil.standin(f) in wctx
74 74 nfile = f in wctx
75 75 exists = lfile or nfile
76 76
77 77 # Don't warn the user when they attempt to add a normal tracked file.
78 78 # The normal add code will do that for us.
79 79 if exact and exists:
80 80 if lfile:
81 81 ui.warn(_('%s already a largefile\n') % f)
82 82 continue
83 83
84 84 if exact or not exists:
85 85 abovemin = (lfsize and
86 86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
87 87 if large or abovemin or (lfmatcher and lfmatcher(f)):
88 88 lfnames.append(f)
89 89 if ui.verbose or not exact:
90 90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
91 91
92 92 bad = []
93 93 standins = []
94 94
95 95 # Need to lock, otherwise there could be a race condition between
96 96 # when standins are created and added to the repo.
97 97 wlock = repo.wlock()
98 98 try:
99 99 if not opts.get('dry_run'):
100 100 lfdirstate = lfutil.openlfdirstate(ui, repo)
101 101 for f in lfnames:
102 102 standinname = lfutil.standin(f)
103 103 lfutil.writestandin(repo, standinname, hash='',
104 104 executable=lfutil.getexecutable(repo.wjoin(f)))
105 105 standins.append(standinname)
106 106 if lfdirstate[f] == 'r':
107 107 lfdirstate.normallookup(f)
108 108 else:
109 109 lfdirstate.add(f)
110 110 lfdirstate.write()
111 111 bad += [lfutil.splitstandin(f)
112 112 for f in lfutil.repoadd(repo, standins)
113 113 if f in m.files()]
114 114 finally:
115 115 wlock.release()
116 116 return bad
117 117
118 118 def removelargefiles(ui, repo, *pats, **opts):
119 119 after = opts.get('after')
120 120 if not pats and not after:
121 121 raise util.Abort(_('no files specified'))
122 122 m = scmutil.match(repo[None], pats, opts)
123 123 try:
124 124 repo.lfstatus = True
125 125 s = repo.status(match=m, clean=True)
126 126 finally:
127 127 repo.lfstatus = False
128 128 manifest = repo[None].manifest()
129 129 modified, added, deleted, clean = [[f for f in list
130 130 if lfutil.standin(f) in manifest]
131 131 for list in [s[0], s[1], s[3], s[6]]]
132 132
133 133 def warn(files, reason):
134 134 for f in files:
135 135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
136 136 % (m.rel(f), reason))
137 137
138 138 if after:
139 139 remove, forget = deleted, []
140 140 warn(modified + added + clean, _('file still exists'))
141 141 else:
142 142 remove, forget = deleted + clean, []
143 143 warn(modified, _('file is modified'))
144 144 warn(added, _('file has been marked for add'))
145 145
146 146 for f in sorted(remove + forget):
147 147 if ui.verbose or not m.exact(f):
148 148 ui.status(_('removing %s\n') % m.rel(f))
149 149
150 150 # Need to lock because standin files are deleted then removed from the
151 151 # repository and we could race inbetween.
152 152 wlock = repo.wlock()
153 153 try:
154 154 lfdirstate = lfutil.openlfdirstate(ui, repo)
155 155 for f in remove:
156 156 if not after:
157 157 # If this is being called by addremove, notify the user that we
158 158 # are removing the file.
159 159 if getattr(repo, "_isaddremove", False):
160 160 ui.status(_('removing %s\n') % f)
161 161 if os.path.exists(repo.wjoin(f)):
162 162 util.unlinkpath(repo.wjoin(f))
163 163 lfdirstate.remove(f)
164 164 lfdirstate.write()
165 165 forget = [lfutil.standin(f) for f in forget]
166 166 remove = [lfutil.standin(f) for f in remove]
167 167 lfutil.repoforget(repo, forget)
168 168 # If this is being called by addremove, let the original addremove
169 169 # function handle this.
170 170 if not getattr(repo, "_isaddremove", False):
171 171 lfutil.reporemove(repo, remove, unlink=True)
172 else:
173 lfutil.reporemove(repo, remove, unlink=False)
172 174 finally:
173 175 wlock.release()
174 176
175 177 # For overriding mercurial.hgweb.webcommands so that largefiles will
176 178 # appear at their right place in the manifests.
177 179 def decodepath(orig, path):
178 180 return lfutil.splitstandin(path) or path
179 181
180 182 # -- Wrappers: modify existing commands --------------------------------
181 183
182 184 # Add works by going through the files that the user wanted to add and
183 185 # checking if they should be added as largefiles. Then it makes a new
184 186 # matcher which matches only the normal files and runs the original
185 187 # version of add.
186 188 def overrideadd(orig, ui, repo, *pats, **opts):
187 189 normal = opts.pop('normal')
188 190 if normal:
189 191 if opts.get('large'):
190 192 raise util.Abort(_('--normal cannot be used with --large'))
191 193 return orig(ui, repo, *pats, **opts)
192 194 bad = addlargefiles(ui, repo, *pats, **opts)
193 195 installnormalfilesmatchfn(repo[None].manifest())
194 196 result = orig(ui, repo, *pats, **opts)
195 197 restorematchfn()
196 198
197 199 return (result == 1 or bad) and 1 or 0
198 200
199 201 def overrideremove(orig, ui, repo, *pats, **opts):
200 202 installnormalfilesmatchfn(repo[None].manifest())
201 203 orig(ui, repo, *pats, **opts)
202 204 restorematchfn()
203 205 removelargefiles(ui, repo, *pats, **opts)
204 206
205 207 def overridestatusfn(orig, repo, rev2, **opts):
206 208 try:
207 209 repo._repo.lfstatus = True
208 210 return orig(repo, rev2, **opts)
209 211 finally:
210 212 repo._repo.lfstatus = False
211 213
212 214 def overridestatus(orig, ui, repo, *pats, **opts):
213 215 try:
214 216 repo.lfstatus = True
215 217 return orig(ui, repo, *pats, **opts)
216 218 finally:
217 219 repo.lfstatus = False
218 220
219 221 def overridedirty(orig, repo, ignoreupdate=False):
220 222 try:
221 223 repo._repo.lfstatus = True
222 224 return orig(repo, ignoreupdate)
223 225 finally:
224 226 repo._repo.lfstatus = False
225 227
226 228 def overridelog(orig, ui, repo, *pats, **opts):
227 229 try:
228 230 repo.lfstatus = True
229 231 orig(ui, repo, *pats, **opts)
230 232 finally:
231 233 repo.lfstatus = False
232 234
233 235 def overrideverify(orig, ui, repo, *pats, **opts):
234 236 large = opts.pop('large', False)
235 237 all = opts.pop('lfa', False)
236 238 contents = opts.pop('lfc', False)
237 239
238 240 result = orig(ui, repo, *pats, **opts)
239 241 if large:
240 242 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
241 243 return result
242 244
243 245 # Override needs to refresh standins so that update's normal merge
244 246 # will go through properly. Then the other update hook (overriding repo.update)
245 247 # will get the new files. Filemerge is also overriden so that the merge
246 248 # will merge standins correctly.
247 249 def overrideupdate(orig, ui, repo, *pats, **opts):
248 250 lfdirstate = lfutil.openlfdirstate(ui, repo)
249 251 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
250 252 False, False)
251 253 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
252 254
253 255 # Need to lock between the standins getting updated and their
254 256 # largefiles getting updated
255 257 wlock = repo.wlock()
256 258 try:
257 259 if opts['check']:
258 260 mod = len(modified) > 0
259 261 for lfile in unsure:
260 262 standin = lfutil.standin(lfile)
261 263 if repo['.'][standin].data().strip() != \
262 264 lfutil.hashfile(repo.wjoin(lfile)):
263 265 mod = True
264 266 else:
265 267 lfdirstate.normal(lfile)
266 268 lfdirstate.write()
267 269 if mod:
268 270 raise util.Abort(_('uncommitted local changes'))
269 271 # XXX handle removed differently
270 272 if not opts['clean']:
271 273 for lfile in unsure + modified + added:
272 274 lfutil.updatestandin(repo, lfutil.standin(lfile))
273 275 finally:
274 276 wlock.release()
275 277 return orig(ui, repo, *pats, **opts)
276 278
277 279 # Before starting the manifest merge, merge.updates will call
278 280 # _checkunknown to check if there are any files in the merged-in
279 281 # changeset that collide with unknown files in the working copy.
280 282 #
281 283 # The largefiles are seen as unknown, so this prevents us from merging
282 284 # in a file 'foo' if we already have a largefile with the same name.
283 285 #
284 286 # The overridden function filters the unknown files by removing any
285 287 # largefiles. This makes the merge proceed and we can then handle this
286 288 # case further in the overridden manifestmerge function below.
287 289 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
288 290 if lfutil.standin(f) in wctx:
289 291 return False
290 292 return origfn(repo, wctx, mctx, f)
291 293
292 294 # The manifest merge handles conflicts on the manifest level. We want
293 295 # to handle changes in largefile-ness of files at this level too.
294 296 #
295 297 # The strategy is to run the original manifestmerge and then process
296 298 # the action list it outputs. There are two cases we need to deal with:
297 299 #
298 300 # 1. Normal file in p1, largefile in p2. Here the largefile is
299 301 # detected via its standin file, which will enter the working copy
300 302 # with a "get" action. It is not "merge" since the standin is all
301 303 # Mercurial is concerned with at this level -- the link to the
302 304 # existing normal file is not relevant here.
303 305 #
304 306 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
305 307 # since the largefile will be present in the working copy and
306 308 # different from the normal file in p2. Mercurial therefore
307 309 # triggers a merge action.
308 310 #
309 311 # In both cases, we prompt the user and emit new actions to either
310 312 # remove the standin (if the normal file was kept) or to remove the
311 313 # normal file and get the standin (if the largefile was kept). The
312 314 # default prompt answer is to use the largefile version since it was
313 315 # presumably changed on purpose.
314 316 #
315 317 # Finally, the merge.applyupdates function will then take care of
316 318 # writing the files into the working copy and lfcommands.updatelfiles
317 319 # will update the largefiles.
318 320 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
319 321 actions = origfn(repo, p1, p2, pa, overwrite, partial)
320 322 processed = []
321 323
322 324 for action in actions:
323 325 if overwrite:
324 326 processed.append(action)
325 327 continue
326 328 f, m = action[:2]
327 329
328 330 choices = (_('&Largefile'), _('&Normal file'))
329 331 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
330 332 # Case 1: normal file in the working copy, largefile in
331 333 # the second parent
332 334 lfile = lfutil.splitstandin(f)
333 335 standin = f
334 336 msg = _('%s has been turned into a largefile\n'
335 337 'use (l)argefile or keep as (n)ormal file?') % lfile
336 338 if repo.ui.promptchoice(msg, choices, 0) == 0:
337 339 processed.append((lfile, "r"))
338 340 processed.append((standin, "g", p2.flags(standin)))
339 341 else:
340 342 processed.append((standin, "r"))
341 343 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
342 344 # Case 2: largefile in the working copy, normal file in
343 345 # the second parent
344 346 standin = lfutil.standin(f)
345 347 lfile = f
346 348 msg = _('%s has been turned into a normal file\n'
347 349 'keep as (l)argefile or use (n)ormal file?') % lfile
348 350 if repo.ui.promptchoice(msg, choices, 0) == 0:
349 351 processed.append((lfile, "r"))
350 352 else:
351 353 processed.append((standin, "r"))
352 354 processed.append((lfile, "g", p2.flags(lfile)))
353 355 else:
354 356 processed.append(action)
355 357
356 358 return processed
357 359
358 360 # Override filemerge to prompt the user about how they wish to merge
359 361 # largefiles. This will handle identical edits, and copy/rename +
360 362 # edit without prompting the user.
361 363 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
362 364 # Use better variable names here. Because this is a wrapper we cannot
363 365 # change the variable names in the function declaration.
364 366 fcdest, fcother, fcancestor = fcd, fco, fca
365 367 if not lfutil.isstandin(orig):
366 368 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
367 369 else:
368 370 if not fcother.cmp(fcdest): # files identical?
369 371 return None
370 372
371 373 # backwards, use working dir parent as ancestor
372 374 if fcancestor == fcother:
373 375 fcancestor = fcdest.parents()[0]
374 376
375 377 if orig != fcother.path():
376 378 repo.ui.status(_('merging %s and %s to %s\n')
377 379 % (lfutil.splitstandin(orig),
378 380 lfutil.splitstandin(fcother.path()),
379 381 lfutil.splitstandin(fcdest.path())))
380 382 else:
381 383 repo.ui.status(_('merging %s\n')
382 384 % lfutil.splitstandin(fcdest.path()))
383 385
384 386 if fcancestor.path() != fcother.path() and fcother.data() == \
385 387 fcancestor.data():
386 388 return 0
387 389 if fcancestor.path() != fcdest.path() and fcdest.data() == \
388 390 fcancestor.data():
389 391 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
390 392 return 0
391 393
392 394 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
393 395 'keep (l)ocal or take (o)ther?') %
394 396 lfutil.splitstandin(orig),
395 397 (_('&Local'), _('&Other')), 0) == 0:
396 398 return 0
397 399 else:
398 400 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
399 401 return 0
400 402
401 403 # Copy first changes the matchers to match standins instead of
402 404 # largefiles. Then it overrides util.copyfile in that function it
403 405 # checks if the destination largefile already exists. It also keeps a
404 406 # list of copied files so that the largefiles can be copied and the
405 407 # dirstate updated.
406 408 def overridecopy(orig, ui, repo, pats, opts, rename=False):
407 409 # doesn't remove largefile on rename
408 410 if len(pats) < 2:
409 411 # this isn't legal, let the original function deal with it
410 412 return orig(ui, repo, pats, opts, rename)
411 413
412 414 def makestandin(relpath):
413 415 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
414 416 return os.path.join(repo.wjoin(lfutil.standin(path)))
415 417
416 418 fullpats = scmutil.expandpats(pats)
417 419 dest = fullpats[-1]
418 420
419 421 if os.path.isdir(dest):
420 422 if not os.path.isdir(makestandin(dest)):
421 423 os.makedirs(makestandin(dest))
422 424 # This could copy both lfiles and normal files in one command,
423 425 # but we don't want to do that. First replace their matcher to
424 426 # only match normal files and run it, then replace it to just
425 427 # match largefiles and run it again.
426 428 nonormalfiles = False
427 429 nolfiles = False
428 430 try:
429 431 try:
430 432 installnormalfilesmatchfn(repo[None].manifest())
431 433 result = orig(ui, repo, pats, opts, rename)
432 434 except util.Abort, e:
433 435 if str(e) != 'no files to copy':
434 436 raise e
435 437 else:
436 438 nonormalfiles = True
437 439 result = 0
438 440 finally:
439 441 restorematchfn()
440 442
441 443 # The first rename can cause our current working directory to be removed.
442 444 # In that case there is nothing left to copy/rename so just quit.
443 445 try:
444 446 repo.getcwd()
445 447 except OSError:
446 448 return result
447 449
448 450 try:
449 451 try:
450 452 # When we call orig below it creates the standins but we don't add
451 453 # them to the dir state until later so lock during that time.
452 454 wlock = repo.wlock()
453 455
454 456 manifest = repo[None].manifest()
455 457 oldmatch = None # for the closure
456 458 def overridematch(ctx, pats=[], opts={}, globbed=False,
457 459 default='relpath'):
458 460 newpats = []
459 461 # The patterns were previously mangled to add the standin
460 462 # directory; we need to remove that now
461 463 for pat in pats:
462 464 if match_.patkind(pat) is None and lfutil.shortname in pat:
463 465 newpats.append(pat.replace(lfutil.shortname, ''))
464 466 else:
465 467 newpats.append(pat)
466 468 match = oldmatch(ctx, newpats, opts, globbed, default)
467 469 m = copy.copy(match)
468 470 lfile = lambda f: lfutil.standin(f) in manifest
469 471 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
470 472 m._fmap = set(m._files)
471 473 origmatchfn = m.matchfn
472 474 m.matchfn = lambda f: (lfutil.isstandin(f) and
473 475 (f in manifest) and
474 476 origmatchfn(lfutil.splitstandin(f)) or
475 477 None)
476 478 return m
477 479 oldmatch = installmatchfn(overridematch)
478 480 listpats = []
479 481 for pat in pats:
480 482 if match_.patkind(pat) is not None:
481 483 listpats.append(pat)
482 484 else:
483 485 listpats.append(makestandin(pat))
484 486
485 487 try:
486 488 origcopyfile = util.copyfile
487 489 copiedfiles = []
488 490 def overridecopyfile(src, dest):
489 491 if (lfutil.shortname in src and
490 492 dest.startswith(repo.wjoin(lfutil.shortname))):
491 493 destlfile = dest.replace(lfutil.shortname, '')
492 494 if not opts['force'] and os.path.exists(destlfile):
493 495 raise IOError('',
494 496 _('destination largefile already exists'))
495 497 copiedfiles.append((src, dest))
496 498 origcopyfile(src, dest)
497 499
498 500 util.copyfile = overridecopyfile
499 501 result += orig(ui, repo, listpats, opts, rename)
500 502 finally:
501 503 util.copyfile = origcopyfile
502 504
503 505 lfdirstate = lfutil.openlfdirstate(ui, repo)
504 506 for (src, dest) in copiedfiles:
505 507 if (lfutil.shortname in src and
506 508 dest.startswith(repo.wjoin(lfutil.shortname))):
507 509 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
508 510 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
509 511 destlfiledir = os.path.dirname(destlfile) or '.'
510 512 if not os.path.isdir(destlfiledir):
511 513 os.makedirs(destlfiledir)
512 514 if rename:
513 515 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
514 516 lfdirstate.remove(srclfile)
515 517 else:
516 518 util.copyfile(srclfile, destlfile)
517 519 lfdirstate.add(destlfile)
518 520 lfdirstate.write()
519 521 except util.Abort, e:
520 522 if str(e) != 'no files to copy':
521 523 raise e
522 524 else:
523 525 nolfiles = True
524 526 finally:
525 527 restorematchfn()
526 528 wlock.release()
527 529
528 530 if nolfiles and nonormalfiles:
529 531 raise util.Abort(_('no files to copy'))
530 532
531 533 return result
532 534
533 535 # When the user calls revert, we have to be careful to not revert any
534 536 # changes to other largefiles accidentally. This means we have to keep
535 537 # track of the largefiles that are being reverted so we only pull down
536 538 # the necessary largefiles.
537 539 #
538 540 # Standins are only updated (to match the hash of largefiles) before
539 541 # commits. Update the standins then run the original revert, changing
540 542 # the matcher to hit standins instead of largefiles. Based on the
541 543 # resulting standins update the largefiles. Then return the standins
542 544 # to their proper state
543 545 def overriderevert(orig, ui, repo, *pats, **opts):
544 546 # Because we put the standins in a bad state (by updating them)
545 547 # and then return them to a correct state we need to lock to
546 548 # prevent others from changing them in their incorrect state.
547 549 wlock = repo.wlock()
548 550 try:
549 551 lfdirstate = lfutil.openlfdirstate(ui, repo)
550 552 (modified, added, removed, missing, unknown, ignored, clean) = \
551 553 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
552 554 for lfile in modified:
553 555 lfutil.updatestandin(repo, lfutil.standin(lfile))
554 556 for lfile in missing:
555 557 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
556 558 os.unlink(repo.wjoin(lfutil.standin(lfile)))
557 559
558 560 try:
559 561 ctx = repo[opts.get('rev')]
560 562 oldmatch = None # for the closure
561 563 def overridematch(ctx, pats=[], opts={}, globbed=False,
562 564 default='relpath'):
563 565 match = oldmatch(ctx, pats, opts, globbed, default)
564 566 m = copy.copy(match)
565 567 def tostandin(f):
566 568 if lfutil.standin(f) in ctx:
567 569 return lfutil.standin(f)
568 570 elif lfutil.standin(f) in repo[None]:
569 571 return None
570 572 return f
571 573 m._files = [tostandin(f) for f in m._files]
572 574 m._files = [f for f in m._files if f is not None]
573 575 m._fmap = set(m._files)
574 576 origmatchfn = m.matchfn
575 577 def matchfn(f):
576 578 if lfutil.isstandin(f):
577 579 # We need to keep track of what largefiles are being
578 580 # matched so we know which ones to update later --
579 581 # otherwise we accidentally revert changes to other
580 582 # largefiles. This is repo-specific, so duckpunch the
581 583 # repo object to keep the list of largefiles for us
582 584 # later.
583 585 if origmatchfn(lfutil.splitstandin(f)) and \
584 586 (f in repo[None] or f in ctx):
585 587 lfileslist = getattr(repo, '_lfilestoupdate', [])
586 588 lfileslist.append(lfutil.splitstandin(f))
587 589 repo._lfilestoupdate = lfileslist
588 590 return True
589 591 else:
590 592 return False
591 593 return origmatchfn(f)
592 594 m.matchfn = matchfn
593 595 return m
594 596 oldmatch = installmatchfn(overridematch)
595 597 scmutil.match
596 598 matches = overridematch(repo[None], pats, opts)
597 599 orig(ui, repo, *pats, **opts)
598 600 finally:
599 601 restorematchfn()
600 602 lfileslist = getattr(repo, '_lfilestoupdate', [])
601 603 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
602 604 printmessage=False)
603 605
604 606 # empty out the largefiles list so we start fresh next time
605 607 repo._lfilestoupdate = []
606 608 for lfile in modified:
607 609 if lfile in lfileslist:
608 610 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
609 611 in repo['.']:
610 612 lfutil.writestandin(repo, lfutil.standin(lfile),
611 613 repo['.'][lfile].data().strip(),
612 614 'x' in repo['.'][lfile].flags())
613 615 lfdirstate = lfutil.openlfdirstate(ui, repo)
614 616 for lfile in added:
615 617 standin = lfutil.standin(lfile)
616 618 if standin not in ctx and (standin in matches or opts.get('all')):
617 619 if lfile in lfdirstate:
618 620 lfdirstate.drop(lfile)
619 621 util.unlinkpath(repo.wjoin(standin))
620 622 lfdirstate.write()
621 623 finally:
622 624 wlock.release()
623 625
624 626 def hgupdate(orig, repo, node):
625 627 # Only call updatelfiles the standins that have changed to save time
626 628 oldstandins = lfutil.getstandinsstate(repo)
627 629 result = orig(repo, node)
628 630 newstandins = lfutil.getstandinsstate(repo)
629 631 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
630 632 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, printmessage=True)
631 633 return result
632 634
633 635 def hgclean(orig, repo, node, show_stats=True):
634 636 result = orig(repo, node, show_stats)
635 637 lfcommands.updatelfiles(repo.ui, repo)
636 638 return result
637 639
638 640 def hgmerge(orig, repo, node, force=None, remind=True):
639 641 # Mark the repo as being in the middle of a merge, so that
640 642 # updatelfiles() will know that it needs to trust the standins in
641 643 # the working copy, not in the standins in the current node
642 644 repo._ismerging = True
643 645 try:
644 646 result = orig(repo, node, force, remind)
645 647 lfcommands.updatelfiles(repo.ui, repo)
646 648 finally:
647 649 repo._ismerging = False
648 650 return result
649 651
650 652 # When we rebase a repository with remotely changed largefiles, we need to
651 653 # take some extra care so that the largefiles are correctly updated in the
652 654 # working copy
653 655 def overridepull(orig, ui, repo, source=None, **opts):
654 656 revsprepull = len(repo)
655 657 if opts.get('rebase', False):
656 658 repo._isrebasing = True
657 659 try:
658 660 if opts.get('update'):
659 661 del opts['update']
660 662 ui.debug('--update and --rebase are not compatible, ignoring '
661 663 'the update flag\n')
662 664 del opts['rebase']
663 665 cmdutil.bailifchanged(repo)
664 666 origpostincoming = commands.postincoming
665 667 def _dummy(*args, **kwargs):
666 668 pass
667 669 commands.postincoming = _dummy
668 670 repo.lfpullsource = source
669 671 if not source:
670 672 source = 'default'
671 673 try:
672 674 result = commands.pull(ui, repo, source, **opts)
673 675 finally:
674 676 commands.postincoming = origpostincoming
675 677 revspostpull = len(repo)
676 678 if revspostpull > revsprepull:
677 679 result = result or rebase.rebase(ui, repo)
678 680 finally:
679 681 repo._isrebasing = False
680 682 else:
681 683 repo.lfpullsource = source
682 684 if not source:
683 685 source = 'default'
684 686 oldheads = lfutil.getcurrentheads(repo)
685 687 result = orig(ui, repo, source, **opts)
686 688 # If we do not have the new largefiles for any new heads we pulled, we
687 689 # will run into a problem later if we try to merge or rebase with one of
688 690 # these heads, so cache the largefiles now direclty into the system
689 691 # cache.
690 692 ui.status(_("caching new largefiles\n"))
691 693 numcached = 0
692 694 heads = lfutil.getcurrentheads(repo)
693 695 newheads = set(heads).difference(set(oldheads))
694 696 for head in newheads:
695 697 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
696 698 numcached += len(cached)
697 699 ui.status(_("%d largefiles cached\n") % numcached)
698 700 if opts.get('all_largefiles'):
699 701 revspostpull = len(repo)
700 702 revs = []
701 703 for rev in xrange(revsprepull + 1, revspostpull):
702 704 revs.append(repo[rev].rev())
703 705 lfcommands.downloadlfiles(ui, repo, revs)
704 706 return result
705 707
706 708 def overrideclone(orig, ui, source, dest=None, **opts):
707 709 if dest is None:
708 710 dest = hg.defaultdest(source)
709 711 if opts.get('all_largefiles') and not hg.islocal(dest):
710 712 raise util.Abort(_(
711 713 '--all-largefiles is incompatible with non-local destination %s' %
712 714 dest))
713 715 result = hg.clone(ui, opts, source, dest,
714 716 pull=opts.get('pull'),
715 717 stream=opts.get('uncompressed'),
716 718 rev=opts.get('rev'),
717 719 update=True, # required for successful walkchangerevs
718 720 branch=opts.get('branch'))
719 721 if result is None:
720 722 return True
721 723 if opts.get('all_largefiles'):
722 724 sourcerepo, destrepo = result
723 725 success, missing = lfcommands.downloadlfiles(ui, destrepo, None)
724 726 return missing != 0
725 727 return result is None
726 728
727 729 def overriderebase(orig, ui, repo, **opts):
728 730 repo._isrebasing = True
729 731 try:
730 732 orig(ui, repo, **opts)
731 733 finally:
732 734 repo._isrebasing = False
733 735
734 736 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
735 737 prefix=None, mtime=None, subrepos=None):
736 738 # No need to lock because we are only reading history and
737 739 # largefile caches, neither of which are modified.
738 740 lfcommands.cachelfiles(repo.ui, repo, node)
739 741
740 742 if kind not in archival.archivers:
741 743 raise util.Abort(_("unknown archive type '%s'") % kind)
742 744
743 745 ctx = repo[node]
744 746
745 747 if kind == 'files':
746 748 if prefix:
747 749 raise util.Abort(
748 750 _('cannot give prefix when archiving to files'))
749 751 else:
750 752 prefix = archival.tidyprefix(dest, kind, prefix)
751 753
752 754 def write(name, mode, islink, getdata):
753 755 if matchfn and not matchfn(name):
754 756 return
755 757 data = getdata()
756 758 if decode:
757 759 data = repo.wwritedata(name, data)
758 760 archiver.addfile(prefix + name, mode, islink, data)
759 761
760 762 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
761 763
762 764 if repo.ui.configbool("ui", "archivemeta", True):
763 765 def metadata():
764 766 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
765 767 hex(repo.changelog.node(0)), hex(node), ctx.branch())
766 768
767 769 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
768 770 if repo.tagtype(t) == 'global')
769 771 if not tags:
770 772 repo.ui.pushbuffer()
771 773 opts = {'template': '{latesttag}\n{latesttagdistance}',
772 774 'style': '', 'patch': None, 'git': None}
773 775 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
774 776 ltags, dist = repo.ui.popbuffer().split('\n')
775 777 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
776 778 tags += 'latesttagdistance: %s\n' % dist
777 779
778 780 return base + tags
779 781
780 782 write('.hg_archival.txt', 0644, False, metadata)
781 783
782 784 for f in ctx:
783 785 ff = ctx.flags(f)
784 786 getdata = ctx[f].data
785 787 if lfutil.isstandin(f):
786 788 path = lfutil.findfile(repo, getdata().strip())
787 789 if path is None:
788 790 raise util.Abort(
789 791 _('largefile %s not found in repo store or system cache')
790 792 % lfutil.splitstandin(f))
791 793 f = lfutil.splitstandin(f)
792 794
793 795 def getdatafn():
794 796 fd = None
795 797 try:
796 798 fd = open(path, 'rb')
797 799 return fd.read()
798 800 finally:
799 801 if fd:
800 802 fd.close()
801 803
802 804 getdata = getdatafn
803 805 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
804 806
805 807 if subrepos:
806 808 for subpath in ctx.substate:
807 809 sub = ctx.sub(subpath)
808 810 sub.archive(repo.ui, archiver, prefix)
809 811
810 812 archiver.done()
811 813
812 814 def hgsubrepoarchive(orig, repo, ui, archiver, prefix):
813 815 rev = repo._state[1]
814 816 ctx = repo._repo[rev]
815 817
816 818 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
817 819
818 820 def write(name, mode, islink, getdata):
819 821 if lfutil.isstandin(name):
820 822 return
821 823 data = getdata()
822 824
823 825 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
824 826
825 827 for f in ctx:
826 828 ff = ctx.flags(f)
827 829 getdata = ctx[f].data
828 830 if lfutil.isstandin(f):
829 831 path = lfutil.findfile(repo._repo, getdata().strip())
830 832 if path is None:
831 833 raise util.Abort(
832 834 _('largefile %s not found in repo store or system cache')
833 835 % lfutil.splitstandin(f))
834 836 f = lfutil.splitstandin(f)
835 837
836 838 def getdatafn():
837 839 fd = None
838 840 try:
839 841 fd = open(os.path.join(prefix, path), 'rb')
840 842 return fd.read()
841 843 finally:
842 844 if fd:
843 845 fd.close()
844 846
845 847 getdata = getdatafn
846 848
847 849 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
848 850
849 851 for subpath in ctx.substate:
850 852 sub = ctx.sub(subpath)
851 853 sub.archive(repo.ui, archiver, prefix)
852 854
853 855 # If a largefile is modified, the change is not reflected in its
854 856 # standin until a commit. cmdutil.bailifchanged() raises an exception
855 857 # if the repo has uncommitted changes. Wrap it to also check if
856 858 # largefiles were changed. This is used by bisect and backout.
857 859 def overridebailifchanged(orig, repo):
858 860 orig(repo)
859 861 repo.lfstatus = True
860 862 modified, added, removed, deleted = repo.status()[:4]
861 863 repo.lfstatus = False
862 864 if modified or added or removed or deleted:
863 865 raise util.Abort(_('outstanding uncommitted changes'))
864 866
865 867 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
866 868 def overridefetch(orig, ui, repo, *pats, **opts):
867 869 repo.lfstatus = True
868 870 modified, added, removed, deleted = repo.status()[:4]
869 871 repo.lfstatus = False
870 872 if modified or added or removed or deleted:
871 873 raise util.Abort(_('outstanding uncommitted changes'))
872 874 return orig(ui, repo, *pats, **opts)
873 875
874 876 def overrideforget(orig, ui, repo, *pats, **opts):
875 877 installnormalfilesmatchfn(repo[None].manifest())
876 878 orig(ui, repo, *pats, **opts)
877 879 restorematchfn()
878 880 m = scmutil.match(repo[None], pats, opts)
879 881
880 882 try:
881 883 repo.lfstatus = True
882 884 s = repo.status(match=m, clean=True)
883 885 finally:
884 886 repo.lfstatus = False
885 887 forget = sorted(s[0] + s[1] + s[3] + s[6])
886 888 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
887 889
888 890 for f in forget:
889 891 if lfutil.standin(f) not in repo.dirstate and not \
890 892 os.path.isdir(m.rel(lfutil.standin(f))):
891 893 ui.warn(_('not removing %s: file is already untracked\n')
892 894 % m.rel(f))
893 895
894 896 for f in forget:
895 897 if ui.verbose or not m.exact(f):
896 898 ui.status(_('removing %s\n') % m.rel(f))
897 899
898 900 # Need to lock because standin files are deleted then removed from the
899 901 # repository and we could race inbetween.
900 902 wlock = repo.wlock()
901 903 try:
902 904 lfdirstate = lfutil.openlfdirstate(ui, repo)
903 905 for f in forget:
904 906 if lfdirstate[f] == 'a':
905 907 lfdirstate.drop(f)
906 908 else:
907 909 lfdirstate.remove(f)
908 910 lfdirstate.write()
909 911 lfutil.reporemove(repo, [lfutil.standin(f) for f in forget],
910 912 unlink=True)
911 913 finally:
912 914 wlock.release()
913 915
914 916 def getoutgoinglfiles(ui, repo, dest=None, **opts):
915 917 dest = ui.expandpath(dest or 'default-push', dest or 'default')
916 918 dest, branches = hg.parseurl(dest, opts.get('branch'))
917 919 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
918 920 if revs:
919 921 revs = [repo.lookup(rev) for rev in revs]
920 922
921 923 remoteui = hg.remoteui
922 924
923 925 try:
924 926 remote = hg.repository(remoteui(repo, opts), dest)
925 927 except error.RepoError:
926 928 return None
927 929 o = lfutil.findoutgoing(repo, remote, False)
928 930 if not o:
929 931 return None
930 932 o = repo.changelog.nodesbetween(o, revs)[0]
931 933 if opts.get('newest_first'):
932 934 o.reverse()
933 935
934 936 toupload = set()
935 937 for n in o:
936 938 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
937 939 ctx = repo[n]
938 940 files = set(ctx.files())
939 941 if len(parents) == 2:
940 942 mc = ctx.manifest()
941 943 mp1 = ctx.parents()[0].manifest()
942 944 mp2 = ctx.parents()[1].manifest()
943 945 for f in mp1:
944 946 if f not in mc:
945 947 files.add(f)
946 948 for f in mp2:
947 949 if f not in mc:
948 950 files.add(f)
949 951 for f in mc:
950 952 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
951 953 files.add(f)
952 954 toupload = toupload.union(
953 955 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
954 956 return toupload
955 957
956 958 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
957 959 orig(ui, repo, dest, **opts)
958 960
959 961 if opts.pop('large', None):
960 962 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
961 963 if toupload is None:
962 964 ui.status(_('largefiles: No remote repo\n'))
963 965 else:
964 966 ui.status(_('largefiles to upload:\n'))
965 967 for file in toupload:
966 968 ui.status(lfutil.splitstandin(file) + '\n')
967 969 ui.status('\n')
968 970
969 971 def overridesummary(orig, ui, repo, *pats, **opts):
970 972 try:
971 973 repo.lfstatus = True
972 974 orig(ui, repo, *pats, **opts)
973 975 finally:
974 976 repo.lfstatus = False
975 977
976 978 if opts.pop('large', None):
977 979 toupload = getoutgoinglfiles(ui, repo, None, **opts)
978 980 if toupload is None:
979 981 ui.status(_('largefiles: No remote repo\n'))
980 982 else:
981 983 ui.status(_('largefiles: %d to upload\n') % len(toupload))
982 984
983 985 def overrideaddremove(orig, ui, repo, *pats, **opts):
984 986 if not lfutil.islfilesrepo(repo):
985 987 return orig(ui, repo, *pats, **opts)
986 988 # Get the list of missing largefiles so we can remove them
987 989 lfdirstate = lfutil.openlfdirstate(ui, repo)
988 990 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
989 991 False, False)
990 992 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
991 993
992 994 # Call into the normal remove code, but the removing of the standin, we want
993 995 # to have handled by original addremove. Monkey patching here makes sure
994 996 # we don't remove the standin in the largefiles code, preventing a very
995 997 # confused state later.
996 998 if missing:
997 999 repo._isaddremove = True
998 1000 removelargefiles(ui, repo, *missing, **opts)
999 1001 repo._isaddremove = False
1000 1002 # Call into the normal add code, and any files that *should* be added as
1001 1003 # largefiles will be
1002 1004 addlargefiles(ui, repo, *pats, **opts)
1003 1005 # Now that we've handled largefiles, hand off to the original addremove
1004 1006 # function to take care of the rest. Make sure it doesn't do anything with
1005 1007 # largefiles by installing a matcher that will ignore them.
1006 1008 installnormalfilesmatchfn(repo[None].manifest())
1007 1009 result = orig(ui, repo, *pats, **opts)
1008 1010 restorematchfn()
1009 1011 return result
1010 1012
1011 1013 # Calling purge with --all will cause the largefiles to be deleted.
1012 1014 # Override repo.status to prevent this from happening.
1013 1015 def overridepurge(orig, ui, repo, *dirs, **opts):
1014 1016 oldstatus = repo.status
1015 1017 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1016 1018 clean=False, unknown=False, listsubrepos=False):
1017 1019 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1018 1020 listsubrepos)
1019 1021 lfdirstate = lfutil.openlfdirstate(ui, repo)
1020 1022 modified, added, removed, deleted, unknown, ignored, clean = r
1021 1023 unknown = [f for f in unknown if lfdirstate[f] == '?']
1022 1024 ignored = [f for f in ignored if lfdirstate[f] == '?']
1023 1025 return modified, added, removed, deleted, unknown, ignored, clean
1024 1026 repo.status = overridestatus
1025 1027 orig(ui, repo, *dirs, **opts)
1026 1028 repo.status = oldstatus
1027 1029
1028 1030 def overriderollback(orig, ui, repo, **opts):
1029 1031 result = orig(ui, repo, **opts)
1030 1032 merge.update(repo, node=None, branchmerge=False, force=True,
1031 1033 partial=lfutil.isstandin)
1032 1034 wlock = repo.wlock()
1033 1035 try:
1034 1036 lfdirstate = lfutil.openlfdirstate(ui, repo)
1035 1037 lfiles = lfutil.listlfiles(repo)
1036 1038 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1037 1039 for file in lfiles:
1038 1040 if file in oldlfiles:
1039 1041 lfdirstate.normallookup(file)
1040 1042 else:
1041 1043 lfdirstate.add(file)
1042 1044 lfdirstate.write()
1043 1045 finally:
1044 1046 wlock.release()
1045 1047 return result
1046 1048
1047 1049 def overridetransplant(orig, ui, repo, *revs, **opts):
1048 1050 try:
1049 1051 oldstandins = lfutil.getstandinsstate(repo)
1050 1052 repo._istransplanting = True
1051 1053 result = orig(ui, repo, *revs, **opts)
1052 1054 newstandins = lfutil.getstandinsstate(repo)
1053 1055 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1054 1056 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1055 1057 printmessage=True)
1056 1058 finally:
1057 1059 repo._istransplanting = False
1058 1060 return result
1059 1061
1060 1062 def overridecat(orig, ui, repo, file1, *pats, **opts):
1061 1063 rev = opts.get('rev')
1062 1064 if not lfutil.standin(file1) in repo[rev]:
1063 1065 result = orig(ui, repo, file1, *pats, **opts)
1064 1066 return result
1065 1067 return lfcommands.catlfile(repo, file1, opts.get('rev'), opts.get('output'))
@@ -1,471 +1,472 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''setup for largefiles repositories: reposetup'''
10 10 import copy
11 11 import types
12 12 import os
13 13
14 14 from mercurial import context, error, manifest, match as match_, util
15 15 from mercurial import node as node_
16 16 from mercurial.i18n import _
17 17
18 18 import lfcommands
19 19 import proto
20 20 import lfutil
21 21
22 22 def reposetup(ui, repo):
23 23 # wire repositories should be given new wireproto functions but not the
24 24 # other largefiles modifications
25 25 if not repo.local():
26 26 return proto.wirereposetup(ui, repo)
27 27
28 28 for name in ('status', 'commitctx', 'commit', 'push'):
29 29 method = getattr(repo, name)
30 30 if (isinstance(method, types.FunctionType) and
31 31 method.func_name == 'wrap'):
32 32 ui.warn(_('largefiles: repo method %r appears to have already been'
33 33 ' wrapped by another extension: '
34 34 'largefiles may behave incorrectly\n')
35 35 % name)
36 36
37 37 class lfilesrepo(repo.__class__):
38 38 lfstatus = False
39 39 def status_nolfiles(self, *args, **kwargs):
40 40 return super(lfilesrepo, self).status(*args, **kwargs)
41 41
42 42 # When lfstatus is set, return a context that gives the names
43 43 # of largefiles instead of their corresponding standins and
44 44 # identifies the largefiles as always binary, regardless of
45 45 # their actual contents.
46 46 def __getitem__(self, changeid):
47 47 ctx = super(lfilesrepo, self).__getitem__(changeid)
48 48 if self.lfstatus:
49 49 class lfilesmanifestdict(manifest.manifestdict):
50 50 def __contains__(self, filename):
51 51 if super(lfilesmanifestdict,
52 52 self).__contains__(filename):
53 53 return True
54 54 return super(lfilesmanifestdict,
55 55 self).__contains__(lfutil.standin(filename))
56 56 class lfilesctx(ctx.__class__):
57 57 def files(self):
58 58 filenames = super(lfilesctx, self).files()
59 59 return [lfutil.splitstandin(f) or f for f in filenames]
60 60 def manifest(self):
61 61 man1 = super(lfilesctx, self).manifest()
62 62 man1.__class__ = lfilesmanifestdict
63 63 return man1
64 64 def filectx(self, path, fileid=None, filelog=None):
65 65 try:
66 66 if filelog is not None:
67 67 result = super(lfilesctx, self).filectx(
68 68 path, fileid, filelog)
69 69 else:
70 70 result = super(lfilesctx, self).filectx(
71 71 path, fileid)
72 72 except error.LookupError:
73 73 # Adding a null character will cause Mercurial to
74 74 # identify this as a binary file.
75 75 if filelog is not None:
76 76 result = super(lfilesctx, self).filectx(
77 77 lfutil.standin(path), fileid, filelog)
78 78 else:
79 79 result = super(lfilesctx, self).filectx(
80 80 lfutil.standin(path), fileid)
81 81 olddata = result.data
82 82 result.data = lambda: olddata() + '\0'
83 83 return result
84 84 ctx.__class__ = lfilesctx
85 85 return ctx
86 86
87 87 # Figure out the status of big files and insert them into the
88 88 # appropriate list in the result. Also removes standin files
89 89 # from the listing. Revert to the original status if
90 90 # self.lfstatus is False.
91 91 def status(self, node1='.', node2=None, match=None, ignored=False,
92 92 clean=False, unknown=False, listsubrepos=False):
93 93 listignored, listclean, listunknown = ignored, clean, unknown
94 94 if not self.lfstatus:
95 95 return super(lfilesrepo, self).status(node1, node2, match,
96 96 listignored, listclean, listunknown, listsubrepos)
97 97 else:
98 98 # some calls in this function rely on the old version of status
99 99 self.lfstatus = False
100 100 if isinstance(node1, context.changectx):
101 101 ctx1 = node1
102 102 else:
103 103 ctx1 = repo[node1]
104 104 if isinstance(node2, context.changectx):
105 105 ctx2 = node2
106 106 else:
107 107 ctx2 = repo[node2]
108 108 working = ctx2.rev() is None
109 109 parentworking = working and ctx1 == self['.']
110 110
111 111 def inctx(file, ctx):
112 112 try:
113 113 if ctx.rev() is None:
114 114 return file in ctx.manifest()
115 115 ctx[file]
116 116 return True
117 117 except KeyError:
118 118 return False
119 119
120 120 if match is None:
121 121 match = match_.always(self.root, self.getcwd())
122 122
123 123 # First check if there were files specified on the
124 124 # command line. If there were, and none of them were
125 125 # largefiles, we should just bail here and let super
126 126 # handle it -- thus gaining a big performance boost.
127 127 lfdirstate = lfutil.openlfdirstate(ui, self)
128 128 if match.files() and not match.anypats():
129 129 for f in lfdirstate:
130 130 if match(f):
131 131 break
132 132 else:
133 133 return super(lfilesrepo, self).status(node1, node2,
134 134 match, listignored, listclean,
135 135 listunknown, listsubrepos)
136 136
137 137 # Create a copy of match that matches standins instead
138 138 # of largefiles.
139 139 def tostandins(files):
140 140 if not working:
141 141 return files
142 142 newfiles = []
143 143 dirstate = repo.dirstate
144 144 for f in files:
145 145 sf = lfutil.standin(f)
146 146 if sf in dirstate:
147 147 newfiles.append(sf)
148 148 elif sf in dirstate.dirs():
149 149 # Directory entries could be regular or
150 150 # standin, check both
151 151 newfiles.extend((f, sf))
152 152 else:
153 153 newfiles.append(f)
154 154 return newfiles
155 155
156 156 # Create a function that we can use to override what is
157 157 # normally the ignore matcher. We've already checked
158 158 # for ignored files on the first dirstate walk, and
159 159 # unecessarily re-checking here causes a huge performance
160 160 # hit because lfdirstate only knows about largefiles
161 161 def _ignoreoverride(self):
162 162 return False
163 163
164 164 m = copy.copy(match)
165 165 m._files = tostandins(m._files)
166 166
167 167 # Get ignored files here even if we weren't asked for them; we
168 168 # must use the result here for filtering later
169 169 result = super(lfilesrepo, self).status(node1, node2, m,
170 170 True, clean, unknown, listsubrepos)
171 171 if working:
172 172 try:
173 173 # Any non-largefiles that were explicitly listed must be
174 174 # taken out or lfdirstate.status will report an error.
175 175 # The status of these files was already computed using
176 176 # super's status.
177 177 # Override lfdirstate's ignore matcher to not do
178 178 # anything
179 179 origignore = lfdirstate._ignore
180 180 lfdirstate._ignore = _ignoreoverride
181 181
182 182 def sfindirstate(f):
183 183 sf = lfutil.standin(f)
184 184 dirstate = repo.dirstate
185 185 return sf in dirstate or sf in dirstate.dirs()
186 186 match._files = [f for f in match._files
187 187 if sfindirstate(f)]
188 188 # Don't waste time getting the ignored and unknown
189 189 # files again; we already have them
190 190 s = lfdirstate.status(match, [], False,
191 191 listclean, False)
192 192 (unsure, modified, added, removed, missing, unknown,
193 193 ignored, clean) = s
194 194 # Replace the list of ignored and unknown files with
195 195 # the previously caclulated lists, and strip out the
196 196 # largefiles
197 197 lfiles = set(lfdirstate._map)
198 198 ignored = set(result[5]).difference(lfiles)
199 199 unknown = set(result[4]).difference(lfiles)
200 200 if parentworking:
201 201 for lfile in unsure:
202 202 standin = lfutil.standin(lfile)
203 203 if standin not in ctx1:
204 204 # from second parent
205 205 modified.append(lfile)
206 206 elif ctx1[standin].data().strip() \
207 207 != lfutil.hashfile(self.wjoin(lfile)):
208 208 modified.append(lfile)
209 209 else:
210 210 clean.append(lfile)
211 211 lfdirstate.normal(lfile)
212 212 else:
213 213 tocheck = unsure + modified + added + clean
214 214 modified, added, clean = [], [], []
215 215
216 216 for lfile in tocheck:
217 217 standin = lfutil.standin(lfile)
218 218 if inctx(standin, ctx1):
219 219 if ctx1[standin].data().strip() != \
220 220 lfutil.hashfile(self.wjoin(lfile)):
221 221 modified.append(lfile)
222 222 else:
223 223 clean.append(lfile)
224 224 else:
225 225 added.append(lfile)
226 226 finally:
227 227 # Replace the original ignore function
228 228 lfdirstate._ignore = origignore
229 229
230 230 for standin in ctx1.manifest():
231 231 if not lfutil.isstandin(standin):
232 232 continue
233 233 lfile = lfutil.splitstandin(standin)
234 234 if not match(lfile):
235 235 continue
236 236 if lfile not in lfdirstate:
237 237 removed.append(lfile)
238 238
239 239 # Filter result lists
240 240 result = list(result)
241 241
242 242 # Largefiles are not really removed when they're
243 243 # still in the normal dirstate. Likewise, normal
244 244 # files are not really removed if it's still in
245 245 # lfdirstate. This happens in merges where files
246 246 # change type.
247 247 removed = [f for f in removed if f not in repo.dirstate]
248 248 result[2] = [f for f in result[2] if f not in lfdirstate]
249 249
250 250 # Unknown files
251 251 unknown = set(unknown).difference(ignored)
252 252 result[4] = [f for f in unknown
253 253 if (repo.dirstate[f] == '?' and
254 254 not lfutil.isstandin(f))]
255 255 # Ignored files were calculated earlier by the dirstate,
256 256 # and we already stripped out the largefiles from the list
257 257 result[5] = ignored
258 258 # combine normal files and largefiles
259 259 normals = [[fn for fn in filelist
260 260 if not lfutil.isstandin(fn)]
261 261 for filelist in result]
262 262 lfiles = (modified, added, removed, missing, [], [], clean)
263 263 result = [sorted(list1 + list2)
264 264 for (list1, list2) in zip(normals, lfiles)]
265 265 else:
266 266 def toname(f):
267 267 if lfutil.isstandin(f):
268 268 return lfutil.splitstandin(f)
269 269 return f
270 270 result = [[toname(f) for f in items] for items in result]
271 271
272 272 if not listunknown:
273 273 result[4] = []
274 274 if not listignored:
275 275 result[5] = []
276 276 if not listclean:
277 277 result[6] = []
278 278 self.lfstatus = True
279 279 return result
280 280
281 281 # As part of committing, copy all of the largefiles into the
282 282 # cache.
283 283 def commitctx(self, *args, **kwargs):
284 284 node = super(lfilesrepo, self).commitctx(*args, **kwargs)
285 285 lfutil.copyalltostore(self, node)
286 286 return node
287 287
288 288 # Before commit, largefile standins have not had their
289 289 # contents updated to reflect the hash of their largefile.
290 290 # Do that here.
291 291 def commit(self, text="", user=None, date=None, match=None,
292 292 force=False, editor=False, extra={}):
293 293 orig = super(lfilesrepo, self).commit
294 294
295 295 wlock = repo.wlock()
296 296 try:
297 297 # Case 0: Rebase or Transplant
298 298 # We have to take the time to pull down the new largefiles now.
299 299 # Otherwise, any largefiles that were modified in the
300 300 # destination changesets get overwritten, either by the rebase
301 301 # or in the first commit after the rebase or transplant.
302 302 # updatelfiles will update the dirstate to mark any pulled
303 303 # largefiles as modified
304 304 if getattr(repo, "_isrebasing", False) or \
305 305 getattr(repo, "_istransplanting", False):
306 306 lfcommands.updatelfiles(repo.ui, repo, filelist=None,
307 307 printmessage=False)
308 308 result = orig(text=text, user=user, date=date, match=match,
309 309 force=force, editor=editor, extra=extra)
310 310 return result
311 311 # Case 1: user calls commit with no specific files or
312 312 # include/exclude patterns: refresh and commit all files that
313 313 # are "dirty".
314 314 if ((match is None) or
315 315 (not match.anypats() and not match.files())):
316 316 # Spend a bit of time here to get a list of files we know
317 317 # are modified so we can compare only against those.
318 318 # It can cost a lot of time (several seconds)
319 319 # otherwise to update all standins if the largefiles are
320 320 # large.
321 321 lfdirstate = lfutil.openlfdirstate(ui, self)
322 322 dirtymatch = match_.always(repo.root, repo.getcwd())
323 323 s = lfdirstate.status(dirtymatch, [], False, False, False)
324 324 modifiedfiles = []
325 325 for i in s:
326 326 modifiedfiles.extend(i)
327 327 lfiles = lfutil.listlfiles(self)
328 328 # this only loops through largefiles that exist (not
329 329 # removed/renamed)
330 330 for lfile in lfiles:
331 331 if lfile in modifiedfiles:
332 332 if os.path.exists(
333 333 self.wjoin(lfutil.standin(lfile))):
334 334 # this handles the case where a rebase is being
335 335 # performed and the working copy is not updated
336 336 # yet.
337 337 if os.path.exists(self.wjoin(lfile)):
338 338 lfutil.updatestandin(self,
339 339 lfutil.standin(lfile))
340 340 lfdirstate.normal(lfile)
341 341 for lfile in lfdirstate:
342 342 if lfile in modifiedfiles:
343 if not os.path.exists(
344 repo.wjoin(lfutil.standin(lfile))):
343 if (not os.path.exists(repo.wjoin(
344 lfutil.standin(lfile)))) or \
345 (not os.path.exists(repo.wjoin(lfile))):
345 346 lfdirstate.drop(lfile)
346 347
347 348 result = orig(text=text, user=user, date=date, match=match,
348 349 force=force, editor=editor, extra=extra)
349 350 # This needs to be after commit; otherwise precommit hooks
350 351 # get the wrong status
351 352 lfdirstate.write()
352 353 return result
353 354
354 355 for f in match.files():
355 356 if lfutil.isstandin(f):
356 357 raise util.Abort(
357 358 _('file "%s" is a largefile standin') % f,
358 359 hint=('commit the largefile itself instead'))
359 360
360 361 # Case 2: user calls commit with specified patterns: refresh
361 362 # any matching big files.
362 363 smatcher = lfutil.composestandinmatcher(self, match)
363 364 standins = lfutil.dirstatewalk(self.dirstate, smatcher)
364 365
365 366 # No matching big files: get out of the way and pass control to
366 367 # the usual commit() method.
367 368 if not standins:
368 369 return orig(text=text, user=user, date=date, match=match,
369 370 force=force, editor=editor, extra=extra)
370 371
371 372 # Refresh all matching big files. It's possible that the
372 373 # commit will end up failing, in which case the big files will
373 374 # stay refreshed. No harm done: the user modified them and
374 375 # asked to commit them, so sooner or later we're going to
375 376 # refresh the standins. Might as well leave them refreshed.
376 377 lfdirstate = lfutil.openlfdirstate(ui, self)
377 378 for standin in standins:
378 379 lfile = lfutil.splitstandin(standin)
379 380 if lfdirstate[lfile] <> 'r':
380 381 lfutil.updatestandin(self, standin)
381 382 lfdirstate.normal(lfile)
382 383 else:
383 384 lfdirstate.drop(lfile)
384 385
385 386 # Cook up a new matcher that only matches regular files or
386 387 # standins corresponding to the big files requested by the
387 388 # user. Have to modify _files to prevent commit() from
388 389 # complaining "not tracked" for big files.
389 390 lfiles = lfutil.listlfiles(repo)
390 391 match = copy.copy(match)
391 392 origmatchfn = match.matchfn
392 393
393 394 # Check both the list of largefiles and the list of
394 395 # standins because if a largefile was removed, it
395 396 # won't be in the list of largefiles at this point
396 397 match._files += sorted(standins)
397 398
398 399 actualfiles = []
399 400 for f in match._files:
400 401 fstandin = lfutil.standin(f)
401 402
402 403 # ignore known largefiles and standins
403 404 if f in lfiles or fstandin in standins:
404 405 continue
405 406
406 407 # append directory separator to avoid collisions
407 408 if not fstandin.endswith(os.sep):
408 409 fstandin += os.sep
409 410
410 411 actualfiles.append(f)
411 412 match._files = actualfiles
412 413
413 414 def matchfn(f):
414 415 if origmatchfn(f):
415 416 return f not in lfiles
416 417 else:
417 418 return f in standins
418 419
419 420 match.matchfn = matchfn
420 421 result = orig(text=text, user=user, date=date, match=match,
421 422 force=force, editor=editor, extra=extra)
422 423 # This needs to be after commit; otherwise precommit hooks
423 424 # get the wrong status
424 425 lfdirstate.write()
425 426 return result
426 427 finally:
427 428 wlock.release()
428 429
429 430 def push(self, remote, force=False, revs=None, newbranch=False):
430 431 o = lfutil.findoutgoing(repo, remote, force)
431 432 if o:
432 433 toupload = set()
433 434 o = repo.changelog.nodesbetween(o, revs)[0]
434 435 for n in o:
435 436 parents = [p for p in repo.changelog.parents(n)
436 437 if p != node_.nullid]
437 438 ctx = repo[n]
438 439 files = set(ctx.files())
439 440 if len(parents) == 2:
440 441 mc = ctx.manifest()
441 442 mp1 = ctx.parents()[0].manifest()
442 443 mp2 = ctx.parents()[1].manifest()
443 444 for f in mp1:
444 445 if f not in mc:
445 446 files.add(f)
446 447 for f in mp2:
447 448 if f not in mc:
448 449 files.add(f)
449 450 for f in mc:
450 451 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f,
451 452 None):
452 453 files.add(f)
453 454
454 455 toupload = toupload.union(
455 456 set([ctx[f].data().strip()
456 457 for f in files
457 458 if lfutil.isstandin(f) and f in ctx]))
458 459 lfcommands.uploadlfiles(ui, self, remote, toupload)
459 460 return super(lfilesrepo, self).push(remote, force, revs,
460 461 newbranch)
461 462
462 463 repo.__class__ = lfilesrepo
463 464
464 465 def checkrequireslfiles(ui, repo, **kwargs):
465 466 if 'largefiles' not in repo.requirements and util.any(
466 467 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
467 468 repo.requirements.add('largefiles')
468 469 repo._writerequirements()
469 470
470 471 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles)
471 472 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)
General Comments 0
You need to be logged in to leave comments. Login now