dirstate.py 52.3 KB
Newer Older
1
2
# dirstate.py - working directory tracking for mercurial
#
3
# Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4
5
#
# This software may be used and distributed according to the terms of the
Matt Mackall's avatar
Matt Mackall committed
6
# GNU General Public License version 2 or any later version.
mpm's avatar
mpm committed
7

Gregory Szorc's avatar
Gregory Szorc committed
8
9
from __future__ import absolute_import

10
import collections
11
import contextlib
Gregory Szorc's avatar
Gregory Szorc committed
12
13
14
15
16
import errno
import os
import stat

from .i18n import _
17
from .pycompat import delattr
18
19
20

from hgdemandimport import tracing

Gregory Szorc's avatar
Gregory Szorc committed
21
from . import (
22
    dirstatemap,
Gregory Szorc's avatar
Gregory Szorc committed
23
24
25
26
    encoding,
    error,
    match as matchmod,
    pathutil,
27
    policy,
28
    pycompat,
Gregory Szorc's avatar
Gregory Szorc committed
29
    scmutil,
30
    sparse,
Gregory Szorc's avatar
Gregory Szorc committed
31
32
    util,
)
mpm's avatar
mpm committed
33

34
35
36
37
38
from .interfaces import (
    dirstate as intdirstate,
    util as interfaceutil,
)

39
40
parsers = policy.importmod('parsers')
rustmod = policy.importrust('dirstate')
41

Matt Mackall's avatar
Matt Mackall committed
42
propertycache = util.propertycache
Idan Kamara's avatar
Idan Kamara committed
43
filecache = scmutil.filecache
44
_rangemask = dirstatemap.rangemask
Idan Kamara's avatar
Idan Kamara committed
45

46
DirstateItem = dirstatemap.DirstateItem
47

48

Idan Kamara's avatar
Idan Kamara committed
49
50
class repocache(filecache):
    """filecache for files in .hg/"""
51

Idan Kamara's avatar
Idan Kamara committed
52
53
    def join(self, obj, fname):
        return obj._opener.join(fname)
Matt Mackall's avatar
Matt Mackall committed
54

55

56
57
class rootcache(filecache):
    """filecache for files in the repository root"""
58

59
60
61
    def join(self, obj, fname):
        return obj._join(fname)

62

63
64
65
66
def _getfsnow(vfs):
    '''Get "now" timestamp on filesystem'''
    tmpfd, tmpname = vfs.mkstemp()
    try:
67
        return os.fstat(tmpfd)[stat.ST_MTIME]
68
69
70
71
    finally:
        os.close(tmpfd)
        vfs.unlink(tmpname)

72

73
74
75
76
77
78
79
80
81
82
83
def requires_parents_change(func):
    def wrap(self, *args, **kwargs):
        if not self.pendingparentchange():
            msg = 'calling `%s` outside of a parentchange context'
            msg %= func.__name__
            raise error.ProgrammingError(msg)
        return func(self, *args, **kwargs)

    return wrap


84
85
def requires_no_parents_change(func):
    def wrap(self, *args, **kwargs):
86
        if self.pendingparentchange():
87
88
89
90
91
92
93
94
            msg = 'calling `%s` inside of a parentchange context'
            msg %= func.__name__
            raise error.ProgrammingError(msg)
        return func(self, *args, **kwargs)

    return wrap


95
@interfaceutil.implementer(intdirstate.idirstate)
96
class dirstate(object):
97
    def __init__(
98
99
100
101
102
103
104
105
        self,
        opener,
        ui,
        root,
        validate,
        sparsematchfn,
        nodeconstants,
        use_dirstate_v2,
106
    ):
durin42's avatar
durin42 committed
107
        """Create a new dirstate object.
108
109
110
111

        opener is an open()-like callable that can be used to open the
        dirstate file; root is the root of the directory tracked by
        the dirstate.
durin42's avatar
durin42 committed
112
        """
113
        self._use_dirstate_v2 = use_dirstate_v2
114
        self._nodeconstants = nodeconstants
