changeset 2953:3d5547845158

fix issue 322. do not allow to add files that shadow files or directories already in dirstate.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Fri, 18 Aug 2006 21:03:29 -0700
parents 7356fa3cff2c
children 51ba31494c69
files mercurial/dirstate.py mercurial/strutil.py mercurial/util.py tests/test-issue322 tests/test-issue322.out
diffstat 5 files changed, 105 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -10,7 +10,7 @@ of the GNU General Public License, incor
 from node import *
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "struct os time bisect stat util re errno")
+demandload(globals(), "struct os time bisect stat strutil util re errno")
 
 class dirstate(object):
     format = ">cllll"
@@ -22,6 +22,7 @@ class dirstate(object):
         self.ui = ui
         self.map = None
         self.pl = None
+        self.dirs = None
         self.copies = {}
         self.ignorefunc = None
         self.blockignore = False
@@ -197,6 +198,38 @@ class dirstate(object):
     def copied(self, file):
         return self.copies.get(file, None)
 
+    def initdirs(self):
+        if self.dirs is None:
+            self.dirs = {}
+            for f in self.map:
+                self.updatedirs(f, 1)
+        
+    def updatedirs(self, path, delta):
+        if self.dirs is not None:
+            for c in strutil.findall(path, '/'):
+                pc = path[:c]
+                self.dirs.setdefault(pc, 0)
+                self.dirs[pc] += delta
+
+    def checkshadows(self, files):
+        def prefixes(f):
+            for c in strutil.rfindall(f, '/'):
+                yield f[:c]
+        self.lazyread()
+        self.initdirs()
+        seendirs = {}
+        for f in files:
+            if self.dirs.get(f):
+                raise util.Abort(_('directory named %r already in dirstate') %
+                                 f)
+            for d in prefixes(f):
+                if d in seendirs:
+                    break
+                if d in self.map:
+                    raise util.Abort(_('file named %r already in dirstate') %
+                                     d)
+                seendirs[d] = True
+
     def update(self, files, state, **kw):
         ''' current states:
         n  normal
@@ -207,10 +240,16 @@ class dirstate(object):
         if not files: return
         self.lazyread()
         self.markdirty()
+        if state == "a":
+            self.initdirs()
+            self.checkshadows(files)
         for f in files:
             if state == "r":
                 self.map[f] = ('r', 0, 0, 0)
+                self.updatedirs(f, -1)
             else:
+                if state == "a":
+                    self.updatedirs(f, 1)
                 s = os.lstat(self.wjoin(f))
                 st_size = kw.get('st_size', s.st_size)
                 st_mtime = kw.get('st_mtime', s.st_mtime)
@@ -222,9 +261,11 @@ class dirstate(object):
         if not files: return
         self.lazyread()
         self.markdirty()
+        self.initdirs()
         for f in files:
             try:
                 del self.map[f]
+                self.updatedirs(f, -1)
             except KeyError:
                 self.ui.warn(_("not in dirstate: %s!\n") % f)
                 pass
@@ -232,6 +273,7 @@ class dirstate(object):
     def clear(self):
         self.map = {}
         self.copies = {}
+        self.dirs = None
         self.markdirty()
 
     def rebuild(self, parent, files):
new file mode 100644
--- /dev/null
+++ b/mercurial/strutil.py
@@ -0,0 +1,34 @@
+# strutil.py - string utilities for Mercurial
+#
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+def findall(haystack, needle, start=0, end=None):
+    if end is None:
+        end = len(haystack)
+    if end < 0:
+        end += len(haystack)
+    if start < 0:
+        start += len(haystack)
+    while start < end:
+        c = haystack.find(needle, start, end)
+        if c == -1:
+            break
+        yield c
+        start = c + 1
+
+def rfindall(haystack, needle, start=0, end=None):
+    if end is None:
+        end = len(haystack)
+    if end < 0:
+        end += len(haystack)
+    if start < 0:
+        start += len(haystack)
+    while end >= 0:
+        c = haystack.rfind(needle, start, end)
+        if c == -1:
+            break
+        yield c
+        end = c - 1
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -995,4 +995,3 @@ def drop_scheme(scheme, path):
         if path.startswith('//'):
             path = path[2:]
     return path
-    
--- a/tests/test-issue322
+++ b/tests/test-issue322
@@ -14,15 +14,7 @@ echo a > a/a
 echo % should fail - would corrupt dirstate
 hg add a/a
 
-echo % should fail - if add succeeded, would corrupt manifest
-hg commit -mb
-
-echo % should fail if commit succeeded - manifest is corrupt
-hg verify
-
 cd ..      
-echo % should succeed, but manifest is corrupt
-hg --debug --traceback clone a b
 
 echo % directory replaced with file
 
@@ -38,8 +30,20 @@ echo a > a
 echo % should fail - would corrupt dirstate
 hg add a
 
-echo % should fail - if add succeeded, would corrupt manifest
-hg commit -mb a
+cd ..
+
+echo % directory replaced with file
 
-echo % should fail if commit succeeded - manifest is corrupt
-hg verify
+hg init d
+cd d
+mkdir b
+mkdir b/c
+echo a > b/c/d
+hg commit -Ama
+rm -rf b
+echo a > b
+
+echo % should fail - would corrupt dirstate
+hg add b
+
+exit 0
new file mode 100644
--- /dev/null
+++ b/tests/test-issue322.out
@@ -0,0 +1,12 @@
+% file replaced with directory
+adding a
+% should fail - would corrupt dirstate
+abort: file named 'a' already in dirstate
+% directory replaced with file
+adding a/a
+% should fail - would corrupt dirstate
+abort: directory named 'a' already in dirstate
+% directory replaced with file
+adding b/c/d
+% should fail - would corrupt dirstate
+abort: directory named 'b' already in dirstate