diff mercurial/dirstate.py @ 1089:142b5d5ec9cc

Break apart hg.py - move the various parts of hg.py into their own files - create node.py to store node manipulation functions
author mpm@selenic.com
date Sat, 27 Aug 2005 14:21:25 -0700
parents mercurial/hg.py@05dc7aba22eb
children 221b5252864c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/dirstate.py	Sat Aug 27 14:21:25 2005 -0700
@@ -0,0 +1,312 @@
+"""
+dirstate.py - working directory tracking for mercurial
+
+Copyright 2005 Matt Mackall <mpm@selenic.com>
+
+This software may be used and distributed according to the terms
+of the GNU General Public License, incorporated herein by reference.
+"""
+
+import sys, struct, os
+from revlog import *
+from demandload import *
+demandload(globals(), "time bisect stat util")
+
+class dirstate:
+    def __init__(self, opener, ui, root):
+        self.opener = opener
+        self.root = root
+        self.dirty = 0
+        self.ui = ui
+        self.map = None
+        self.pl = None
+        self.copies = {}
+        self.ignorefunc = None
+
+    def wjoin(self, f):
+        return os.path.join(self.root, f)
+
+    def getcwd(self):
+        cwd = os.getcwd()
+        if cwd == self.root: return ''
+        return cwd[len(self.root) + 1:]
+
+    def ignore(self, f):
+        if not self.ignorefunc:
+            bigpat = []
+            try:
+                l = file(self.wjoin(".hgignore"))
+                for pat in l:
+                    p = pat.rstrip()
+                    if p:
+                        try:
+                            re.compile(p)
+                        except:
+                            self.ui.warn("ignoring invalid ignore"
+                                         + " regular expression '%s'\n" % p)
+                        else:
+                            bigpat.append(p)
+            except IOError: pass
+
+            if bigpat:
+                s = "(?:%s)" % (")|(?:".join(bigpat))
+                r = re.compile(s)
+                self.ignorefunc = r.search
+            else:
+                self.ignorefunc = util.never
+
+        return self.ignorefunc(f)
+
+    def __del__(self):
+        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 parents(self):
+        if not self.pl:
+            self.read()
+        return self.pl
+
+    def markdirty(self):
+        if not self.dirty:
+            self.dirty = 1
+
+    def setparents(self, p1, p2=nullid):
+        self.markdirty()
+        self.pl = p1, p2
+
+    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 = {}
+        self.pl = [nullid, nullid]
+        try:
+            st = self.opener("dirstate").read()
+            if not st: return
+        except: return
+
+        self.pl = [st[:20], st[20: 40]]
+
+        pos = 40
+        while pos < len(st):
+            e = struct.unpack(">cllll", st[pos:pos+17])
+            l = e[4]
+            pos += 17
+            f = st[pos:pos + l]
+            if '\0' in f:
+                f, c = f.split('\0')
+                self.copies[f] = c
+            self.map[f] = e[:4]
+            pos += l
+
+    def copy(self, source, dest):
+        self.read()
+        self.markdirty()
+        self.copies[dest] = source
+
+    def copied(self, file):
+        return self.copies.get(file, None)
+
+    def update(self, files, state, **kw):
+        ''' current states:
+        n  normal
+        m  needs merging
+        r  marked for removal
+        a  marked for addition'''
+
+        if not files: return
+        self.read()
+        self.markdirty()
+        for f in files:
+            if state == "r":
+                self.map[f] = ('r', 0, 0, 0)
+            else:
+                s = os.stat(os.path.join(self.root, f))
+                st_size = kw.get('st_size', s.st_size)
+                st_mtime = kw.get('st_mtime', s.st_mtime)
+                self.map[f] = (state, s.st_mode, st_size, st_mtime)
+
+    def forget(self, files):
+        if not files: return
+        self.read()
+        self.markdirty()
+        for f in files:
+            try:
+                del self.map[f]
+            except KeyError:
+                self.ui.warn("not in dirstate: %s!\n" % f)
+                pass
+
+    def clear(self):
+        self.map = {}
+        self.markdirty()
+
+    def write(self):
+        st = self.opener("dirstate", "w")
+        st.write("".join(self.pl))
+        for f, e in self.map.items():
+            c = self.copied(f)
+            if c:
+                f = f + "\0" + c
+            e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
+            st.write(e + f)
+        self.dirty = 0
+
+    def filterfiles(self, files):
+        ret = {}
+        unknown = []
+
+        for x in files:
+            if x is '.':
+                return self.map.copy()
+            if x not in self.map:
+                unknown.append(x)
+            else:
+                ret[x] = self.map[x]
+
+        if not unknown:
+            return ret
+
+        b = self.map.keys()
+        b.sort()
+        blen = len(b)
+
+        for x in unknown:
+            bs = bisect.bisect(b, x)
+            if bs != 0 and  b[bs-1] == x:
+                ret[x] = self.map[x]
+                continue
+            while bs < blen:
+                s = b[bs]
+                if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
+                    ret[s] = self.map[s]
+                else:
+                    break
+                bs += 1
+        return ret
+
+    def walk(self, files=None, match=util.always, dc=None):
+        self.read()
+
+        # walk all files by default
+        if not files:
+            files = [self.root]
+            if not dc:
+                dc = self.map.copy()
+        elif not dc:
+            dc = self.filterfiles(files)
+
+        known = {'.hg': 1}
+        def seen(fn):
+            if fn in known: return True
+            known[fn] = 1
+        def traverse():
+            for ff in util.unique(files):
+                f = os.path.join(self.root, ff)
+                try:
+                    st = os.stat(f)
+                except OSError, inst:
+                    if ff not in dc: self.ui.warn('%s: %s\n' % (
+                        util.pathto(self.getcwd(), ff),
+                        inst.strerror))
+                    continue
+                if stat.S_ISDIR(st.st_mode):
+                    for dir, subdirs, fl in os.walk(f):
+                        d = dir[len(self.root) + 1:]
+                        nd = util.normpath(d)
+                        if nd == '.': nd = ''
+                        if seen(nd):
+                            subdirs[:] = []
+                            continue
+                        for sd in subdirs:
+                            ds = os.path.join(nd, sd +'/')
+                            if self.ignore(ds) or not match(ds):
+                                subdirs.remove(sd)
+                        subdirs.sort()
+                        fl.sort()
+                        for fn in fl:
+                            fn = util.pconvert(os.path.join(d, fn))
+                            yield 'f', fn
+                elif stat.S_ISREG(st.st_mode):
+                    yield 'f', ff
+                else:
+                    kind = 'unknown'
+                    if stat.S_ISCHR(st.st_mode): kind = 'character device'
+                    elif stat.S_ISBLK(st.st_mode): kind = 'block device'
+                    elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
+                    elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
+                    elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
+                    self.ui.warn('%s: unsupported file type (type is %s)\n' % (
+                        util.pathto(self.getcwd(), ff),
+                        kind))
+
+            ks = dc.keys()
+            ks.sort()
+            for k in ks:
+                yield 'm', k
+
+        # yield only files that match: all in dirstate, others only if
+        # not in .hgignore
+
+        for src, fn in util.unique(traverse()):
+            fn = util.normpath(fn)
+            if seen(fn): continue
+            if fn not in dc and self.ignore(fn):
+                continue
+            if match(fn):
+                yield src, fn
+
+    def changes(self, files=None, match=util.always):
+        self.read()
+        if not files:
+            dc = self.map.copy()
+        else:
+            dc = self.filterfiles(files)
+        lookup, modified, added, unknown = [], [], [], []
+        removed, deleted = [], []
+
+        for src, fn in self.walk(files, match, dc=dc):
+            try:
+                s = os.stat(os.path.join(self.root, fn))
+            except OSError:
+                continue
+            if not stat.S_ISREG(s.st_mode):
+                continue
+            c = dc.get(fn)
+            if c:
+                del dc[fn]
+                if c[0] == 'm':
+                    modified.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:
+                    modified.append(fn)
+                elif c[3] != s.st_mtime:
+                    lookup.append(fn)
+            else:
+                unknown.append(fn)
+
+        for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
+            if c[0] == 'r':
+                removed.append(fn)
+            else:
+                deleted.append(fn)
+        return (lookup, modified, added, removed + deleted, unknown)