Matt Mackall's avatar
Matt Mackall committed
115
        self._opener = opener
116
        self._validate = validate
Matt Mackall's avatar
Matt Mackall committed
117
        self._root = root
118
        self._sparsematchfn = sparsematchfn
119
120
        # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
        # UNC path pointing to root share (issue4557)
121
        self._rootdir = pathutil.normasprefix(root)
122
        self._dirty = False
Martin Geisler's avatar
Martin Geisler committed
123
        self._lastnormaltime = 0
Matt Mackall's avatar
Matt Mackall committed
124
        self._ui = ui
Idan Kamara's avatar
Idan Kamara committed
125
        self._filecache = {}
126
        self._parentwriters = 0
127
128
        self._filename = b'dirstate'
        self._pendingfilename = b'%s.pending' % self._filename
129
130
        self._plchangecallbacks = {}
        self._origpl = None
131
        self._mapcls = dirstatemap.dirstatemap
132
133
134
135
        # Access and cache cwd early, so we don't access it for the first time
        # after a working-copy update caused it to not exist (accessing it then
        # raises an exception).
        self._cwd
136

137
138
139
140
141
142
143
    def prefetch_parents(self):
        """make sure the parents are loaded

        Used to avoid a race condition.
        """
        self._pl

144
145
    @contextlib.contextmanager
    def parentchange(self):
durin42's avatar
durin42 committed
146
        """Context manager for handling dirstate parents.
147
148
149
150

        If an exception occurs in the scope of the context manager,
        the incoherent dirstate won't be written when wlock is
        released.
durin42's avatar
durin42 committed
151
        """
152
153
154
155
156
157
158
159
160
        self._parentwriters += 1
        yield
        # Typically we want the "undo" step of a context manager in a
        # finally block so it happens even when an exception
        # occurs. In this case, however, we only want to decrement
        # parentwriters if the code in the with statement exits
        # normally, so we don't have a try/finally here on purpose.
        self._parentwriters -= 1

161
    def pendingparentchange(self):
durin42's avatar
durin42 committed
162
        """Returns true if the dirstate is in the middle of a set of changes
163
        that modify the dirstate parent.
durin42's avatar
durin42 committed
164
        """
165
        return self._parentwriters > 0
mpm's avatar
mpm committed
166

Matt Mackall's avatar
Matt Mackall committed
167
168
    @propertycache
    def _map(self):
169
        """Return the dirstate contents (see documentation for dirstatemap)."""
170
        self._map = self._mapcls(
171
172
173
174
175
            self._ui,
            self._opener,
            self._root,
            self._nodeconstants,
            self._use_dirstate_v2,
176
        )
Matt Mackall's avatar
Matt Mackall committed
177
178
        return self._map

179
180
181
182
183
184
185
186
187
188
189
190
191
    @property
    def _sparsematcher(self):
        """The matcher for the sparse checkout.

        The working directory may not include every file from a manifest. The
        matcher obtained by this property will match a path if it is to be
        included in the working directory.
        """
        # TODO there is potential to cache this property. For now, the matcher
        # is resolved on every access. (But the called function does use a
        # cache to keep the lookup fast.)
        return self._sparsematchfn()

192
    @repocache(b'branch')
Matt Mackall's avatar
Matt Mackall committed
193
194
    def _branch(self):
        try:
195
            return self._opener.read(b"branch").strip() or b"default"
196
        except IOError as inst:
197
198
            if inst.errno != errno.ENOENT:
                raise
199
            return b"default"
Matt Mackall's avatar
Matt Mackall committed
200

201
    @property
Matt Mackall's avatar
Matt Mackall committed
202
    def _pl(self):
203
        return self._map.parents()
Matt Mackall's avatar
Matt Mackall committed
204

205
206
    def hasdir(self, d):
        return self._map.hastrackeddir(d)
207

208
    @rootcache(b'.hgignore')
Matt Mackall's avatar
Matt Mackall committed
209
    def _ignore(self):
210
        files = self._ignorefiles()
211
        if not files:
212
            return matchmod.never()
213

