--- a/mercurial/commands.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/commands.py Sun Nov 13 02:08:39 2005 +0100
@@ -2429,14 +2429,7 @@
cmd, args = args[0], args[1:]
defaults = ui.config("defaults", cmd)
if defaults:
- # reparse with command defaults added
- args = [cmd] + defaults.split() + args
- try:
- args = fancyopts.fancyopts(args, globalopts, options)
- except fancyopts.getopt.GetoptError, inst:
- raise ParseError(None, inst)
-
- cmd, args = args[0], args[1:]
+ args = defaults.split() + args
aliases, i = find(cmd)
cmd = aliases[0]
--- a/mercurial/dirstate.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/dirstate.py Sun Nov 13 02:08:39 2005 +0100
@@ -101,16 +101,15 @@
try:
return self.map[key]
except TypeError:
- self.read()
+ self.lazyread()
return self[key]
def __contains__(self, key):
- if not self.map: self.read()
+ self.lazyread()
return key in self.map
def parents(self):
- if not self.pl:
- self.read()
+ self.lazyread()
return self.pl
def markdirty(self):
@@ -118,8 +117,7 @@
self.dirty = 1
def setparents(self, p1, p2=nullid):
- if not self.pl:
- self.read()
+ self.lazyread()
self.markdirty()
self.pl = p1, p2
@@ -129,9 +127,11 @@
except KeyError:
return "?"
+ def lazyread(self):
+ if self.map is None:
+ self.read()
+
def read(self):
- if self.map is not None: return self.map
-
self.map = {}
self.pl = [nullid, nullid]
try:
@@ -154,7 +154,7 @@
pos += l
def copy(self, source, dest):
- self.read()
+ self.lazyread()
self.markdirty()
self.copies[dest] = source
@@ -169,7 +169,7 @@
a marked for addition'''
if not files: return
- self.read()
+ self.lazyread()
self.markdirty()
for f in files:
if state == "r":
@@ -184,7 +184,7 @@
def forget(self, files):
if not files: return
- self.read()
+ self.lazyread()
self.markdirty()
for f in files:
try:
@@ -198,7 +198,7 @@
self.markdirty()
def write(self):
- st = self.opener("dirstate", "w")
+ st = self.opener("dirstate", "w", atomic=True)
st.write("".join(self.pl))
for f, e in self.map.items():
c = self.copied(f)
@@ -241,7 +241,7 @@
bs += 1
return ret
- def supported_type(self, f, st, verbose=True):
+ def supported_type(self, f, st, verbose=False):
if stat.S_ISREG(st.st_mode):
return True
if verbose:
@@ -258,7 +258,7 @@
return False
def statwalk(self, files=None, match=util.always, dc=None):
- self.read()
+ self.lazyread()
# walk all files by default
if not files:
@@ -352,7 +352,7 @@
continue
self.blockignore = True
if statmatch(ff, st):
- if self.supported_type(ff, st):
+ if self.supported_type(ff, st, verbose=True):
yield 'f', ff, st
elif ff in dc:
yield 'm', ff, st
--- a/mercurial/localrepo.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/localrepo.py Sun Nov 13 02:08:39 2005 +0100
@@ -232,13 +232,13 @@
return False
def undo(self):
+ wlock = self.wlock()
lock = self.lock()
if os.path.exists(self.join("undo")):
self.ui.status(_("rolling back last transaction\n"))
transaction.rollback(self.opener, self.join("undo"))
- self.dirstate = None
util.rename(self.join("undo.dirstate"), self.join("dirstate"))
- self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
+ self.dirstate.read()
else:
self.ui.warn(_("no undo information available\n"))
@@ -251,6 +251,17 @@
return lock.lock(self.join("lock"), wait)
raise inst
+ def wlock(self, wait=1):
+ try:
+ wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
+ except lock.LockHeld, inst:
+ if not wait:
+ raise inst
+ self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
+ wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
+ self.dirstate.read()
+ return wlock
+
def rawcommit(self, files, text, user, date, p1=None, p2=None):
orig_parent = self.dirstate.parents()[0] or nullid
p1 = p1 or self.dirstate.parents()[0] or nullid
@@ -267,6 +278,8 @@
else:
update_dirstate = 0
+ wlock = self.wlock()
+ lock = self.lock()
tr = self.transaction()
mm = m1.copy()
mfm = mf1.copy()
@@ -355,6 +368,7 @@
if not self.hook("precommit"):
return None
+ wlock = self.wlock()
lock = self.lock()
tr = self.transaction()
@@ -472,6 +486,10 @@
# are we comparing the working directory?
if not node2:
+ try:
+ wlock = self.wlock(wait=0)
+ except lock.LockHeld:
+ wlock = None
l, c, a, d, u = self.dirstate.changes(files, match)
# are we comparing working dir against its parent?
@@ -483,6 +501,8 @@
for f in l:
if fcmp(f, mf2):
c.append(f)
+ elif wlock is not None:
+ self.dirstate.update([f], "n")
for l in c, a, d, u:
l.sort()
@@ -526,6 +546,7 @@
return (c, a, d, u)
def add(self, list):
+ wlock = self.wlock()
for f in list:
p = self.wjoin(f)
if not os.path.exists(p):
@@ -538,6 +559,7 @@
self.dirstate.update([f], "a")
def forget(self, list):
+ wlock = self.wlock()
for f in list:
if self.dirstate.state(f) not in 'ai':
self.ui.warn(_("%s not added!\n") % f)
@@ -551,6 +573,7 @@
util.unlink(self.wjoin(f))
except OSError, inst:
if inst.errno != errno.ENOENT: raise
+ wlock = self.wlock()
for f in list:
p = self.wjoin(f)
if os.path.exists(p):
@@ -568,6 +591,7 @@
mn = self.changelog.read(p)[0]
mf = self.manifest.readflags(mn)
m = self.manifest.read(mn)
+ wlock = self.wlock()
for f in list:
if self.dirstate.state(f) not in "r":
self.ui.warn("%s not removed!\n" % f)
@@ -584,6 +608,7 @@
elif not os.path.isfile(p):
self.ui.warn(_("copy failed: %s is not a file\n") % dest)
else:
+ wlock = self.wlock()
if self.dirstate.state(dest) == '?':
self.dirstate.update([dest], "a")
self.dirstate.copy(source, dest)
@@ -1374,6 +1399,9 @@
mw[f] = ""
mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
+ if moddirstate:
+ wlock = self.wlock()
+
for f in d:
if f in mw: del mw[f]
--- a/mercurial/lock.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/lock.py Sun Nov 13 02:08:39 2005 +0100
@@ -12,10 +12,11 @@
pass
class lock:
- def __init__(self, file, wait=1):
+ def __init__(self, file, wait=1, releasefn=None):
self.f = file
self.held = 0
self.wait = wait
+ self.releasefn = releasefn
self.lock()
def __del__(self):
@@ -43,6 +44,8 @@
def release(self):
if self.held:
self.held = 0
+ if self.releasefn:
+ self.releasefn()
try:
os.unlink(self.f)
except: pass
--- a/mercurial/manifest.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/manifest.py Sun Nov 13 02:08:39 2005 +0100
@@ -9,13 +9,12 @@
from revlog import *
from i18n import gettext as _
from demandload import *
-demandload(globals(), "bisect")
+demandload(globals(), "bisect array")
class manifest(revlog):
def __init__(self, opener):
self.mapcache = None
self.listcache = None
- self.addlist = None
revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
def read(self, node):
@@ -25,8 +24,9 @@
text = self.revision(node)
map = {}
flag = {}
- self.listcache = (text, text.splitlines(1))
- for l in self.listcache[1]:
+ self.listcache = array.array('c', text)
+ lines = text.splitlines(1)
+ for l in lines:
(f, n) = l.split('\0')
map[f] = bin(n[:40])
flag[f] = (n[40:-1] == "x")
@@ -39,57 +39,67 @@
self.read(node)
return self.mapcache[2]
+ def diff(self, a, b):
+ return mdiff.textdiff(str(a), str(b))
+
def add(self, map, flags, transaction, link, p1=None, p2=None,
changed=None):
- # directly generate the mdiff delta from the data collected during
- # the bisect loop below
- def gendelta(delta):
- i = 0
- result = []
- while i < len(delta):
- start = delta[i][2]
- end = delta[i][3]
- l = delta[i][4]
- if l == None:
- l = ""
- while i < len(delta) - 1 and start <= delta[i+1][2] \
- and end >= delta[i+1][2]:
- if delta[i+1][3] > end:
- end = delta[i+1][3]
- if delta[i+1][4]:
- l += delta[i+1][4]
+
+ # returns a tuple (start, end). If the string is found
+ # m[start:end] are the line containing that string. If start == end
+ # the string was not found and they indicate the proper sorted
+ # insertion point. This was taken from bisect_left, and modified
+ # to find line start/end as it goes along.
+ #
+ # m should be a buffer or a string
+ # s is a string
+ #
+ def manifestsearch(m, s, lo=0, hi=None):
+ def advance(i, c):
+ while i < lenm and m[i] != c:
i += 1
- result.append(struct.pack(">lll", start, end, len(l)) + l)
- i += 1
- return result
+ return i
+ lenm = len(m)
+ if not hi:
+ hi = lenm
+ while lo < hi:
+ mid = (lo + hi) // 2
+ start = mid
+ while start > 0 and m[start-1] != '\n':
+ start -= 1
+ end = advance(start, '\0')
+ if m[start:end] < s:
+ # we know that after the null there are 40 bytes of sha1
+ # this translates to the bisect lo = mid + 1
+ lo = advance(end + 40, '\n') + 1
+ else:
+ # this translates to the bisect hi = mid
+ hi = start
+ end = advance(lo, '\0')
+ found = m[lo:end]
+ if cmp(s, found) == 0:
+ # we know that after the null there are 40 bytes of sha1
+ end = advance(end + 40, '\n')
+ return (lo, end+1)
+ else:
+ return (lo, lo)
# apply the changes collected during the bisect loop to our addlist
- def addlistdelta(addlist, delta):
- # apply the deltas to the addlist. start from the bottom up
+ # return a delta suitable for addrevision
+ def addlistdelta(addlist, x):
+ # start from the bottom up
# so changes to the offsets don't mess things up.
- i = len(delta)
+ i = len(x)
while i > 0:
i -= 1
- start = delta[i][0]
- end = delta[i][1]
- if delta[i][4]:
- addlist[start:end] = [delta[i][4]]
+ start = x[i][0]
+ end = x[i][1]
+ if x[i][2]:
+ addlist[start:end] = array.array('c', x[i][2])
else:
del addlist[start:end]
- return addlist
-
- # calculate the byte offset of the start of each line in the
- # manifest
- def calcoffsets(addlist):
- offsets = [0] * (len(addlist) + 1)
- offset = 0
- i = 0
- while i < len(addlist):
- offsets[i] = offset
- offset += len(addlist[i])
- i += 1
- offsets[i] = offset
- return offsets
+ return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
+ for d in x ])
# if we're using the listcache, make sure it is valid and
# parented by the same node we're diffing against
@@ -98,15 +108,13 @@
files = map.keys()
files.sort()
- self.addlist = ["%s\000%s%s\n" %
+ text = ["%s\000%s%s\n" %
(f, hex(map[f]), flags[f] and "x" or '')
for f in files]
+ self.listcache = array.array('c', "".join(text))
cachedelta = None
else:
- addlist = self.listcache[1]
-
- # find the starting offset for each line in the add list
- offsets = calcoffsets(addlist)
+ addlist = self.listcache
# combine the changed lists into one list for sorting
work = [[x, 0] for x in changed[0]]
@@ -114,45 +122,52 @@
work.sort()
delta = []
- bs = 0
+ dstart = None
+ dend = None
+ dline = [""]
+ start = 0
+ # zero copy representation of addlist as a buffer
+ addbuf = buffer(addlist)
+ # start with a readonly loop that finds the offset of
+ # each line and creates the deltas
for w in work:
f = w[0]
# bs will either be the index of the item or the insert point
- bs = bisect.bisect(addlist, f, bs)
- if bs < len(addlist):
- fn = addlist[bs][:addlist[bs].index('\0')]
- else:
- fn = None
+ start, end = manifestsearch(addbuf, f, start)
if w[1] == 0:
l = "%s\000%s%s\n" % (f, hex(map[f]),
flags[f] and "x" or '')
else:
- l = None
- start = bs
- if fn != f:
- # item not found, insert a new one
- end = bs
- if w[1] == 1:
- raise AssertionError(
+ l = ""
+ if start == end and w[1] == 1:
+ # item we want to delete was not found, error out
+ raise AssertionError(
_("failed to remove %s from manifest\n") % f)
+ if dstart != None and dstart <= start and dend >= start:
+ if dend < end:
+ dend = end
+ if l:
+ dline.append(l)
else:
- # item is found, replace/delete the existing line
- end = bs + 1
- delta.append([start, end, offsets[start], offsets[end], l])
+ if dstart != None:
+ delta.append([dstart, dend, "".join(dline)])
+ dstart = start
+ dend = end
+ dline = [l]
- self.addlist = addlistdelta(addlist, delta)
- if self.mapcache[0] == self.tip():
- cachedelta = "".join(gendelta(delta))
- else:
- cachedelta = None
+ if dstart != None:
+ delta.append([dstart, dend, "".join(dline)])
+ # apply the delta to the addlist, and get a delta for addrevision
+ cachedelta = addlistdelta(addlist, delta)
- text = "".join(self.addlist)
- if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
- raise AssertionError(_("manifest delta failure\n"))
- n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
+ # the delta is only valid if we've been processing the tip revision
+ if self.mapcache[0] != self.tip():
+ cachedelta = None
+ self.listcache = addlist
+
+ n = self.addrevision(buffer(self.listcache), transaction, link, p1, \
+ p2, cachedelta)
self.mapcache = (n, map, flags)
- self.listcache = (text, self.addlist)
- self.addlist = None
return n
--- a/mercurial/revlog.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/revlog.py Sun Nov 13 02:08:39 2005 +0100
@@ -31,15 +31,15 @@
def compress(text):
""" generate a possibly-compressed representation of text """
- if not text: return text
+ if not text: return ("", text)
if len(text) < 44:
- if text[0] == '\0': return text
- return 'u' + text
+ if text[0] == '\0': return ("", text)
+ return ('u', text)
bin = zlib.compress(text)
if len(bin) > len(text):
- if text[0] == '\0': return text
- return 'u' + text
- return bin
+ if text[0] == '\0': return ("", text)
+ return ('u', text)
+ return ("", bin)
def decompress(bin):
""" decompress the given input """
@@ -71,6 +71,9 @@
self.all = 0
self.revlog = revlog
+ def trunc(self, pos):
+ self.l = pos/self.s
+
def load(self, pos=None):
if self.all: return
if pos is not None:
@@ -104,8 +107,12 @@
return self.p.index[pos]
def __getitem__(self, pos):
return self.p.index[pos] or self.load(pos)
+ def __delitem__(self, pos):
+ del self.p.index[pos]
def append(self, e):
self.p.index.append(e)
+ def trunc(self, pos):
+ self.p.trunc(pos)
class lazymap:
"""a lazy version of the node map"""
@@ -140,6 +147,8 @@
raise KeyError("node " + hex(key))
def __setitem__(self, key, val):
self.p.map[key] = val
+ def __delitem__(self, key):
+ del self.p.map[key]
class RevlogError(Exception): pass
@@ -543,14 +552,16 @@
end = self.end(t)
if not d:
prev = self.revision(self.tip())
- d = self.diff(prev, text)
+ d = self.diff(prev, str(text))
data = compress(d)
- dist = end - start + len(data)
+ l = len(data[1]) + len(data[0])
+ dist = end - start + l
# full versions are inserted when the needed deltas
# become comparable to the uncompressed text
if not n or dist > len(text) * 2:
data = compress(text)
+ l = len(data[1]) + len(data[0])
base = n
else:
base = self.base(t)
@@ -559,14 +570,17 @@
if t >= 0:
offset = self.end(t)
- e = (offset, len(data), base, link, p1, p2, node)
+ e = (offset, l, base, link, p1, p2, node)
self.index.append(e)
self.nodemap[node] = n
entry = struct.pack(indexformat, *e)
transaction.add(self.datafile, e[0])
- self.opener(self.datafile, "a").write(data)
+ f = self.opener(self.datafile, "a")
+ if data[0]:
+ f.write(data[0])
+ f.write(data[1])
transaction.add(self.indexfile, n * len(entry))
self.opener(self.indexfile, "a").write(entry)
@@ -801,7 +815,8 @@
# current size.
if chain == prev:
- cdelta = compress(delta)
+ tempd = compress(delta)
+ cdelta = tempd[0] + tempd[1]
if chain != prev or (end - start + len(cdelta)) > measure * 2:
# flush our writes here so we can read it in revision
@@ -828,6 +843,36 @@
ifh.close()
return node
+ def strip(self, rev, minlink):
+ if self.count() == 0 or rev >= self.count():
+ return
+
+ # When stripping away a revision, we need to make sure it
+ # does not actually belong to an older changeset.
+ # The minlink parameter defines the oldest revision
+ # we're allowed to strip away.
+ while minlink > self.index[rev][3]:
+ rev += 1
+ if rev >= self.count():
+ return
+
+ # first truncate the files on disk
+ end = self.start(rev)
+ self.opener(self.datafile, "a").truncate(end)
+ end = rev * struct.calcsize(indexformat)
+ self.opener(self.indexfile, "a").truncate(end)
+
+ # then reset internal state in memory to forget those revisions
+ self.cache = None
+ for p in self.index[rev:]:
+ del self.nodemap[p[6]]
+ del self.index[rev:]
+
+ # truncating the lazyindex also truncates the lazymap.
+ if isinstance(self.index, lazyindex):
+ self.index.trunc(end)
+
+
def checksize(self):
expected = 0
if self.count():
--- a/mercurial/util.py Sun Nov 13 02:06:02 2005 +0100
+++ b/mercurial/util.py Sun Nov 13 02:08:39 2005 +0100
@@ -362,7 +362,36 @@
remote file access from higher level code.
"""
p = base
- def o(path, mode="r", text=False):
+
+ def mktempcopy(name):
+ d, fn = os.path.split(name)
+ fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
+ fp = os.fdopen(fd, "wb")
+ try:
+ fp.write(file(name, "rb").read())
+ except:
+ try: os.unlink(temp)
+ except: pass
+ raise
+ fp.close()
+ st = os.lstat(name)
+ os.chmod(temp, st.st_mode)
+ return temp
+
+ class atomicfile(file):
+ """the file will only be copied on close"""
+ def __init__(self, name, mode, atomic=False):
+ self.__name = name
+ self.temp = mktempcopy(name)
+ file.__init__(self, self.temp, mode)
+ def close(self):
+ if not self.closed:
+ rename(self.temp, self.__name)
+ file.close(self)
+ def __del__(self):
+ self.close()
+
+ def o(path, mode="r", text=False, atomic=False):
f = os.path.join(p, path)
if not text:
@@ -376,21 +405,10 @@
if not os.path.isdir(d):
os.makedirs(d)
else:
+ if atomic:
+ return atomicfile(f, mode)
if nlink > 1:
- d, fn = os.path.split(f)
- fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
- fp = os.fdopen(fd, "wb")
- try:
- fp.write(file(f, "rb").read())
- except:
- try: os.unlink(temp)
- except: pass
- raise
- fp.close()
- st = os.lstat(f)
- os.chmod(temp, st.st_mode)
- rename(temp, f)
-
+ rename(mktempcopy(f), f)
return file(f, mode)
return o
--- a/tests/test-symlinks Sun Nov 13 02:06:02 2005 +0100
+++ b/tests/test-symlinks Sun Nov 13 02:08:39 2005 +0100
@@ -39,3 +39,4 @@
mkfifo a.c
# it should show a.c, dir/a.o and dir/b.o removed
hg status
+hg status a.c
--- a/tests/test-symlinks.out Sun Nov 13 02:06:02 2005 +0100
+++ b/tests/test-symlinks.out Sun Nov 13 02:08:39 2005 +0100
@@ -1,15 +1,11 @@
-bar: unsupported file type (type is symbolic link)
adding foo
-bar: unsupported file type (type is symbolic link)
-bar: unsupported file type (type is symbolic link)
adding bomb
-bar: unsupported file type (type is symbolic link)
adding a.c
adding dir/a.o
adding dir/b.o
-a.c: unsupported file type (type is fifo)
-dir/b.o: unsupported file type (type is symbolic link)
R a.c
R dir/a.o
R dir/b.o
? .hgignore
+a.c: unsupported file type (type is fifo)
+R a.c