Show More
dirstate.py
312 lines
| 9.2 KiB
| text/x-python
|
PythonLexer
/ mercurial / dirstate.py
mpm@selenic.com
|
r1089 | """ | ||
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. | ||||
""" | ||||
mpm@selenic.com
|
r1094 | import struct, os | ||
from node import * | ||||
mpm@selenic.com
|
r1089 | 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) | ||||