214
215
        pats = [b'include:%s' % f for f in files]
        return matchmod.match(self._root, b'', [], pats, warn=self._ui.warn)
Matt Mackall's avatar
Matt Mackall committed
216
217
218

    @propertycache
    def _slash(self):
219
        return self._ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/'
Matt Mackall's avatar
Matt Mackall committed
220
221
222
223
224
225
226

    @propertycache
    def _checklink(self):
        return util.checklink(self._root)

    @propertycache
    def _checkexec(self):
227
        return bool(util.checkexec(self._root))
Matt Mackall's avatar
Matt Mackall committed
228
229
230

    @propertycache
    def _checkcase(self):
231
        return not util.fscasesensitive(self._join(b'.hg'))
Matt Mackall's avatar
Matt Mackall committed
232

233
    def _join(self, f):
234
        # much faster than os.path.join()
235
        # it's safe because f is always a relative path
236
        return self._rootdir + f
mpm's avatar
mpm committed
237

238
239
    def flagfunc(self, buildfallback):
        if self._checklink and self._checkexec:
240

241
            def f(x):
242
243
244
                try:
                    st = os.lstat(self._join(x))
                    if util.statislink(st):
245
                        return b'l'
246
                    if util.statisexec(st):
247
                        return b'x'
248
249
                except OSError:
                    pass
250
                return b''
251

252
253
254
            return f

        fallback = buildfallback()
Matt Mackall's avatar
Matt Mackall committed
255
        if self._checklink:
256

Matt Mackall's avatar
Matt Mackall committed
257
            def f(x):
258
                if os.path.islink(self._join(x)):
259
260
261
262
                    return b'l'
                if b'x' in fallback(x):
                    return b'x'
                return b''
263

Matt Mackall's avatar
Matt Mackall committed
264
265
            return f
        if self._checkexec:
266

Matt Mackall's avatar
Matt Mackall committed
267
            def f(x):
268
269
                if b'l' in fallback(x):
                    return b'l'
270
                if util.isexec(self._join(x)):
271
272
                    return b'x'
                return b''
273

Matt Mackall's avatar
Matt Mackall committed
274
            return f
275
276
        else:
            return fallback
Matt Mackall's avatar
Matt Mackall committed
277

278
279
    @propertycache
    def _cwd(self):
280
        # internal config: ui.forcecwd
281
        forcecwd = self._ui.config(b'ui', b'forcecwd')
282
283
        if forcecwd:
            return forcecwd
284
        return encoding.getcwd()
285

mpm's avatar
mpm committed
286
    def getcwd(self):
durin42's avatar
durin42 committed
287
        """Return the path from which a canonical path is calculated.
288
289
290
291

        This path should be used to resolve file patterns or to convert
        canonical paths back to file paths for display. It shouldn't be
        used to get real file paths. Use vfs functions instead.
durin42's avatar
durin42 committed
292
        """
293
        cwd = self._cwd
294
        if cwd == self._root:
295
            return b''
Matt Mackall's avatar
Matt Mackall committed
296
297
        # self._root ends with a path separator if self._root is '/' or 'C:\'
        rootsep = self._root
298
        if not util.endswithsep(rootsep):
299
            rootsep += pycompat.ossep
300
        if cwd.startswith(rootsep):
301
            return cwd[len(rootsep) :]
302
303
304
        else:
            # we're outside the repo. return an absolute path.
            return cwd
mpm's avatar
mpm committed
305

306
307
308
    def pathto(self, f, cwd=None):
        if cwd is None:
            cwd = self.getcwd()
Matt Mackall's avatar
Matt Mackall committed
309
        path = util.pathto(self._root, cwd, f)
310
        if self._slash:
311
            return util.pconvert(path)
312
        return path
313

mpm's avatar
mpm committed
314
    def __getitem__(self, key):
durin42's avatar
durin42 committed
315
        """Return the current state of key (a filename) in the dirstate.
316

317
318
319
320
321
322
        States are:
          n  normal
          m  needs merging
          r  marked for removal
          a  marked for addition
          ?  not tracked
323
324
325
326

        XXX The "state" is a bit obscure to be in the "public" API. we should
        consider migrating all user of this to going through the dirstate entry
        instead.
durin42's avatar
durin42 committed
327
        """
