changeset 1542:482b4efdf013

Merge with upstream
author Thomas Arendsen Hein <thomas@intevation.de>
date Sun, 13 Nov 2005 02:08:39 +0100
parents 583b3696d24d (current diff) 7ae0ce7a3dc4 (diff)
children 5e47e42b14ba
files mercurial/commands.py
diffstat 9 files changed, 234 insertions(+), 135 deletions(-) [+]
line wrap: on
line diff
--- 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