changeset 536:c15b4bc0a11c

Refactor diffrevs/diffdir into changes -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Refactor diffrevs/diffdir into changes Add dirstate.changes to replace most of diffdir Add localrepository.changes to replace diffrevs/diffdir This code can now efficiently check for changes in single files, and often without consulting the manifest. This should eventually make 'hg diff Makefile' in a large project much faster. This also fixes a bug where 'hg diff -r tip' failed to account for files that had been added but not committed yet. manifest hash: 20fde5d4b4cee49a76bcfe50f2dacf58b1f2258b -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.0 (GNU/Linux) iD8DBQFCxMxpywK+sNU5EO8RAhzOAJ9VLQJoC+hiRYQtTSPbDhXBEJfQZwCgpDx9 GAwQ9jZHNsgXckBfXNCkJV8= =hMuc -----END PGP SIGNATURE-----
author mpm@selenic.com
date Thu, 30 Jun 2005 20:54:01 -0800
parents fba26990604a
children 411e05b04ffa
files mercurial/commands.py mercurial/hg.py mercurial/hgweb.py tests/test-bad-pull.out tests/test-diffdir tests/test-diffdir.out
diffstat 6 files changed, 119 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -39,12 +39,12 @@ def dodiff(ui, repo, path, files = None,
     if node2:
         change = repo.changelog.read(node2)
         mmap2 = repo.manifest.read(change[0])
-        (c, a, d) = repo.diffrevs(node1, node2)
+        (c, a, d, u) = repo.changes(node1, node2)
         def read(f): return repo.file(f).read(mmap2[f])
         date2 = date(change)
     else:
         date2 = time.asctime()
-        (c, a, d, u) = repo.diffdir(path, node1)
+        (c, a, d, u) = repo.changes(None, node1, path)
         if not node1:
             node1 = repo.dirstate.parents()[0]
         def read(f): return repo.wfile(f).read()
@@ -124,7 +124,7 @@ def show_changeset(ui, repo, rev=0, chan
     ui.status("date:        %s\n" % time.asctime(
         time.localtime(float(changes[2].split(' ')[0]))))
     if ui.debugflag:
-        files = repo.diffrevs(changelog.parents(changenode)[0], changenode)
+        files = repo.changes(changelog.parents(changenode)[0], changenode)
         for key, value in zip(["files:", "files+:", "files-:"], files):
             if value:
                 ui.note("%-12s %s\n" % (key, " ".join(value)))
@@ -214,7 +214,7 @@ def addremove(ui, repo, *files):
             elif s not in 'nmai' and isfile:
                 u.append(f)
     else:
-        (c, a, d, u) = repo.diffdir(repo.root)
+        (c, a, d, u) = repo.changes(None, None)
     repo.add(u)
     repo.remove(d)
 
@@ -447,7 +447,7 @@ def identify(ui, repo):
         return
 
     hexfunc = ui.verbose and hg.hex or hg.short
-    (c, a, d, u) = repo.diffdir(repo.root)
+    (c, a, d, u) = repo.changes(None, None)
     output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
                         (c or a or d) and "+" or "")]
 