328
329
        msg = b"don't use dirstate[file], use dirstate.get_entry(file)"
        util.nouideprecwarn(msg, b'6.1', stacklevel=2)
330
331
332
333
        entry = self._map.get(key)
        if entry is not None:
            return entry.state
        return b'?'
mpm's avatar
mpm committed
334

335
336
337
338
339
340
341
    def get_entry(self, path):
        """return a DirstateItem for the associated path"""
        entry = self._map.get(path)
        if entry is None:
            return DirstateItem()
        return entry

mpm's avatar
mpm committed
342
    def __contains__(self, key):
Matt Mackall's avatar
Matt Mackall committed
343
344
345
        return key in self._map

    def __iter__(self):
346
        return iter(sorted(self._map))
mpm's avatar
mpm committed
347

348
    def items(self):
349
        return pycompat.iteritems(self._map)
350

351
352
    iteritems = items

mpm's avatar
mpm committed
353
    def parents(self):
354
        return [self._validate(p) for p in self._pl]
mpm's avatar
mpm committed
355

356
357
358
359
360
361
    def p1(self):
        return self._validate(self._pl[0])

    def p2(self):
        return self._validate(self._pl[1])

362
363
364
365
366
    @property
    def in_merge(self):
        """True if a merge is in progress"""
        return self._pl[1] != self._nodeconstants.nullid

367
    def branch(self):
368
        return encoding.tolocal(self._branch)
369

370
    def setparents(self, p1, p2=None):
371
372
        """Set dirstate parents to p1 and p2.

373
        When moving from two parents to one, "merged" entries a
374
375
376
377
378
        adjusted to normal and previous copy records discarded and
        returned by the call.

        See localrepo.setparents()
        """
379
380
        if p2 is None:
            p2 = self._nodeconstants.nullid
381
        if self._parentwriters == 0:
382
            raise ValueError(
383
384
                b"cannot set dirstate parent outside of "
                b"dirstate.parentchange context manager"
385
            )
386

387
        self._dirty = True
388
        oldp2 = self._pl[1]
389
390
        if self._origpl is None:
            self._origpl = self._pl
391
        nullid = self._nodeconstants.nullid
392
393
394
        # True if we need to fold p2 related state back to a linear case
        fold_p2 = oldp2 != nullid and p2 == nullid
        return self._map.setparents(p1, p2, fold_p2=fold_p2)
mpm's avatar
mpm committed
395

396
    def setbranch(self, branch):
397
        self.__class__._branch.set(self, encoding.fromlocal(branch))
398
        f = self._opener(b'branch', b'w', atomictemp=True, checkambig=True)
399
        try:
400
            f.write(self._branch + b'\n')
401
            f.close()
402
403
404

            # make sure filecache has the correct stat info for _branch after
            # replacing the underlying file
405
            ce = self._filecache[b'_branch']
406
407
            if ce:
                ce.refresh()
408
        except:  # re-raises
409
410
            f.discard()
            raise
411

412
    def invalidate(self):
durin42's avatar
durin42 committed
413
        """Causes the next access to reread the dirstate.
414
415
416

        This is different from localrepo.invalidatedirstate() because it always
        rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
durin42's avatar
durin42 committed
417
        check whether the dirstate has changed before rereading it."""
418

419
        for a in ("_map", "_branch", "_ignore"):
420
421
            if a in self.__dict__:
                delattr(self, a)
Martin Geisler's avatar
Martin Geisler committed
422
        self._lastnormaltime = 0
423
        self._dirty = False
424
        self._parentwriters = 0
425
        self._origpl = None
426

mpm's avatar
mpm committed
427
    def copy(self, source, dest):
428
        """Mark dest as a copy of source. Unmark dest if source is None."""
429
430
        if source == dest:
            return
431
        self._dirty = True
432
        if source is not None:
