diff --git a/hg b/hg
index 8ff4532376a41375b54ffade603d524c706606b4_aGc=..3113a94c1bff3f166a3f33ec8698b549ed1811e0_aGc= 100644
--- a/hg
+++ b/hg
@@ -67,7 +67,7 @@
         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()
 
@@ -79,9 +79,7 @@
         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:
@@ -132,6 +130,9 @@
 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)
 
@@ -250,7 +251,7 @@
     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)
     
@@ -355,8 +356,8 @@
     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)
 
diff --git a/mercurial/commands.py b/mercurial/commands.py
index 8ff4532376a41375b54ffade603d524c706606b4_bWVyY3VyaWFsL2NvbW1hbmRzLnB5..3113a94c1bff3f166a3f33ec8698b549ed1811e0_bWVyY3VyaWFsL2NvbW1hbmRzLnB5 100644
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -70,8 +70,8 @@
 
 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)
 
@@ -129,7 +129,7 @@
 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
@@ -134,4 +134,4 @@
 
     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
@@ -137,4 +137,5 @@
     for f in d: print "R", f
+    for f in u: print "?", f
 
 def undo(ui, repo):
     repo.undo()
diff --git a/mercurial/hg.py b/mercurial/hg.py
index 8ff4532376a41375b54ffade603d524c706606b4_bWVyY3VyaWFsL2hnLnB5..3113a94c1bff3f166a3f33ec8698b549ed1811e0_bWVyY3VyaWFsL2hnLnB5 100644
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -149,9 +149,9 @@
         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
@@ -153,6 +153,7 @@
     def __init__(self, opener, ui):
         self.opener = opener
         self.dirty = 0
         self.ui = ui
         self.map = None
+
     def __del__(self):
@@ -158,8 +159,10 @@
     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]
@@ -160,12 +163,22 @@
     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:
@@ -167,10 +180,10 @@
     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):
@@ -173,8 +186,8 @@
         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]
@@ -180,4 +193,4 @@
             f = st[pos:pos + l]
-            self.map[f] = e[:3]
+            self.map[f] = e[:4]
             pos += l
         
@@ -182,7 +195,13 @@
             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:
@@ -185,10 +204,14 @@
         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)
 
@@ -194,12 +217,5 @@
 
-    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
@@ -207,7 +223,7 @@
             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):
@@ -215,5 +231,5 @@
         self.dirty = 1
 
     def write(self):
-        st = self.opener("dircache", "w")
+        st = self.opener("dirstate", "w")
         for f, e in self.map.items():
@@ -219,5 +235,5 @@
         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
 
@@ -280,7 +296,7 @@
         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:
@@ -340,8 +356,9 @@
     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)
 
@@ -343,7 +360,7 @@
             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()
@@ -349,7 +366,4 @@
             node = self.changelog.tip()
-            mf = self.changelog.read(node)[0]
-            mm = self.manifest.read(mf)
-            f = mm.keys()
             f.sort()
 
             self.setcurrent(node)
@@ -353,8 +367,7 @@
             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")
@@ -389,6 +402,6 @@
         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")
 
@@ -394,3 +407,3 @@
 
-    def commit(self, parent, update = None, text = ""):
+    def commit(self, parent, files = None, text = ""):
         self.lock()
@@ -396,5 +409,2 @@
         self.lock()
-        try:
-            remove = [ l[:-1] for l in self.opener("to-remove") ]
-            os.unlink(self.join("to-remove"))
 
@@ -400,4 +410,17 @@
 
-        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
 
@@ -403,8 +426,5 @@
 
-        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
 
@@ -413,9 +433,9 @@
         # 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:
@@ -418,9 +438,10 @@
             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)
 
@@ -444,8 +465,8 @@
         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
@@ -465,8 +486,8 @@
                 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 = []
@@ -470,4 +491,6 @@
 
     def diffdir(self, path, changeset):
         changed = []
+        added = []
+        unknown = []
         mf = {}
@@ -473,8 +496,7 @@
         mf = {}
-        added = []
 
         if changeset:
             change = self.changelog.read(changeset)
             mf = self.manifest.read(change[0])
 
         if changeset == self.current:
@@ -475,10 +497,10 @@
 
         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)
 
@@ -498,6 +520,6 @@
                 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)
@@ -502,4 +524,13 @@
                         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)
@@ -505,6 +536,6 @@
                         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
@@ -507,9 +538,9 @@
                         if fcmp(fn):
                             changed.append(fn)
                 else:
                     if self.ignore(fn): continue
-                    added.append(fn)
+                    unknown.append(fn)
 
         deleted = dc.keys()
         deleted.sort()
 
@@ -512,8 +543,8 @@
 
         deleted = dc.keys()
         deleted.sort()
 
-        return (changed, added, deleted)
+        return (changed, added, deleted, unknown)
 
     def diffrevs(self, node1, node2):
         changed, added = [], []
@@ -537,6 +568,20 @@
         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):
@@ -541,4 +586,3 @@
 
     def remove(self, list):
-        dl = self.opener("to-remove", "a")
         for f in list:
@@ -544,5 +588,11 @@
         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()]