mercurial/dirstate.py
changeset 48684 568f63b5a30f
parent 48683 111f5a0cbcaa
child 48685 21ac6aedd5e5
equal deleted inserted replaced
48683:111f5a0cbcaa 48684:568f63b5a30f
    10 import collections
    10 import collections
    11 import contextlib
    11 import contextlib
    12 import errno
    12 import errno
    13 import os
    13 import os
    14 import stat
    14 import stat
       
    15 import uuid
    15 
    16 
    16 from .i18n import _
    17 from .i18n import _
    17 from .pycompat import delattr
    18 from .pycompat import delattr
    18 
    19 
    19 from hgdemandimport import tracing
    20 from hgdemandimport import tracing
    21 from . import (
    22 from . import (
    22     dirstatemap,
    23     dirstatemap,
    23     encoding,
    24     encoding,
    24     error,
    25     error,
    25     match as matchmod,
    26     match as matchmod,
       
    27     node,
    26     pathutil,
    28     pathutil,
    27     policy,
    29     policy,
    28     pycompat,
    30     pycompat,
    29     scmutil,
    31     scmutil,
    30     sparse,
    32     sparse,
    97         root,
    99         root,
    98         validate,
   100         validate,
    99         sparsematchfn,
   101         sparsematchfn,
   100         nodeconstants,
   102         nodeconstants,
   101         use_dirstate_v2,
   103         use_dirstate_v2,
       
   104         use_tracked_key=False,
   102     ):
   105     ):
   103         """Create a new dirstate object.
   106         """Create a new dirstate object.
   104 
   107 
   105         opener is an open()-like callable that can be used to open the
   108         opener is an open()-like callable that can be used to open the
   106         dirstate file; root is the root of the directory tracked by
   109         dirstate file; root is the root of the directory tracked by
   107         the dirstate.
   110         the dirstate.
   108         """
   111         """
   109         self._use_dirstate_v2 = use_dirstate_v2
   112         self._use_dirstate_v2 = use_dirstate_v2
       
   113         self._use_tracked_key = use_tracked_key
   110         self._nodeconstants = nodeconstants
   114         self._nodeconstants = nodeconstants
   111         self._opener = opener
   115         self._opener = opener
   112         self._validate = validate
   116         self._validate = validate
   113         self._root = root
   117         self._root = root
   114         self._sparsematchfn = sparsematchfn
   118         self._sparsematchfn = sparsematchfn
   115         # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
   119         # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
   116         # UNC path pointing to root share (issue4557)
   120         # UNC path pointing to root share (issue4557)
   117         self._rootdir = pathutil.normasprefix(root)
   121         self._rootdir = pathutil.normasprefix(root)
       
   122         # True is any internal state may be different
   118         self._dirty = False
   123         self._dirty = False
       
   124         # True if the set of tracked file may be different
       
   125         self._dirty_tracked_set = False
   119         self._ui = ui
   126         self._ui = ui
   120         self._filecache = {}
   127         self._filecache = {}
   121         self._parentwriters = 0
   128         self._parentwriters = 0
   122         self._filename = b'dirstate'
   129         self._filename = b'dirstate'
       
   130         self._filename_tk = b'dirstate-tracked-key'
   123         self._pendingfilename = b'%s.pending' % self._filename
   131         self._pendingfilename = b'%s.pending' % self._filename
   124         self._plchangecallbacks = {}
   132         self._plchangecallbacks = {}
   125         self._origpl = None
   133         self._origpl = None
   126         self._mapcls = dirstatemap.dirstatemap
   134         self._mapcls = dirstatemap.dirstatemap
   127         # Access and cache cwd early, so we don't access it for the first time
   135         # Access and cache cwd early, so we don't access it for the first time
   407 
   415 
   408         for a in ("_map", "_branch", "_ignore"):
   416         for a in ("_map", "_branch", "_ignore"):
   409             if a in self.__dict__:
   417             if a in self.__dict__:
   410                 delattr(self, a)
   418                 delattr(self, a)
   411         self._dirty = False
   419         self._dirty = False
       
   420         self._dirty_tracked_set = False
   412         self._parentwriters = 0
   421         self._parentwriters = 0
   413         self._origpl = None
   422         self._origpl = None
   414 
   423 
   415     def copy(self, source, dest):
   424     def copy(self, source, dest):
   416         """Mark dest as a copy of source. Unmark dest if source is None."""
   425         """Mark dest as a copy of source. Unmark dest if source is None."""
   444         if entry is None or not entry.tracked:
   453         if entry is None or not entry.tracked:
   445             self._check_new_tracked_filename(filename)
   454             self._check_new_tracked_filename(filename)
   446         pre_tracked = self._map.set_tracked(filename)
   455         pre_tracked = self._map.set_tracked(filename)
   447         if reset_copy:
   456         if reset_copy:
   448             self._map.copymap.pop(filename, None)
   457             self._map.copymap.pop(filename, None)
       
   458         if pre_tracked:
       
   459             self._dirty_tracked_set = True
   449         return pre_tracked
   460         return pre_tracked
   450 
   461 
   451     @requires_no_parents_change
   462     @requires_no_parents_change
   452     def set_untracked(self, filename):
   463     def set_untracked(self, filename):
   453         """a "public" method for generic code to mark a file as untracked
   464         """a "public" method for generic code to mark a file as untracked
   458         return True the file was previously tracked, False otherwise.
   469         return True the file was previously tracked, False otherwise.
   459         """
   470         """
   460         ret = self._map.set_untracked(filename)
   471         ret = self._map.set_untracked(filename)
   461         if ret:
   472         if ret:
   462             self._dirty = True
   473             self._dirty = True
       
   474             self._dirty_tracked_set = True
   463         return ret
   475         return ret
   464 
   476 
   465     @requires_no_parents_change
   477     @requires_no_parents_change
   466     def set_clean(self, filename, parentfiledata):
   478     def set_clean(self, filename, parentfiledata):
   467         """record that the current state of the file on disk is known to be clean"""
   479         """record that the current state of the file on disk is known to be clean"""
   542         # note: I do not think we need to double check name clash here since we
   554         # note: I do not think we need to double check name clash here since we
   543         # are in a update/merge case that should already have taken care of
   555         # are in a update/merge case that should already have taken care of
   544         # this. The test agrees
   556         # this. The test agrees
   545 
   557 
   546         self._dirty = True
   558         self._dirty = True
       
   559         old_entry = self._map.get(filename)
       
   560         if old_entry is None:
       
   561             prev_tracked = False
       
   562         else:
       
   563             prev_tracked = old_entry.tracked
       
   564         if prev_tracked != wc_tracked:
       
   565             self._dirty_tracked_set = True
   547 
   566 
   548         self._map.reset_state(
   567         self._map.reset_state(
   549             filename,
   568             filename,
   550             wc_tracked,
   569             wc_tracked,
   551             p1_tracked,
   570             p1_tracked,
   700 
   719 
   701     def write(self, tr):
   720     def write(self, tr):
   702         if not self._dirty:
   721         if not self._dirty:
   703             return
   722             return
   704 
   723 
   705         filename = self._filename
   724         write_key = self._use_tracked_key and self._dirty_tracked_set
   706         if tr:
   725         if tr:
   707             # delay writing in-memory changes out
   726             # delay writing in-memory changes out
       
   727             if write_key:
       
   728                 tr.addfilegenerator(
       
   729                     b'dirstate-0-key-pre',
       
   730                     (self._filename_tk,),
       
   731                     lambda f: self._write_tracked_key(tr, f),
       
   732                     location=b'plain',
       
   733                 )
   708             tr.addfilegenerator(
   734             tr.addfilegenerator(
   709                 b'dirstate-1-main',
   735                 b'dirstate-1-main',
   710                 (self._filename,),
   736                 (self._filename,),
   711                 lambda f: self._writedirstate(tr, f),
   737                 lambda f: self._writedirstate(tr, f),
   712                 location=b'plain',
   738                 location=b'plain',
   713             )
   739             )
       
   740             if write_key:
       
   741                 tr.addfilegenerator(
       
   742                     b'dirstate-2-key-post',
       
   743                     (self._filename_tk,),
       
   744                     lambda f: self._write_tracked_key(tr, f),
       
   745                     location=b'plain',
       
   746                 )
   714             return
   747             return
   715 
   748 
   716         file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True)
   749         file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True)
       
   750         if write_key:
       
   751             # we change the key-file before changing the dirstate to make sure
       
   752             # reading invalidate there cache before we start writing
       
   753             with file(self._filename_tk) as f:
       
   754                 self._write_tracked_key(tr, f)
   717         with file(self._filename) as f:
   755         with file(self._filename) as f:
   718             self._writedirstate(tr, f)
   756             self._writedirstate(tr, f)
       
   757         if write_key:
       
   758             # we update the key-file after writing to make sure reader have a
       
   759             # key that match the newly written content
       
   760             with file(self._filename_tk) as f:
       
   761                 self._write_tracked_key(tr, f)
   719 
   762 
   720     def addparentchangecallback(self, category, callback):
   763     def addparentchangecallback(self, category, callback):
   721         """add a callback to be called when the wd parents are changed
   764         """add a callback to be called when the wd parents are changed
   722 
   765 
   723         Callback will be called with the following arguments:
   766         Callback will be called with the following arguments:
   734             for c, callback in sorted(
   777             for c, callback in sorted(
   735                 pycompat.iteritems(self._plchangecallbacks)
   778                 pycompat.iteritems(self._plchangecallbacks)
   736             ):
   779             ):
   737                 callback(self, self._origpl, self._pl)
   780                 callback(self, self._origpl, self._pl)
   738             self._origpl = None
   781             self._origpl = None
   739 
       
   740         self._map.write(tr, st)
   782         self._map.write(tr, st)
   741         self._dirty = False
   783         self._dirty = False
       
   784         self._dirty_tracked_set = False
       
   785 
       
   786     def _write_tracked_key(self, tr, f):
       
   787         key = node.hex(uuid.uuid4().bytes)
       
   788         f.write(b"1\n%s\n" % key)  # 1 is the format version
   742 
   789 
   743     def _dirignore(self, f):
   790     def _dirignore(self, f):
   744         if self._ignore(f):
   791         if self._ignore(f):
   745             return True
   792             return True
   746         for p in pathutil.finddirs(f):
   793         for p in pathutil.finddirs(f):