433
            self._map.copymap[dest] = source
434
435
        else:
            self._map.copymap.pop(dest, None)
mpm's avatar
mpm committed
436
437

    def copied(self, file):
438
        return self._map.copymap.get(file, None)
Matt Mackall's avatar
Matt Mackall committed
439
440

    def copies(self):
441
        return self._map.copymap
mpm's avatar
mpm committed
442

443
444
445
446
447
448
449
450
451
    @requires_no_parents_change
    def set_tracked(self, filename):
        """a "public" method for generic code to mark a file as tracked

        This function is to be called outside of "update/merge" case. For
        example by a command like `hg add X`.

        return True the file was previously untracked, False otherwise.
        """
452
        self._dirty = True
453
        entry = self._map.get(filename)
454
        if entry is None or not entry.tracked:
455
            self._check_new_tracked_filename(filename)
456
        return self._map.set_tracked(filename)
457

458
459
460
461
462
463
464
465
466
    @requires_no_parents_change
    def set_untracked(self, filename):
        """a "public" method for generic code to mark a file as untracked

        This function is to be called outside of "update/merge" case. For
        example by a command like `hg remove X`.

        return True the file was previously tracked, False otherwise.
        """
467
468
        ret = self._map.set_untracked(filename)
        if ret:
469
            self._dirty = True
470
        return ret
471

472
473
474
475
    @requires_no_parents_change
    def set_clean(self, filename, parentfiledata=None):
        """record that the current state of the file on disk is known to be clean"""
        self._dirty = True
476
477
478
479
        if parentfiledata:
            (mode, size, mtime) = parentfiledata
        else:
            (mode, size, mtime) = self._get_filedata(filename)
480
481
482
        if not self._map[filename].tracked:
            self._check_new_tracked_filename(filename)
        self._map.set_clean(filename, mode, size, mtime)
483
484
485
486
487
        if mtime > self._lastnormaltime:
            # Remember the most recent modification timeslot for status(),
            # to make sure we won't miss future size-preserving file content
            # modifications that happen within the same timeslot.
            self._lastnormaltime = mtime
488

489
490
491
492
493
494
    @requires_no_parents_change
    def set_possibly_dirty(self, filename):
        """record that the current state of the file on disk is unknown"""
        self._dirty = True
        self._map.set_possibly_dirty(filename)

495
    @requires_parents_change
496
    def update_file_p1(
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
        self,
        filename,
        p1_tracked,
    ):
        """Set a file as tracked in the parent (or not)

        This is to be called when adjust the dirstate to a new parent after an history
        rewriting operation.

        It should not be called during a merge (p2 != nullid) and only within
        a `with dirstate.parentchange():` context.
        """
        if self.in_merge:
            msg = b'update_file_reference should not be called when merging'
            raise error.ProgrammingError(msg)
        entry = self._map.get(filename)
        if entry is None:
            wc_tracked = False
        else:
            wc_tracked = entry.tracked
517
        if not (p1_tracked or wc_tracked):
518
            # the file is no longer relevant to anyone
519
520
            if self._map.get(filename) is not None:
                self._map.reset_state(filename)
521
                self._dirty = True
522
        elif (not p1_tracked) and wc_tracked:
523
524
            if entry is not None and entry.added:
                return  # avoid dropping copy information (maybe?)
525

526
        parentfiledata = None
527
        if wc_tracked and p1_tracked:
528
529
530
531
532
533
            parentfiledata = self._get_filedata(filename)

        self._map.reset_state(
            filename,
            wc_tracked,
            p1_tracked,
534
535
536
            # the underlying reference might have changed, we will have to
            # check it.
            has_meaningful_mtime=False,
537
538
            parentfiledata=parentfiledata,
        )
539
540
541
542
543
544
545
546
        if (
            parentfiledata is not None
            and parentfiledata[2] > self._lastnormaltime
        ):
            # Remember the most recent modification timeslot for status(),
            # to make sure we won't miss future size-preserving file content
            # modifications that happen within the same timeslot.
            self._lastnormaltime = parentfiledata[2]
547

