""" dirstate.py - working directory tracking for mercurial Copyright 2005 Matt Mackall This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. """ import struct, os from node import * from demandload import * demandload(globals(), "time bisect stat util re") 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) if self.copies.has_key(f): del self.copies[f] 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)