@@ -645,7 +645,7 @@ def status(ui, repo):
     R = removed
     ? = not tracked'''
 
-    (c, a, d, u) = repo.diffdir(os.getcwd())
+    (c, a, d, u) = repo.changes(None, None, os.getcwd())
     (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
 
     for f in c: print "C", f
@@ -660,7 +660,7 @@ def tag(ui, repo, name, rev = None, **op
 	ui.warn("abort: 'tip' is a reserved name!\n")
 	return -1
 
-    (c, a, d, u) = repo.diffdir(repo.root)
+    (c, a, d, u) = repo.changes(None, None)
     for x in (c, a, d, u):
 	if ".hgtags" in x:
 	    ui.warn("abort: working copy of .hgtags is changed!\n")
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -288,9 +288,56 @@ class dirstate:
             st.write(e + f)
         self.dirty = 0
 
-    def dup(self):
+    def changes(self, files, ignore):
         self.read()
-        return self.map.copy()
+        dc = self.map.copy()
+        lookup, changed, added, unknown = [], [], [], []
+
+        # compare all files by default
+        if not files: files = [self.root]
+
+        def uniq(g):
+            seen = {}
+            for f in g:
+                if f not in seen:
+                    seen[f] = 1
+                    yield f
+
+        # recursive generator of all files listed
+        def walk(files):
+            for f in uniq(files):
+                if os.path.isdir(f):
+                    for dir, subdirs, fl in os.walk(f):
+                        d = dir[len(self.root) + 1:]
+                        if ".hg" in subdirs: subdirs.remove(".hg")
+                        for fn in fl:
+                            fn = util.pconvert(os.path.join(d, fn))
+                            yield fn
+                else:
+                    yield f[len(self.root) + 1:]
+
+        for fn in uniq(walk(files)):
+            try: s = os.stat(os.path.join(self.root, fn))
+            except: continue
+
+            if fn in dc:
+                c = dc[fn]
+                del dc[fn]
+
+                if c[0] == 'm':
+                    changed.append(fn)
+                elif c[0] == 'a':
+                    added.append(fn)
+                elif c[0] == 'r':
+                    unknown.append(fn)
+                elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
+                    changed.append(fn)
+                elif c[1] != s.st_mode or c[3] != s.st_mtime:
+                    lookup.append(fn)
+            else:
+                if not ignore(fn): unknown.append(fn)
+
+        return (lookup, changed, added, dc.keys(), unknown)
 
 # used to avoid circular references so destructors work
 def opener(base):
@@ -568,7 +615,7 @@ class localrepository:
                 else:
                     self.ui.warn("%s not tracked!\n" % f)
         else:
-            (c, a, d, u) = self.diffdir(self.root)
+            (c, a, d, u) = self.changes(None, None)
             commit = c + a
             remove = d
 
@@ -644,81 +691,60 @@ class localrepository:
         self.dirstate.update(new, "n")
         self.dirstate.forget(remove)
 
-    def diffdir(self, path, changeset = None):
-        changed = []
-        added = []
-        unknown = []
-        mf = {}
+    def changes(self, node1, node2, *files):
+        # changed, added, deleted, unknown
+        c, a, d, u, mf1 = [], [], [], [], None
 
-        if changeset:
-            change = self.changelog.read(changeset)
-            mf = self.manifest.read(change[0])
-            dc = dict.fromkeys(mf)
-        else:
-            changeset = self.dirstate.parents()[0]
-            change = self.changelog.read(changeset)
-            mf = self.manifest.read(change[0])
-            dc = self.dirstate.dup()
-
-        def fcmp(fn):
+        def fcmp(fn, mf):
             t1 = self.wfile(fn).read()
             t2 = self.file(fn).revision(mf[fn])
             return cmp(t1, t2)
 
-        for dir, subdirs, files in os.walk(path):
-            d = dir[len(self.root)+1:]
-            if ".hg" in subdirs: subdirs.remove(".hg")
+        # are we comparing the working directory?
+        if not node1:
+            l, c, a, d, u = self.dirstate.changes(files, self.ignore)
+
+            # are we comparing working dir against its parent?
+            if not node2:
+                if l:
+                    # do a full compare of any files that might have changed
+                    change = self.changelog.read(self.dirstate.parents()[0])
+                    mf1 = self.manifest.read(change[0])
+                    for f in lookup:
+                        if fcmp(f, mf):
+                            c.append(f)
+                return (c, a, d, u)
 
-            for f in files:
-                fn = util.pconvert(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:
-                        if fcmp(fn):
-                            changed.append(fn)
-                    elif c[0] == 'm':
-                        changed.append(fn)
-                    elif c[0] == 'a':
-                        added.append(fn)
-                    elif c[0] == 'r':
-                        unknown.append(fn)
-                    elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
-                        changed.append(fn)
-                    elif c[1] != s.st_mode or c[3] != s.st_mtime:
-                        if fcmp(fn):
-                            changed.append(fn)
-                else:
-                    if self.ignore(fn): continue
-                    unknown.append(fn)
+        # are we comparing working dir against non-tip?
+        # generate a pseudo-manifest for the working dir
+        if not node1:
+            if not mf1:
+                change = self.changelog.read(self.dirstate.parents()[0])
+                mf1 = self.manifest.read(change[0])
+            for f in a + c + l:
+                mf1[f] = ""
+            for f in d:
+                if f in mf1: del mf1[f]
+        else:
+            change = self.changelog.read(node1)
+            mf1 = self.manifest.read(change[0])
 
-        deleted = dc.keys()
-        deleted.sort()
-
-        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])
 
         for fn in mf2:
             if mf1.has_key(fn):
                 if mf1[fn] != mf2[fn]:
-                    changed.append(fn)
+                    if mf1[fn] != "" or fcmp(fn, mf2):
+                        c.append(fn)
                 del mf1[fn]
             else:
-                added.append(fn)
+                a.append(fn)
 
-        deleted = mf1.keys()
-        deleted.sort()
+        d = mf1.keys()
+        d.sort()
 
-        return (changed, added, deleted)
+        return (c, a, d, u)
 
     def add(self, list):
         for f in list:
@@ -1044,7 +1070,7 @@ class localrepository:
         ma = self.manifest.read(man)
         mfa = self.manifest.readflags(man)
 
-        (c, a, d, u) = self.diffdir(self.root)
+        (c, a, d, u) = self.changes(None, None)
 
         # is this a jump, or a merge?  i.e. is there a linear path
         # from p1 to p2?
--- a/mercurial/hgweb.py
+++ b/mercurial/hgweb.py
@@ -194,7 +194,7 @@ class hgweb:
         date1 = self.date(change1)
         date2 = self.date(change2)
 
-        c, a, d = r.diffrevs(node1, node2)
+        c, a, d = r.changes(node1, node2)
         c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
 
         for f in c:
--- a/tests/test-bad-pull.out
+++ b/tests/test-bad-pull.out
@@ -7,6 +7,7 @@ rollback completed
 + echo 255
 255
 + ls copy
+ls: copy: No such file or directory
 + cat
 + python dumb.py
 + hg clone http://localhost:20059/foo copy2
new file mode 100755
--- /dev/null
+++ b/tests/test-diffdir
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+hg init
+touch a
+hg add a
+hg ci -t "a" -u test -d "0 0"
+
+echo 123 > b
+hg add b
+hg diff | sed "s/\(\(---\|+++\).*\)\t.*/\1/"
+
+hg diff -r tip | sed "s/\(\(---\|+++\).*\)\t.*/\1/"
new file mode 100644
--- /dev/null
+++ b/tests/test-diffdir.out
@@ -0,0 +1,10 @@
+diff -r 3903775176ed b
+--- /dev/null
++++ b/b
+@@ -0,0 +1,1 @@
++123
+diff -r 3903775176ed b
+--- /dev/null
++++ b/b
+@@ -0,0 +1,1 @@
++123