548
549
550
551
552
553
    @requires_parents_change
    def update_file(
        self,
        filename,
        wc_tracked,
        p1_tracked,
554
        p2_info=False,
555
        possibly_dirty=False,
556
        parentfiledata=None,
557
558
559
560
561
562
563
564
    ):
        """update the information about a file in the dirstate

        This is to be called when the direstates parent changes to keep track
        of what is the file situation in regards to the working copy and its parent.

        This function must be called within a `dirstate.parentchange` context.

565
        note: the API is at an early stage and we might need to adjust it
566
567
568
        depending of what information ends up being relevant and useful to
        other processing.
        """
569
570
571
572
573
574
575
576

        # note: I do not think we need to double check name clash here since we
        # are in a update/merge case that should already have taken care of
        # this. The test agrees

        self._dirty = True

        need_parent_file_data = (
577
            not possibly_dirty and not p2_info and wc_tracked and p1_tracked
578
579
        )

580
        if need_parent_file_data and parentfiledata is None:
581
            parentfiledata = self._get_filedata(filename)
582
583
584
585
586

        self._map.reset_state(
            filename,
            wc_tracked,
            p1_tracked,
587
            p2_info=p2_info,
588
            has_meaningful_mtime=not possibly_dirty,
589
590
            parentfiledata=parentfiledata,
        )
591
592
593
594
595
596
597
598
        if (
            parentfiledata is not None
            and parentfiledata[2] > self._lastnormaltime
        ):
            # Remember the most recent modification timeslot for status(),
            # to make sure we won't miss future size-preserving file content
            # modifications that happen within the same timeslot.
            self._lastnormaltime = parentfiledata[2]
599

600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
    def _check_new_tracked_filename(self, filename):
        scmutil.checkfilename(filename)
        if self._map.hastrackeddir(filename):
            msg = _(b'directory %r already in dirstate')
            msg %= pycompat.bytestr(filename)
            raise error.Abort(msg)
        # shadows
        for d in pathutil.finddirs(filename):
            if self._map.hastrackeddir(d):
                break
            entry = self._map.get(d)
            if entry is not None and not entry.removed:
                msg = _(b'file %r in dirstate clashes with %r')
                msg %= (pycompat.bytestr(d), pycompat.bytestr(filename))
                raise error.Abort(msg)

616
617
618
619
620
621
622
623
    def _get_filedata(self, filename):
        """returns"""
        s = os.lstat(self._join(filename))
        mode = s.st_mode
        size = s.st_size
        mtime = s[stat.ST_MTIME]
        return (mode, size, mtime)

624
625
626
627
628
    def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
        if exists is None:
            exists = os.path.lexists(os.path.join(self._root, path))
        if not exists:
            # Maybe a path component exists
629
630
            if not ignoremissing and b'/' in path:
                d, f = path.rsplit(b'/', 1)
631
                d = self._normalize(d, False, ignoremissing, None)
632
                folded = d + b"/" + f
633
634
635
636
637
638
            else:
                # No path components, preserve original case
                folded = path
        else:
            # recursively normalize leading directory components
            # against dirstate
639
640
            if b'/' in normed:
                d, f = normed.rsplit(b'/', 1)
641
                d = self._normalize(d, False, ignoremissing, True)
642
643
                r = self._root + b"/" + d
                folded = d + b"/" + util.fspath(f, r)
644
645
646
647
648
649
            else:
                folded = util.fspath(normed, self._root)
            storemap[normed] = folded

        return folded

650
    def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
651
        normed = util.normcase(path)
652
        folded = self._map.filefoldmap.get(normed, None)
653
        if folded is None:
654
            if isknown:
655
                folded = path
656
            else:
657
658
659
                folded = self._discoverpath(
                    path, normed, ignoremissing, exists, self._map.filefoldmap
                )
660
        return folded
661

662
    def _normalize(self, path, isknown, ignoremissing=False, exists=None):
663
        normed = util.normcase(path)
664
        folded = self._map.filefoldmap.get(normed, None)
665
        if folded is None:
