##// END OF EJS Templates
largefiles: rely on main scoping for writing dirstate in `markcommitted`...
marmoute -
r50945:22cd517b default
parent child Browse files
Show More
@@ -1,818 +1,817 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 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import contextlib
12 12 import copy
13 13 import os
14 14 import stat
15 15
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18 from mercurial.pycompat import open
19 19
20 20 from mercurial import (
21 21 dirstate,
22 22 encoding,
23 23 error,
24 24 httpconnection,
25 25 match as matchmod,
26 26 pycompat,
27 27 requirements,
28 28 scmutil,
29 29 sparse,
30 30 util,
31 31 vfs as vfsmod,
32 32 )
33 33 from mercurial.utils import hashutil
34 34 from mercurial.dirstateutils import timestamp
35 35
36 36 shortname = b'.hglf'
37 37 shortnameslash = shortname + b'/'
38 38 longname = b'largefiles'
39 39
40 40 # -- Private worker functions ------------------------------------------
41 41
42 42
43 43 @contextlib.contextmanager
44 44 def lfstatus(repo, value=True):
45 45 oldvalue = getattr(repo, 'lfstatus', False)
46 46 repo.lfstatus = value
47 47 try:
48 48 yield
49 49 finally:
50 50 repo.lfstatus = oldvalue
51 51
52 52
53 53 def getminsize(ui, assumelfiles, opt, default=10):
54 54 lfsize = opt
55 55 if not lfsize and assumelfiles:
56 56 lfsize = ui.config(longname, b'minsize', default=default)
57 57 if lfsize:
58 58 try:
59 59 lfsize = float(lfsize)
60 60 except ValueError:
61 61 raise error.Abort(
62 62 _(b'largefiles: size must be number (not %s)\n') % lfsize
63 63 )
64 64 if lfsize is None:
65 65 raise error.Abort(_(b'minimum size for largefiles must be specified'))
66 66 return lfsize
67 67
68 68
69 69 def link(src, dest):
70 70 """Try to create hardlink - if that fails, efficiently make a copy."""
71 71 util.makedirs(os.path.dirname(dest))
72 72 try:
73 73 util.oslink(src, dest)
74 74 except OSError:
75 75 # if hardlinks fail, fallback on atomic copy
76 76 with open(src, b'rb') as srcf, util.atomictempfile(dest) as dstf:
77 77 for chunk in util.filechunkiter(srcf):
78 78 dstf.write(chunk)
79 79 os.chmod(dest, os.stat(src).st_mode)
80 80
81 81
82 82 def usercachepath(ui, hash):
83 83 """Return the correct location in the "global" largefiles cache for a file
84 84 with the given hash.
85 85 This cache is used for sharing of largefiles across repositories - both
86 86 to preserve download bandwidth and storage space."""
87 87 return os.path.join(_usercachedir(ui), hash)
88 88
89 89
90 90 def _usercachedir(ui, name=longname):
91 91 '''Return the location of the "global" largefiles cache.'''
92 92 path = ui.configpath(name, b'usercache')
93 93 if path:
94 94 return path
95 95
96 96 hint = None
97 97
98 98 if pycompat.iswindows:
99 99 appdata = encoding.environ.get(
100 100 b'LOCALAPPDATA', encoding.environ.get(b'APPDATA')
101 101 )
102 102 if appdata:
103 103 return os.path.join(appdata, name)
104 104
105 105 hint = _(b"define %s or %s in the environment, or set %s.usercache") % (
106 106 b"LOCALAPPDATA",
107 107 b"APPDATA",
108 108 name,
109 109 )
110 110 elif pycompat.isdarwin:
111 111 home = encoding.environ.get(b'HOME')
112 112 if home:
113 113 return os.path.join(home, b'Library', b'Caches', name)
114 114
115 115 hint = _(b"define %s in the environment, or set %s.usercache") % (
116 116 b"HOME",
117 117 name,
118 118 )
119 119 elif pycompat.isposix:
120 120 path = encoding.environ.get(b'XDG_CACHE_HOME')
121 121 if path:
122 122 return os.path.join(path, name)
123 123 home = encoding.environ.get(b'HOME')
124 124 if home:
125 125 return os.path.join(home, b'.cache', name)
126 126
127 127 hint = _(b"define %s or %s in the environment, or set %s.usercache") % (
128 128 b"XDG_CACHE_HOME",
129 129 b"HOME",
130 130 name,
131 131 )
132 132 else:
133 133 raise error.Abort(
134 134 _(b'unknown operating system: %s\n') % pycompat.osname
135 135 )
136 136
137 137 raise error.Abort(_(b'unknown %s usercache location') % name, hint=hint)
138 138
139 139
140 140 def inusercache(ui, hash):
141 141 path = usercachepath(ui, hash)
142 142 return os.path.exists(path)
143 143
144 144
145 145 def findfile(repo, hash):
146 146 """Return store path of the largefile with the specified hash.
147 147 As a side effect, the file might be linked from user cache.
148 148 Return None if the file can't be found locally."""
149 149 path, exists = findstorepath(repo, hash)
150 150 if exists:
151 151 repo.ui.note(_(b'found %s in store\n') % hash)
152 152 return path
153 153 elif inusercache(repo.ui, hash):
154 154 repo.ui.note(_(b'found %s in system cache\n') % hash)
155 155 path = storepath(repo, hash)
156 156 link(usercachepath(repo.ui, hash), path)
157 157 return path
158 158 return None
159 159
160 160
161 161 class largefilesdirstate(dirstate.dirstate):
162 162 _large_file_dirstate = True
163 163
164 164 def __getitem__(self, key):
165 165 return super(largefilesdirstate, self).__getitem__(unixpath(key))
166 166
167 167 def set_tracked(self, f):
168 168 return super(largefilesdirstate, self).set_tracked(unixpath(f))
169 169
170 170 def set_untracked(self, f):
171 171 return super(largefilesdirstate, self).set_untracked(unixpath(f))
172 172
173 173 def normal(self, f, parentfiledata=None):
174 174 # not sure if we should pass the `parentfiledata` down or throw it
175 175 # away. So throwing it away to stay on the safe side.
176 176 return super(largefilesdirstate, self).normal(unixpath(f))
177 177
178 178 def remove(self, f):
179 179 return super(largefilesdirstate, self).remove(unixpath(f))
180 180
181 181 def add(self, f):
182 182 return super(largefilesdirstate, self).add(unixpath(f))
183 183
184 184 def drop(self, f):
185 185 return super(largefilesdirstate, self).drop(unixpath(f))
186 186
187 187 def forget(self, f):
188 188 return super(largefilesdirstate, self).forget(unixpath(f))
189 189
190 190 def normallookup(self, f):
191 191 return super(largefilesdirstate, self).normallookup(unixpath(f))
192 192
193 193 def _ignore(self, f):
194 194 return False
195 195
196 196 def write(self, tr):
197 197 # (1) disable PENDING mode always
198 198 # (lfdirstate isn't yet managed as a part of the transaction)
199 199 # (2) avoid develwarn 'use dirstate.write with ....'
200 200 if tr:
201 201 tr.addbackup(b'largefiles/dirstate', location=b'plain')
202 202 super(largefilesdirstate, self).write(None)
203 203
204 204
205 205 def openlfdirstate(ui, repo, create=True):
206 206 """
207 207 Return a dirstate object that tracks largefiles: i.e. its root is
208 208 the repo root, but it is saved in .hg/largefiles/dirstate.
209 209
210 210 If a dirstate object already exists and is being used for a 'changing_*'
211 211 context, it will be returned.
212 212 """
213 213 sub_dirstate = getattr(repo.dirstate, '_sub_dirstate', None)
214 214 if sub_dirstate is not None:
215 215 return sub_dirstate
216 216 vfs = repo.vfs
217 217 lfstoredir = longname
218 218 opener = vfsmod.vfs(vfs.join(lfstoredir))
219 219 use_dirstate_v2 = requirements.DIRSTATE_V2_REQUIREMENT in repo.requirements
220 220 lfdirstate = largefilesdirstate(
221 221 opener,
222 222 ui,
223 223 repo.root,
224 224 repo.dirstate._validate,
225 225 lambda: sparse.matcher(repo),
226 226 repo.nodeconstants,
227 227 use_dirstate_v2,
228 228 )
229 229
230 230 # If the largefiles dirstate does not exist, populate and create
231 231 # it. This ensures that we create it on the first meaningful
232 232 # largefiles operation in a new clone.
233 233 if create and not vfs.exists(vfs.join(lfstoredir, b'dirstate')):
234 234 try:
235 235 with repo.wlock(wait=False):
236 236 matcher = getstandinmatcher(repo)
237 237 standins = repo.dirstate.walk(
238 238 matcher, subrepos=[], unknown=False, ignored=False
239 239 )
240 240
241 241 if len(standins) > 0:
242 242 vfs.makedirs(lfstoredir)
243 243
244 244 for standin in standins:
245 245 lfile = splitstandin(standin)
246 246 lfdirstate.hacky_extension_update_file(
247 247 lfile,
248 248 p1_tracked=True,
249 249 wc_tracked=True,
250 250 possibly_dirty=True,
251 251 )
252 252 except error.LockError:
253 253 # Assume that whatever was holding the lock was important.
254 254 # If we were doing something important, we would already have
255 255 # either the lock or a largefile dirstate.
256 256 pass
257 257 return lfdirstate
258 258
259 259
260 260 def lfdirstatestatus(lfdirstate, repo):
261 261 pctx = repo[b'.']
262 262 match = matchmod.always()
263 263 unsure, s, mtime_boundary = lfdirstate.status(
264 264 match, subrepos=[], ignored=False, clean=False, unknown=False
265 265 )
266 266 modified, clean = s.modified, s.clean
267 267 wctx = repo[None]
268 268 for lfile in unsure:
269 269 try:
270 270 fctx = pctx[standin(lfile)]
271 271 except LookupError:
272 272 fctx = None
273 273 if not fctx or readasstandin(fctx) != hashfile(repo.wjoin(lfile)):
274 274 modified.append(lfile)
275 275 else:
276 276 clean.append(lfile)
277 277 st = wctx[lfile].lstat()
278 278 mode = st.st_mode
279 279 size = st.st_size
280 280 mtime = timestamp.reliable_mtime_of(st, mtime_boundary)
281 281 if mtime is not None:
282 282 cache_data = (mode, size, mtime)
283 283 lfdirstate.set_clean(lfile, cache_data)
284 284 return s
285 285
286 286
287 287 def listlfiles(repo, rev=None, matcher=None):
288 288 """return a list of largefiles in the working copy or the
289 289 specified changeset"""
290 290
291 291 if matcher is None:
292 292 matcher = getstandinmatcher(repo)
293 293
294 294 # ignore unknown files in working directory
295 295 return [
296 296 splitstandin(f)
297 297 for f in repo[rev].walk(matcher)
298 298 if rev is not None or repo.dirstate.get_entry(f).any_tracked
299 299 ]
300 300
301 301
302 302 def instore(repo, hash, forcelocal=False):
303 303 '''Return true if a largefile with the given hash exists in the store'''
304 304 return os.path.exists(storepath(repo, hash, forcelocal))
305 305
306 306
307 307 def storepath(repo, hash, forcelocal=False):
308 308 """Return the correct location in the repository largefiles store for a
309 309 file with the given hash."""
310 310 if not forcelocal and repo.shared():
311 311 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
312 312 return repo.vfs.join(longname, hash)
313 313
314 314
315 315 def findstorepath(repo, hash):
316 316 """Search through the local store path(s) to find the file for the given
317 317 hash. If the file is not found, its path in the primary store is returned.
318 318 The return value is a tuple of (path, exists(path)).
319 319 """
320 320 # For shared repos, the primary store is in the share source. But for
321 321 # backward compatibility, force a lookup in the local store if it wasn't
322 322 # found in the share source.
323 323 path = storepath(repo, hash, False)
324 324
325 325 if instore(repo, hash):
326 326 return (path, True)
327 327 elif repo.shared() and instore(repo, hash, True):
328 328 return storepath(repo, hash, True), True
329 329
330 330 return (path, False)
331 331
332 332
333 333 def copyfromcache(repo, hash, filename):
334 334 """Copy the specified largefile from the repo or system cache to
335 335 filename in the repository. Return true on success or false if the
336 336 file was not found in either cache (which should not happened:
337 337 this is meant to be called only after ensuring that the needed
338 338 largefile exists in the cache)."""
339 339 wvfs = repo.wvfs
340 340 path = findfile(repo, hash)
341 341 if path is None:
342 342 return False
343 343 wvfs.makedirs(wvfs.dirname(wvfs.join(filename)))
344 344 # The write may fail before the file is fully written, but we
345 345 # don't use atomic writes in the working copy.
346 346 with open(path, b'rb') as srcfd, wvfs(filename, b'wb') as destfd:
347 347 gothash = copyandhash(util.filechunkiter(srcfd), destfd)
348 348 if gothash != hash:
349 349 repo.ui.warn(
350 350 _(b'%s: data corruption in %s with hash %s\n')
351 351 % (filename, path, gothash)
352 352 )
353 353 wvfs.unlink(filename)
354 354 return False
355 355 return True
356 356
357 357
358 358 def copytostore(repo, ctx, file, fstandin):
359 359 wvfs = repo.wvfs
360 360 hash = readasstandin(ctx[fstandin])
361 361 if instore(repo, hash):
362 362 return
363 363 if wvfs.exists(file):
364 364 copytostoreabsolute(repo, wvfs.join(file), hash)
365 365 else:
366 366 repo.ui.warn(
367 367 _(b"%s: largefile %s not available from local store\n")
368 368 % (file, hash)
369 369 )
370 370
371 371
372 372 def copyalltostore(repo, node):
373 373 '''Copy all largefiles in a given revision to the store'''
374 374
375 375 ctx = repo[node]
376 376 for filename in ctx.files():
377 377 realfile = splitstandin(filename)
378 378 if realfile is not None and filename in ctx.manifest():
379 379 copytostore(repo, ctx, realfile, filename)
380 380
381 381
382 382 def copytostoreabsolute(repo, file, hash):
383 383 if inusercache(repo.ui, hash):
384 384 link(usercachepath(repo.ui, hash), storepath(repo, hash))
385 385 else:
386 386 util.makedirs(os.path.dirname(storepath(repo, hash)))
387 387 with open(file, b'rb') as srcf:
388 388 with util.atomictempfile(
389 389 storepath(repo, hash), createmode=repo.store.createmode
390 390 ) as dstf:
391 391 for chunk in util.filechunkiter(srcf):
392 392 dstf.write(chunk)
393 393 linktousercache(repo, hash)
394 394
395 395
396 396 def linktousercache(repo, hash):
397 397 """Link / copy the largefile with the specified hash from the store
398 398 to the cache."""
399 399 path = usercachepath(repo.ui, hash)
400 400 link(storepath(repo, hash), path)
401 401
402 402
403 403 def getstandinmatcher(repo, rmatcher=None):
404 404 '''Return a match object that applies rmatcher to the standin directory'''
405 405 wvfs = repo.wvfs
406 406 standindir = shortname
407 407
408 408 # no warnings about missing files or directories
409 409 badfn = lambda f, msg: None
410 410
411 411 if rmatcher and not rmatcher.always():
412 412 pats = [wvfs.join(standindir, pat) for pat in rmatcher.files()]
413 413 if not pats:
414 414 pats = [wvfs.join(standindir)]
415 415 match = scmutil.match(repo[None], pats, badfn=badfn)
416 416 else:
417 417 # no patterns: relative to repo root
418 418 match = scmutil.match(repo[None], [wvfs.join(standindir)], badfn=badfn)
419 419 return match
420 420
421 421
422 422 def composestandinmatcher(repo, rmatcher):
423 423 """Return a matcher that accepts standins corresponding to the
424 424 files accepted by rmatcher. Pass the list of files in the matcher
425 425 as the paths specified by the user."""
426 426 smatcher = getstandinmatcher(repo, rmatcher)
427 427 isstandin = smatcher.matchfn
428 428
429 429 def composedmatchfn(f):
430 430 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
431 431
432 432 smatcher.matchfn = composedmatchfn
433 433
434 434 return smatcher
435 435
436 436
437 437 def standin(filename):
438 438 """Return the repo-relative path to the standin for the specified big
439 439 file."""
440 440 # Notes:
441 441 # 1) Some callers want an absolute path, but for instance addlargefiles
442 442 # needs it repo-relative so it can be passed to repo[None].add(). So
443 443 # leave it up to the caller to use repo.wjoin() to get an absolute path.
444 444 # 2) Join with '/' because that's what dirstate always uses, even on
445 445 # Windows. Change existing separator to '/' first in case we are
446 446 # passed filenames from an external source (like the command line).
447 447 return shortnameslash + util.pconvert(filename)
448 448
449 449
450 450 def isstandin(filename):
451 451 """Return true if filename is a big file standin. filename must be
452 452 in Mercurial's internal form (slash-separated)."""
453 453 return filename.startswith(shortnameslash)
454 454
455 455
456 456 def splitstandin(filename):
457 457 # Split on / because that's what dirstate always uses, even on Windows.
458 458 # Change local separator to / first just in case we are passed filenames
459 459 # from an external source (like the command line).
460 460 bits = util.pconvert(filename).split(b'/', 1)
461 461 if len(bits) == 2 and bits[0] == shortname:
462 462 return bits[1]
463 463 else:
464 464 return None
465 465
466 466
467 467 def updatestandin(repo, lfile, standin):
468 468 """Re-calculate hash value of lfile and write it into standin
469 469
470 470 This assumes that "lfutil.standin(lfile) == standin", for efficiency.
471 471 """
472 472 file = repo.wjoin(lfile)
473 473 if repo.wvfs.exists(lfile):
474 474 hash = hashfile(file)
475 475 executable = getexecutable(file)
476 476 writestandin(repo, standin, hash, executable)
477 477 else:
478 478 raise error.Abort(_(b'%s: file not found!') % lfile)
479 479
480 480
481 481 def readasstandin(fctx):
482 482 """read hex hash from given filectx of standin file
483 483
484 484 This encapsulates how "standin" data is stored into storage layer."""
485 485 return fctx.data().strip()
486 486
487 487
488 488 def writestandin(repo, standin, hash, executable):
489 489 '''write hash to <repo.root>/<standin>'''
490 490 repo.wwrite(standin, hash + b'\n', executable and b'x' or b'')
491 491
492 492
493 493 def copyandhash(instream, outfile):
494 494 """Read bytes from instream (iterable) and write them to outfile,
495 495 computing the SHA-1 hash of the data along the way. Return the hash."""
496 496 hasher = hashutil.sha1(b'')
497 497 for data in instream:
498 498 hasher.update(data)
499 499 outfile.write(data)
500 500 return hex(hasher.digest())
501 501
502 502
503 503 def hashfile(file):
504 504 if not os.path.exists(file):
505 505 return b''
506 506 with open(file, b'rb') as fd:
507 507 return hexsha1(fd)
508 508
509 509
510 510 def getexecutable(filename):
511 511 mode = os.stat(filename).st_mode
512 512 return (
513 513 (mode & stat.S_IXUSR)
514 514 and (mode & stat.S_IXGRP)
515 515 and (mode & stat.S_IXOTH)
516 516 )
517 517
518 518
519 519 def urljoin(first, second, *arg):
520 520 def join(left, right):
521 521 if not left.endswith(b'/'):
522 522 left += b'/'
523 523 if right.startswith(b'/'):
524 524 right = right[1:]
525 525 return left + right
526 526
527 527 url = join(first, second)
528 528 for a in arg:
529 529 url = join(url, a)
530 530 return url
531 531
532 532
533 533 def hexsha1(fileobj):
534 534 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
535 535 object data"""
536 536 h = hashutil.sha1()
537 537 for chunk in util.filechunkiter(fileobj):
538 538 h.update(chunk)
539 539 return hex(h.digest())
540 540
541 541
542 542 def httpsendfile(ui, filename):
543 543 return httpconnection.httpsendfile(ui, filename, b'rb')
544 544
545 545
546 546 def unixpath(path):
547 547 '''Return a version of path normalized for use with the lfdirstate.'''
548 548 return util.pconvert(os.path.normpath(path))
549 549
550 550
551 551 def islfilesrepo(repo):
552 552 '''Return true if the repo is a largefile repo.'''
553 553 if b'largefiles' in repo.requirements and any(
554 554 shortnameslash in f[1] for f in repo.store.datafiles()
555 555 ):
556 556 return True
557 557
558 558 return any(openlfdirstate(repo.ui, repo, False))
559 559
560 560
561 561 class storeprotonotcapable(Exception):
562 562 def __init__(self, storetypes):
563 563 self.storetypes = storetypes
564 564
565 565
566 566 def getstandinsstate(repo):
567 567 standins = []
568 568 matcher = getstandinmatcher(repo)
569 569 wctx = repo[None]
570 570 for standin in repo.dirstate.walk(
571 571 matcher, subrepos=[], unknown=False, ignored=False
572 572 ):
573 573 lfile = splitstandin(standin)
574 574 try:
575 575 hash = readasstandin(wctx[standin])
576 576 except IOError:
577 577 hash = None
578 578 standins.append((lfile, hash))
579 579 return standins
580 580
581 581
582 582 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
583 583 lfstandin = standin(lfile)
584 584 if lfstandin not in repo.dirstate:
585 585 lfdirstate.hacky_extension_update_file(
586 586 lfile,
587 587 p1_tracked=False,
588 588 wc_tracked=False,
589 589 )
590 590 else:
591 591 entry = repo.dirstate.get_entry(lfstandin)
592 592 lfdirstate.hacky_extension_update_file(
593 593 lfile,
594 594 wc_tracked=entry.tracked,
595 595 p1_tracked=entry.p1_tracked,
596 596 p2_info=entry.p2_info,
597 597 possibly_dirty=True,
598 598 )
599 599
600 600
601 601 def markcommitted(orig, ctx, node):
602 602 repo = ctx.repo()
603 603
604 lfdirstate = openlfdirstate(repo.ui, repo)
605 with lfdirstate.changing_parents(repo):
604 with repo.dirstate.changing_parents(repo):
606 605 orig(node)
607 606
608 607 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
609 608 # because files coming from the 2nd parent are omitted in the latter.
610 609 #
611 610 # The former should be used to get targets of "synclfdirstate",
612 611 # because such files:
613 612 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
614 613 # - have to be marked as "n" after commit, but
615 614 # - aren't listed in "repo[node].files()"
616 615
616 lfdirstate = openlfdirstate(repo.ui, repo)
617 617 for f in ctx.files():
618 618 lfile = splitstandin(f)
619 619 if lfile is not None:
620 620 synclfdirstate(repo, lfdirstate, lfile, False)
621 lfdirstate.write(repo.currenttransaction())
622 621
623 622 # As part of committing, copy all of the largefiles into the cache.
624 623 #
625 624 # Using "node" instead of "ctx" implies additional "repo[node]"
626 625 # lookup while copyalltostore(), but can omit redundant check for
627 626 # files comming from the 2nd parent, which should exist in store
628 627 # at merging.
629 628 copyalltostore(repo, node)
630 629
631 630
632 631 def getlfilestoupdate(oldstandins, newstandins):
633 632 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
634 633 filelist = []
635 634 for f in changedstandins:
636 635 if f[0] not in filelist:
637 636 filelist.append(f[0])
638 637 return filelist
639 638
640 639
641 640 def getlfilestoupload(repo, missing, addfunc):
642 641 makeprogress = repo.ui.makeprogress
643 642 with makeprogress(
644 643 _(b'finding outgoing largefiles'),
645 644 unit=_(b'revisions'),
646 645 total=len(missing),
647 646 ) as progress:
648 647 for i, n in enumerate(missing):
649 648 progress.update(i)
650 649 parents = [p for p in repo[n].parents() if p != repo.nullid]
651 650
652 651 with lfstatus(repo, value=False):
653 652 ctx = repo[n]
654 653
655 654 files = set(ctx.files())
656 655 if len(parents) == 2:
657 656 mc = ctx.manifest()
658 657 mp1 = ctx.p1().manifest()
659 658 mp2 = ctx.p2().manifest()
660 659 for f in mp1:
661 660 if f not in mc:
662 661 files.add(f)
663 662 for f in mp2:
664 663 if f not in mc:
665 664 files.add(f)
666 665 for f in mc:
667 666 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
668 667 files.add(f)
669 668 for fn in files:
670 669 if isstandin(fn) and fn in ctx:
671 670 addfunc(fn, readasstandin(ctx[fn]))
672 671
673 672
674 673 def updatestandinsbymatch(repo, match):
675 674 """Update standins in the working directory according to specified match
676 675
677 676 This returns (possibly modified) ``match`` object to be used for
678 677 subsequent commit process.
679 678 """
680 679
681 680 ui = repo.ui
682 681
683 682 # Case 1: user calls commit with no specific files or
684 683 # include/exclude patterns: refresh and commit all files that
685 684 # are "dirty".
686 685 if match is None or match.always():
687 686 # Spend a bit of time here to get a list of files we know
688 687 # are modified so we can compare only against those.
689 688 # It can cost a lot of time (several seconds)
690 689 # otherwise to update all standins if the largefiles are
691 690 # large.
692 691 lfdirstate = openlfdirstate(ui, repo)
693 692 dirtymatch = matchmod.always()
694 693 unsure, s, mtime_boundary = lfdirstate.status(
695 694 dirtymatch, subrepos=[], ignored=False, clean=False, unknown=False
696 695 )
697 696 modifiedfiles = unsure + s.modified + s.added + s.removed
698 697 lfiles = listlfiles(repo)
699 698 # this only loops through largefiles that exist (not
700 699 # removed/renamed)
701 700 for lfile in lfiles:
702 701 if lfile in modifiedfiles:
703 702 fstandin = standin(lfile)
704 703 if repo.wvfs.exists(fstandin):
705 704 # this handles the case where a rebase is being
706 705 # performed and the working copy is not updated
707 706 # yet.
708 707 if repo.wvfs.exists(lfile):
709 708 updatestandin(repo, lfile, fstandin)
710 709
711 710 return match
712 711
713 712 lfiles = listlfiles(repo)
714 713 match._files = repo._subdirlfs(match.files(), lfiles)
715 714
716 715 # Case 2: user calls commit with specified patterns: refresh
717 716 # any matching big files.
718 717 smatcher = composestandinmatcher(repo, match)
719 718 standins = repo.dirstate.walk(
720 719 smatcher, subrepos=[], unknown=False, ignored=False
721 720 )
722 721
723 722 # No matching big files: get out of the way and pass control to
724 723 # the usual commit() method.
725 724 if not standins:
726 725 return match
727 726
728 727 # Refresh all matching big files. It's possible that the
729 728 # commit will end up failing, in which case the big files will
730 729 # stay refreshed. No harm done: the user modified them and
731 730 # asked to commit them, so sooner or later we're going to
732 731 # refresh the standins. Might as well leave them refreshed.
733 732 lfdirstate = openlfdirstate(ui, repo)
734 733 for fstandin in standins:
735 734 lfile = splitstandin(fstandin)
736 735 if lfdirstate.get_entry(lfile).tracked:
737 736 updatestandin(repo, lfile, fstandin)
738 737
739 738 # Cook up a new matcher that only matches regular files or
740 739 # standins corresponding to the big files requested by the
741 740 # user. Have to modify _files to prevent commit() from
742 741 # complaining "not tracked" for big files.
743 742 match = copy.copy(match)
744 743 origmatchfn = match.matchfn
745 744
746 745 # Check both the list of largefiles and the list of
747 746 # standins because if a largefile was removed, it
748 747 # won't be in the list of largefiles at this point
749 748 match._files += sorted(standins)
750 749
751 750 actualfiles = []
752 751 for f in match._files:
753 752 fstandin = standin(f)
754 753
755 754 # For largefiles, only one of the normal and standin should be
756 755 # committed (except if one of them is a remove). In the case of a
757 756 # standin removal, drop the normal file if it is unknown to dirstate.
758 757 # Thus, skip plain largefile names but keep the standin.
759 758 if f in lfiles or fstandin in standins:
760 759 if not repo.dirstate.get_entry(fstandin).removed:
761 760 if not repo.dirstate.get_entry(f).removed:
762 761 continue
763 762 elif not repo.dirstate.get_entry(f).any_tracked:
764 763 continue
765 764
766 765 actualfiles.append(f)
767 766 match._files = actualfiles
768 767
769 768 def matchfn(f):
770 769 if origmatchfn(f):
771 770 return f not in lfiles
772 771 else:
773 772 return f in standins
774 773
775 774 match.matchfn = matchfn
776 775
777 776 return match
778 777
779 778
780 779 class automatedcommithook:
781 780 """Stateful hook to update standins at the 1st commit of resuming
782 781
783 782 For efficiency, updating standins in the working directory should
784 783 be avoided while automated committing (like rebase, transplant and
785 784 so on), because they should be updated before committing.
786 785
787 786 But the 1st commit of resuming automated committing (e.g. ``rebase
788 787 --continue``) should update them, because largefiles may be
789 788 modified manually.
790 789 """
791 790
792 791 def __init__(self, resuming):
793 792 self.resuming = resuming
794 793
795 794 def __call__(self, repo, match):
796 795 if self.resuming:
797 796 self.resuming = False # avoids updating at subsequent commits
798 797 return updatestandinsbymatch(repo, match)
799 798 else:
800 799 return match
801 800
802 801
803 802 def getstatuswriter(ui, repo, forcibly=None):
804 803 """Return the function to write largefiles specific status out
805 804
806 805 If ``forcibly`` is ``None``, this returns the last element of
807 806 ``repo._lfstatuswriters`` as "default" writer function.
808 807
809 808 Otherwise, this returns the function to always write out (or
810 809 ignore if ``not forcibly``) status.
811 810 """
812 811 if forcibly is None and util.safehasattr(repo, b'_largefilesenabled'):
813 812 return repo._lfstatuswriters[-1]
814 813 else:
815 814 if forcibly:
816 815 return ui.status # forcibly WRITE OUT
817 816 else:
818 817 return lambda *msg, **opts: None # forcibly IGNORE
General Comments 0
You need to be logged in to leave comments. Login now