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): |