change dircache into dirstate
authormpm@selenic.com
Thu, 02 Jun 2005 17:39:29 -0800
changeset 220 3113a94c1bff3f166a3f33ec8698b549ed1811e0
parent 219 8ff4532376a41375b54ffade603d524c706606b4
child 221 2bfe525ef6ca5e74d505464d3d85389a13a2b464
push id1
push usergszorc@mozilla.com
push dateWed, 18 Mar 2015 16:34:57 +0000
change dircache into dirstate -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 change dircache into dirstate The dircache now tracks adds and removes directly diffdir now makes a proper distinction between added and unknown files Add a forget command to unadd files Undo tries to fix up the state of just the files in the undone commit Add and remove complain about files that are not in a proper state of existence manifest hash: ca0cd6abc5e119670acf11a54fefa2bc986eadf3 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.0 (GNU/Linux) iD8DBQFCn7TRywK+sNU5EO8RAhnSAKC2oHg1HJOCGsvpUYj4SBEq0HmuJQCgr5gl jEBTs5AFD5IhF73YAgrcnkE= =prQA -----END PGP SIGNATURE-----
hg
mercurial/commands.py
mercurial/hg.py
--- a/hg
+++ b/hg
@@ -62,31 +62,29 @@ def diff(files = None, node1 = None, nod
         mmap2 = repo.manifest.read(change[0])
         (c, a, d) = repo.diffrevs(node1, node2)
         def read(f): return repo.file(f).read(mmap2[f])
         date2 = date(change)
     else:
         date2 = time.asctime()
         if not node1:
             node1 = repo.current
-        (c, a, d) = repo.diffdir(repo.root, node1)
+        (c, a, d, u) = repo.diffdir(repo.root, node1)
         a = [] # ignore unknown files in repo, by popular request
         def read(f): return file(os.path.join(repo.root, f)).read()
 
     change = repo.changelog.read(node1)
     mmap = repo.manifest.read(change[0])
     date1 = date(change)
 
     if files:
         c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
 
     for f in c:
-        to = ""
-        if mmap.has_key(f):
-            to = repo.file(f).read(mmap[f])
+        to = repo.file(f).read(mmap[f])
         tn = read(f)
         sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
     for f in a:
         to = ""
         tn = read(f)
         sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
     for f in d:
         to = repo.file(f).read(mmap[f])
@@ -127,16 +125,19 @@ except IOError:
 
 relpath = None
 if os.getcwd() != repo.root:
     relpath = os.getcwd()[len(repo.root) + 1: ]
 
 elif cmd == "add":
     repo.add(args)
 
+elif cmd == "forget":
+    repo.forget(args)
+
 elif cmd == "remove" or cmd == "rm" or cmd == "del" or cmd == "delete":
     repo.remove(args)
 
 elif cmd == "commit" or cmd == "checkin" or cmd == "ci":
     if 1:
         if len(args) > 0:
             repo.commit(repo.current, args)
         else:
@@ -245,17 +246,17 @@ elif cmd == "debugchangegroup":
     for chunk in repo.changegroup(newer):
         sys.stdout.write(chunk)
 
 elif cmd == "debugaddchangegroup":
     data = sys.stdin.read()
     repo.addchangegroup(data)
 
 elif cmd == "addremove":
-    (c, a, d) = repo.diffdir(repo.root, repo.current)
+    (c, a, d, u) = repo.diffdir(repo.root, repo.current)
     repo.add(a)
     repo.remove(d)
     
 elif cmd == "history":
     for i in range(repo.changelog.count()):
         n = repo.changelog.node(i)
         changes = repo.changelog.read(n)
         (p1, p2) = repo.changelog.parents(n)
@@ -350,18 +351,18 @@ elif cmd == "debugindexdot":
     for i in range(r.count()):
         e = r.index[i]
         print "\t%d -> %d" % (r.rev(e[4]), i)
         if e[5] != hg.nullid:
             print "\t%d -> %d" % (r.rev(e[5]), i)
     print "}"
 
 elif cmd == "merge":
-    (c, a, d) = repo.diffdir(repo.root, repo.current)
-    if c:
+    (c, a, d, u) = repo.diffdir(repo.root, repo.current)
+    if c or a or d:
         ui.warn("aborting (outstanding changes in working directory)\n")
         sys.exit(1)
 
     if args:
         paths = {}
         try:
             pf = os.path.join(os.environ["HOME"], ".hgpaths")
             for l in file(pf):
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -65,18 +65,18 @@ def init(ui):
 
 def branch(ui, path):
     '''branch from a local repository'''
     # this should eventually support remote repos
     os.system("cp -al %s/.hg .hg" % path)
 
 def checkout(ui, repo, changeset=None):
     '''checkout a given changeset or the current tip'''
-    (c, a, d) = repo.diffdir(repo.root, repo.current)
-    if c:
+    (c, a, d, u) = repo.diffdir(repo.root, repo.current)
+    if c or a or d:
         ui.warn("aborting (outstanding changes in working directory)\n")
         sys.exit(1)
 
     node = repo.changelog.tip()
     if changeset:
         node = repo.lookup(changeset)
     repo.checkout(node)
 
@@ -124,22 +124,23 @@ def annotate(u, repo, *args, **ops):
 
 def status(ui, repo):
     '''show changed files in the working directory
 
 C = changed
 A = added
 R = removed
 ? = not tracked'''
-    (c, a, d) = repo.diffdir(repo.root, repo.current)
-    (c, a, d) = map(lambda x: relfilter(repo, x), (c, a, d))
+    (c, a, d, u) = repo.diffdir(repo.root, repo.current)
+    (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
 
     for f in c: print "C", f
-    for f in a: print "?", f
+    for f in a: print "A", f
     for f in d: print "R", f
+    for f in u: print "?", f
 
 def undo(ui, repo):
     repo.undo()
 
 table = {
     "init": (init, [], 'hg init'),
     "branch|clone": (branch, [], 'hg branch [path]'),
     "help": (help, [], 'hg help [command]'),
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -144,85 +144,101 @@ class changelog(revlog):
                 os.environ.get("EMAIL") or
                 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
         date = date or "%d %d" % (time.time(), time.timezone)
         list.sort()
         l = [hex(manifest), user, date] + list + ["", desc]
         text = "\n".join(l)
         return self.addrevision(text, transaction, self.count(), p1, p2)
 
-class dircache:
+class dirstate:
     def __init__(self, opener, ui):
         self.opener = opener
         self.dirty = 0
         self.ui = ui
         self.map = None
+
     def __del__(self):
-        if self.dirty: self.write()
+        if self.dirty:
+            self.write()
+
     def __getitem__(self, key):
         try:
             return self.map[key]
         except TypeError:
             self.read()
             return self[key]
-        
+
+    def __contains__(self, key):
+        if not self.map: self.read()
+        return key in self.map
+
+    def state(self, key):
+        try:
+            return self[key][0]
+        except KeyError:
+            return "?"
+
     def read(self):
         if self.map is not None: return self.map
 
         self.map = {}
         try:
-            st = self.opener("dircache").read()
+            st = self.opener("dirstate").read()
         except: return
 
         pos = 0
         while pos < len(st):
-            e = struct.unpack(">llll", st[pos:pos+16])
-            l = e[3]
-            pos += 16
+            e = struct.unpack(">cllll", st[pos:pos+17])
+            l = e[4]
+            pos += 17
             f = st[pos:pos + l]
-            self.map[f] = e[:3]
+            self.map[f] = e[:4]
             pos += l
         
-    def update(self, files):
+    def update(self, files, state):
+        ''' current states:
+        n  normal
+        i  invalid
+        r  marked for removal
+        a  marked for addition'''
+
         if not files: return
         self.read()
         self.dirty = 1
         for f in files:
-            try:
-                s = os.stat(f)
-                self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
-            except IOError:
-                self.remove(f)
+            if state == "r":
+                self.map[f] = ('r', 0, 0, 0)
+            else:
+                try:
+                    s = os.stat(f)
+                    self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
+                except OSError:
+                    if state != "i": raise
+                    self.map[f] = ('r', 0, 0, 0)
 
-    def taint(self, files):
-        if not files: return
-        self.read()
-        self.dirty = 1
-        for f in files:
-            self.map[f] = (0, -1, 0)
-
-    def remove(self, files):
+    def forget(self, files):
         if not files: return
         self.read()
         self.dirty = 1
         for f in files:
             try:
                 del self.map[f]
             except KeyError:
-                self.ui.warn("Not in dircache: %s\n" % f)
+                self.ui.warn("not in dirstate: %s!\n" % f)
                 pass
 
     def clear(self):
         self.map = {}
         self.dirty = 1
 
     def write(self):
-        st = self.opener("dircache", "w")
+        st = self.opener("dirstate", "w")
         for f, e in self.map.items():
-            e = struct.pack(">llll", e[0], e[1], e[2], len(f))
+            e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
             st.write(e + f)
         self.dirty = 0
 
     def copy(self):
         self.read()
         return self.map.copy()
 
 # used to avoid circular references so destructors work
@@ -275,17 +291,17 @@ class localrepository:
 
         self.opener = opener(self.path)
         self.manifest = manifest(self.opener)
         self.changelog = changelog(self.opener)
         self.ignorelist = None
         self.tags = None
 
         if not self.remote:
-            self.dircache = dircache(self.opener, ui)
+            self.dirstate = dirstate(self.opener, ui)
             try:
                 self.current = bin(self.opener("current").read())
             except IOError:
                 self.current = None
 
     def setcurrent(self, node):
         self.current = node
         self.opener("current", "w").write(hex(node))
@@ -335,31 +351,28 @@ class localrepository:
             self.ui.status("attempting to rollback interrupted transaction\n")
             return rollback(self.opener, self.join("recover"))
         else:
             self.ui.warn("no interrupted transaction available\n")
 
     def undo(self):
         self.lock()
         if os.path.exists(self.join("undo")):
+            f = self.changelog.read(self.changelog.tip())[3]
             self.ui.status("attempting to rollback last transaction\n")
             rollback(self.opener, self.join("undo"))
             self.manifest = manifest(self.opener)
             self.changelog = changelog(self.opener)
 
-            self.ui.status("discarding dircache\n")
+            self.ui.status("discarding dirstate\n")
             node = self.changelog.tip()
-            mf = self.changelog.read(node)[0]
-            mm = self.manifest.read(mf)
-            f = mm.keys()
             f.sort()
 
             self.setcurrent(node)
-            self.dircache.clear()
-            self.dircache.taint(f)
+            self.dirstate.update(f, 'i')
         
         else:
             self.ui.warn("no undo information available\n")
 
     def lock(self, wait = 1):
         try:
             return lock.lock(self.join("lock"), 0)
         except lock.LockHeld, inst:
@@ -384,48 +397,56 @@ class localrepository:
             r = self.file(f)
             prev = pmmap.get(f, nullid)
             mmap[f] = r.add(t, tr, linkrev, prev)
 
         mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
         n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
         tr.close()
         self.setcurrent(n)
-        self.dircache.clear()
-        self.dircache.update(mmap)
+        self.dirstate.clear()
+        self.dirstate.update(mmap.keys(), "n")
 
-    def commit(self, parent, update = None, text = ""):
+    def commit(self, parent, files = None, text = ""):
         self.lock()
-        try:
-            remove = [ l[:-1] for l in self.opener("to-remove") ]
-            os.unlink(self.join("to-remove"))
 
-        except IOError:
-            remove = []
+        commit = []
+        remove = []
+        if files:
+            for f in files:
+                s = self.dirstate.state(f)
+                if s in 'cai':
+                    commit.append(f)
+                elif s == 'r':
+                    remove.append(f)
+                else:
+                    self.warn("%s not tracked!\n")
+        else:
+            (c, a, d, u) = self.diffdir(self.root, parent)
+            commit = c + a
+            remove = d
 
-        if update == None:
-            update = self.diffdir(self.root, parent)[0]
-
-        if not update:
+        if not commit and not remove:
             self.ui.status("nothing changed\n")
             return
 
         tr = self.transaction()
 
         # check in files
         new = {}
         linkrev = self.changelog.count()
-        update.sort()
-        for f in update:
+        commit.sort()
+        for f in commit:
             self.ui.note(f + "\n")
             try:
                 t = file(f).read()
             except IOError:
-                remove.append(f)
-                continue
+                self.warn("trouble committing %s!\n" % f)
+                raise
+
             r = self.file(f)
             new[f] = r.add(t, tr, linkrev)
 
         # update manifest
         mmap = self.manifest.read(self.manifest.tip())
         mmap.update(new)
         for f in remove:
             del mmap[f]
@@ -439,18 +460,18 @@ class localrepository:
         edittext += "".join(["HG: changed %s\n" % f for f in new])
         edittext += "".join(["HG: removed %s\n" % f for f in remove])
         edittext = self.ui.edit(edittext)
 
         n = self.changelog.add(mnode, new, edittext, tr)
         tr.close()
 
         self.setcurrent(n)
-        self.dircache.update(new)
-        self.dircache.remove(remove)
+        self.dirstate.update(new, "n")
+        self.dirstate.forget(remove)
 
     def checkout(self, node):
         # checkout is really dumb at the moment
         # it ought to basically merge
         change = self.changelog.read(node)
         l = self.manifest.read(change[0]).items()
         l.sort()
 
@@ -460,30 +481,31 @@ class localrepository:
             t = self.file(f).revision(n)
             try:
                 file(f, "w").write(t)
             except IOError:
                 os.makedirs(os.path.dirname(f))
                 file(f, "w").write(t)
 
         self.setcurrent(node)
-        self.dircache.clear()
-        self.dircache.update([f for f,n in l])
+        self.dirstate.clear()
+        self.dirstate.update([f for f,n in l], "n")
 
     def diffdir(self, path, changeset):
         changed = []
+        added = []
+        unknown = []
         mf = {}
-        added = []
 
         if changeset:
             change = self.changelog.read(changeset)
             mf = self.manifest.read(change[0])
 
         if changeset == self.current:
-            dc = self.dircache.copy()
+            dc = self.dirstate.copy()
         else:
             dc = dict.fromkeys(mf)
 
         def fcmp(fn):
             t1 = file(os.path.join(self.root, fn)).read()
             t2 = self.file(fn).revision(mf[fn])
             return cmp(t1, t2)
 
@@ -493,32 +515,41 @@ class localrepository:
             
             for f in files:
                 fn = os.path.join(d, f)
                 try: s = os.stat(os.path.join(self.root, fn))
                 except: continue
                 if fn in dc:
                     c = dc[fn]
                     del dc[fn]
-                    if not c or c[1] < 0:
+                    if not c:
                         if fcmp(fn):
                             changed.append(fn)
-                    elif c[1] != s.st_size:
+                    if c[0] == 'i':
+                        if fn not in mf:
+                            added.append(fn)
+                        elif fcmp(fn):
+                            changed.append(fn)
+                    elif c[0] == 'a':
+                        added.append(fn)
+                    elif c[0] == 'r':
+                        unknown.append(fn)
+                    elif c[2] != s.st_size:
                         changed.append(fn)
-                    elif c[0] != s.st_mode or c[2] != s.st_mtime:
+                    elif c[1] != s.st_mode or c[3] != s.st_mtime:
                         if fcmp(fn):
                             changed.append(fn)
                 else:
                     if self.ignore(fn): continue
-                    added.append(fn)
+                    unknown.append(fn)
 
         deleted = dc.keys()
         deleted.sort()
 
-        return (changed, added, deleted)
+        return (changed, added, deleted, unknown)
 
     def diffrevs(self, node1, node2):
         changed, added = [], []
 
         change = self.changelog.read(node1)
         mf1 = self.manifest.read(change[0])
         change = self.changelog.read(node2)
         mf2 = self.manifest.read(change[0])
@@ -532,22 +563,41 @@ class localrepository:
                 added.append(fn)
                 
         deleted = mf1.keys()
         deleted.sort()
     
         return (changed, added, deleted)
 
     def add(self, list):
-        self.dircache.taint(list)
+        for f in list:
+            p = os.path.join(self.root, f)
+            if not os.path.isfile(p):
+                self.ui.warn("%s does not exist!\n" % f)
+            elif self.dirstate.state(f) == 'n':
+                self.ui.warn("%s already tracked!\n" % f)
+            else:
+                self.dirstate.update([f], "a")
+
+    def forget(self, list):
+        for f in list:
+            if self.dirstate.state(f) not in 'ai':
+                self.ui.warn("%s not added!\n" % f)
+            else:
+                self.dirstate.forget([f])
 
     def remove(self, list):
-        dl = self.opener("to-remove", "a")
         for f in list:
-            dl.write(f + "\n")
+            p = os.path.join(self.root, f)
+            if os.path.isfile(p):
+                self.ui.warn("%s still exists!\n" % f)
+            elif f not in self.dirstate:
+                self.ui.warn("%s not tracked!\n" % f)
+            else:
+                self.dirstate.update([f], "r")
 
     def branches(self, nodes):
         if not nodes: nodes = [self.changelog.tip()]
         b = []
         for n in nodes:
             t = n
             while n:
                 p = self.changelog.parents(n)