666
            folded = self._map.dirfoldmap.get(normed, None)
667
        if folded is None:
668
            if isknown:
669
                folded = path
670
            else:
671
672
                # store discovered result in dirfoldmap so that future
                # normalizefile calls don't start matching directories
673
674
675
                folded = self._discoverpath(
                    path, normed, ignoremissing, exists, self._map.dirfoldmap
                )
676
677
        return folded

678
    def normalize(self, path, isknown=False, ignoremissing=False):
durin42's avatar
durin42 committed
679
        """
680
681
682
        normalize the case of a pathname when on a casefolding filesystem

        isknown specifies whether the filename came from walking the
683
684
685
686
687
        disk, to avoid extra filesystem access.

        If ignoremissing is True, missing path are returned
        unchanged. Otherwise, we try harder to normalize possibly
        existing path components.
688
689
690
691
692
693

        The normalized case is determined based on the following precedence:

        - version of name already stored in the dirstate
        - version of name stored on disk
        - version provided via command arguments
durin42's avatar
durin42 committed
694
        """
695
696

        if self._checkcase:
697
            return self._normalize(path, isknown, ignoremissing)
698
        return path
699

700
    def clear(self):
701
        self._map.clear()
Martin Geisler's avatar
Martin Geisler committed
702
        self._lastnormaltime = 0
Alexis S. L. Carvalho's avatar
Alexis S. L. Carvalho committed
703
        self._dirty = True
704

705
    def rebuild(self, parent, allfiles, changedfiles=None):
706
        if changedfiles is None:
707
            # Rebuild entire dirstate
708
709
            to_lookup = allfiles
            to_drop = []
710
711
712
            lastnormaltime = self._lastnormaltime
            self.clear()
            self._lastnormaltime = lastnormaltime
713
714
715
716
717
718
719
720
721
722
723
724
725
726
        elif len(changedfiles) < 10:
            # Avoid turning allfiles into a set, which can be expensive if it's
            # large.
            to_lookup = []
            to_drop = []
            for f in changedfiles:
                if f in allfiles:
                    to_lookup.append(f)
                else:
                    to_drop.append(f)
        else:
            changedfilesset = set(changedfiles)
            to_lookup = changedfilesset & set(allfiles)
            to_drop = changedfilesset - to_lookup
727

728
729
        if self._origpl is None:
            self._origpl = self._pl
730
        self._map.setparents(parent, self._nodeconstants.nullid)
731
732

        for f in to_lookup:
733
734
735
736
737
738
739
740
741

            if self.in_merge:
                self.set_tracked(f)
            else:
                self._map.reset_state(
                    f,
                    wc_tracked=True,
                    p1_tracked=True,
                )
742
        for f in to_drop:
743
            self._map.reset_state(f)
744

745
        self._dirty = True
mpm's avatar
mpm committed
746

747
    def identity(self):
durin42's avatar
durin42 committed
748
        """Return identity of dirstate itself to detect changing in storage
749
750
751

        If identity of previous dirstate is equal to this, writing
        changes based on the former dirstate out can keep consistency.
durin42's avatar
durin42 committed
752
        """
753
        return self._map.identity
754

755
    def write(self, tr):
756
        if not self._dirty:
757
            return
758

759
        filename = self._filename
760
        if tr:
761
762
763
764
765
766
            # 'dirstate.write()' is not only for writing in-memory
            # changes out, but also for dropping ambiguous timestamp.
            # delayed writing re-raise "ambiguous timestamp issue".
            # See also the wiki page below for detail:
            # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan

767
            # record when mtime start to be ambiguous
768
            now = _getfsnow(self._opener)
769
770

            # delay writing in-memory changes out
771
            tr.addfilegenerator(
772
                b'dirstate',
773
                (self._filename,),
774
                lambda f: self._writedirstate(tr, f, now=now),
775
                location=b'plain',
776
            )
777
778
            return

779
        st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
780
        self._writedirstate(tr, st)
781

