mercurial/manifest.py
author Bryan O'Sullivan <bos@serpentine.com>
Sat, 17 Sep 2005 00:27:27 -0700
changeset 1270 fc3b41570082
parent 1098 50a0a36dd48a
child 1400 cf9a1233738a
permissions -rw-r--r--
Switch to new syntax for .hgignore files. Here is the new syntax, in summary. Trailing white space is dropped. The escape character is "\". Comments start with #. Empty lines are skipped. Lines can be of the following formats: syntax: regexp # defaults following lines to non-rooted regexps syntax: glob # defaults following lines to non-rooted globs re:pattern # non-rooted regular expression glob:pattern # non-rooted glob pattern # pattern of the current default type The default pattern type is regexp, which is completely backwards compatible with the old hgignore syntax. In the dirstate class, the ignore method has been reworked to be based on the util.matcher function, by way of a new dirstate.hgignore method.

# manifest.py - manifest revision class 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
from revlog import *
from demandload import *
demandload(globals(), "bisect")

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):
        if node == nullid: return {} # don't upset local cache
        if self.mapcache and self.mapcache[0] == node:
            return self.mapcache[1]
        text = self.revision(node)
        map = {}
        flag = {}
        self.listcache = (text, text.splitlines(1))
        for l in self.listcache[1]:
            (f, n) = l.split('\0')
            map[f] = bin(n[:40])
            flag[f] = (n[40:-1] == "x")
        self.mapcache = (node, map, flag)
        return map

    def readflags(self, node):
        if node == nullid: return {} # don't upset local cache
        if not self.mapcache or self.mapcache[0] != node:
            self.read(node)
        return self.mapcache[2]

    def diff(self, a, b):
        # this is sneaky, as we're not actually using a and b
        if self.listcache and self.addlist and self.listcache[0] == a:
            d = mdiff.diff(self.listcache[1], self.addlist, 1)
            if mdiff.patch(a, d) != b:
                raise AssertionError("sortdiff failed!")
            return d
        else:
            return mdiff.textdiff(a, 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]
                    i += 1
                result.append(struct.pack(">lll", start, end, len(l)) +  l)
                i += 1
            return result

        # 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
            # so changes to the offsets don't mess things up.
            i = len(delta)
            while i > 0:
                i -= 1
                start = delta[i][0]
                end = delta[i][1]
                if delta[i][4]:
                    addlist[start:end] = [delta[i][4]]
                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

        # if we're using the listcache, make sure it is valid and
        # parented by the same node we're diffing against
        if not changed or not self.listcache or not p1 or \
               self.mapcache[0] != p1:
            files = map.keys()
            files.sort()

            self.addlist = ["%s\000%s%s\n" %
                            (f, hex(map[f]), flags[f] and "x" or '')
                            for f in files]
            cachedelta = None
        else:
            addlist = self.listcache[1]

            # find the starting offset for each line in the add list
            offsets = calcoffsets(addlist)

            # combine the changed lists into one list for sorting
            work = [[x, 0] for x in changed[0]]
            work[len(work):] = [[x, 1] for x in changed[1]]
            work.sort()

            delta = []
            bs = 0

            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
                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(
                            "failed to remove %s from manifest\n" % f)
                else:
                    # item is found, replace/delete the existing line
                    end = bs + 1
                delta.append([start, end, offsets[start], offsets[end], l])

            self.addlist = addlistdelta(addlist, delta)
            if self.mapcache[0] == self.tip():
                cachedelta = "".join(gendelta(delta))
            else:
                cachedelta = None

        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)
        self.mapcache = (n, map, flags)
        self.listcache = (text, self.addlist)
        self.addlist = None

        return n