782
783
784
785
786
787
788
789
790
791
792
    def addparentchangecallback(self, category, callback):
        """add a callback to be called when the wd parents are changed

        Callback will be called with the following arguments:
            dirstate, (oldp1, oldp2), (newp1, newp2)

        Category is a unique identifier to allow overwriting an old callback
        with a newer callback.
        """
        self._plchangecallbacks[category] = callback

793
    def _writedirstate(self, tr, st, now=None):
794
795
        # notify callbacks about parents change
        if self._origpl is not None and self._origpl != self._pl:
796
797
798
            for c, callback in sorted(
                pycompat.iteritems(self._plchangecallbacks)
            ):
799
800
                callback(self, self._origpl, self._pl)
            self._origpl = None
801
802
803
804
805

        if now is None:
            # use the modification time of the newly created temporary file as the
            # filesystem's notion of 'now'
            now = util.fstat(st)[stat.ST_MTIME] & _rangemask
806
807
808

        # enough 'delaywrite' prevents 'pack_dirstate' from dropping
        # timestamp of each entries in dirstate, because of 'now > mtime'
809
        delaywrite = self._ui.configint(b'debug', b'dirstate.delaywrite')
810
        if delaywrite > 0:
811
            # do we have any files to delay for?
812
            for f, e in pycompat.iteritems(self._map):
813
                if e.need_delay(now):
814
815
                    import time  # to avoid useless import

816
817
818
819
820
821
                    # rather than sleep n seconds, sleep until the next
                    # multiple of n seconds
                    clock = time.time()
                    start = int(clock) - (int(clock) % delaywrite)
                    end = start + delaywrite
                    time.sleep(end - clock)
822
                    now = end  # trust our estimate that the end is near now
823
                    break
824

825
        self._map.write(tr, st, now)
826
        self._lastnormaltime = 0
827
        self._dirty = False
mpm's avatar
mpm committed
828

829
830
831
    def _dirignore(self, f):
        if self._ignore(f):
            return True
832
        for p in pathutil.finddirs(f):
833
            if self._ignore(p):
834
835
836
                return True
        return False

837
838
    def _ignorefiles(self):
        files = []
839
840
841
842
        if os.path.exists(self._join(b'.hgignore')):
            files.append(self._join(b'.hgignore'))
        for name, path in self._ui.configitems(b"ui"):
            if name == b'ignore' or name.startswith(b'ignore.'):
843
844
845
846
847
                # we need to use os.path.join here rather than self._join
                # because path is arbitrary and user-specified
                files.append(os.path.join(self._rootdir, util.expandpath(path)))
        return files

848
849
850
851
852
    def _ignorefileandline(self, f):
        files = collections.deque(self._ignorefiles())
        visited = set()
        while files:
            i = files.popleft()
853
854
855
            patterns = matchmod.readpatternfile(
                i, self._ui.warn, sourceinfo=True
            )
856
            for pattern, lineno, line in patterns:
857
858
                kind, p = matchmod._patsplit(pattern, b'glob')
                if kind == b"subinclude":
859
860
861
                    if p not in visited:
                        files.append(p)
                    continue
862
                m = matchmod.match(
863
                    self._root, b'', [], [pattern], warn=self._ui.warn
864
                )
865
866
867
                if m(f):
                    return (i, lineno, line)
            visited.add(i)
868
        return (None, -1, b"")
869

870
    def _walkexplicit(self, match, subrepos):
durin42's avatar
durin42 committed
871
        """Get stat data about the files explicitly specified by match.
Matt Mackall's avatar
Matt Mackall committed
872

873
        Return a triple (results, dirsfound, dirsnotfound).
874
875
        - results is a mapping from filename to stat result. It also contains
          listings mapping subrepos and .hg to None.
876
        - dirsfound is a list of files found to be directories.
877
        - dirsnotfound is a list of files that the dirstate thinks are
durin42's avatar
durin42 committed
878
          directories and that were not found."""
879

880
        def badtype(mode):
881
            kind = _(b'unknown')
882
            if stat.S_ISCHR(mode):
883
                kind = _(b'character device')
884
            elif stat.S_ISBLK(mode):