[Aptitude-svn-commit] r3969 - in branches/aptitude-0.3/aptitude: . src/generic tests

Daniel Burrows dburrows at costa.debian.org
Fri Aug 26 23:42:44 UTC 2005


Author: dburrows
Date: Fri Aug 26 23:42:37 2005
New Revision: 3969

Added:
   branches/aptitude-0.3/aptitude/src/generic/immset.h
   branches/aptitude-0.3/aptitude/tests/test_wtree.cc
Modified:
   branches/aptitude-0.3/aptitude/ChangeLog
   branches/aptitude-0.3/aptitude/tests/Makefile.am
Log:
Add a core framework for immutable functional balanced tree objects.

Modified: branches/aptitude-0.3/aptitude/ChangeLog
==============================================================================
--- branches/aptitude-0.3/aptitude/ChangeLog	(original)
+++ branches/aptitude-0.3/aptitude/ChangeLog	Fri Aug 26 23:42:37 2005
@@ -1,5 +1,13 @@
 2005-08-26  Daniel Burrows  <dburrows at debian.org>
 
+	* src/generic/wtree.h, tests/Makefile.am, tests/test_wtree.h:
+
+	  Write a basic framework for weight-based immutable trees.  These
+	  trees explicitly share memory between different versions of a
+	  tree (for instance, if a tree is updated by adding one element)
+	  and I hope they'll help with some of the time and space issues
+	  in the problem resolver.
+
 	* src/generic/problemresolver/problemresolver.h:
 
 	  Fix the generation of conflicts from forbiddings: if a -> b c

Added: branches/aptitude-0.3/aptitude/src/generic/immset.h
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/generic/immset.h	Fri Aug 26 23:42:37 2005
@@ -0,0 +1,498 @@
+// immset.h                                     -*-c++-*-
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   This program is free software; you can redistribute it and/or
+//   modify it under the terms of the GNU General Public License as
+//   published by the Free Software Foundation; either version 2 of
+//   the License, or (at your option) any later version.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//   General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+//
+// This file defines a class to represent immutable sets.  These sets
+// behave like std::set, except that their contents cannot be changed;
+// they have no erase() or insert() operators, and only
+// const_iterators.  This restriction allows immutable sets to be
+// implemented in a way that makes both copying and creating a new set
+// by adding an element very efficient (O(1) and O(lg n) respectively;
+// with std::set these are both O(n)).
+
+#ifndef IMMSET_H
+#define IMMSET_H
+
+#include <assert.h>
+
+namespace imm
+{
+  /** A generic node in a weighted tree a described in "Implementing
+   *  Sets Efficiently In A Functional Language".  The tree invariant
+   *  is that the tree is not "too unbalanced"; in this case, that no
+   *  subtree is more than 4 times larger than its sibling.  (note
+   *  that w must be at least 3.75; see the paper for details)
+   *
+   *  A brief note on choice of algorithm: while a rbtree is more
+   *  common and may be slightly more efficient, it is a much trickier
+   *  data structure to implement, and some operations (like set-union
+   *  and set-intersection) may be difficult to implement efficiently.
+   *
+   *  The weighted-tree data structure is reasonably efficient and
+   *  much more straightforward to implement correctly.
+   */
+  template<typename Val, const int w = 4>
+  class wtree_node
+  {
+    class impl
+    {
+      typedef unsigned int size_type;
+
+      /** Left and right children (may be \b null). */
+      wtree_node left, right;
+
+      /** The size of the subtree rooted at this node. */
+      size_type size;
+
+      /** The reference-count of this node. */
+      mutable int refcount;
+
+      /** The enclosed value. */
+      Val val;
+    public:
+      impl(const Val &_val,
+	   const wtree_node &_left, const wtree_node &_right)
+	:left(_left), right(_right), size(_left.size()+_right.size()+1),
+	 refcount(1), val(_val)
+      {
+      }
+
+      /** \return the left child. */
+      wtree_node getLeftChild() const
+      {
+	return left;
+      }
+
+      /** \return the right child. */
+      wtree_node getRightChild() const
+      {
+	return right;
+      }
+
+      size_type getSize() const
+      {
+	return size;
+      }
+
+      const Val &getVal() const
+      {
+	return val;
+      }
+
+      void incref() const
+      {
+	assert(refcount>0);
+
+	++refcount;
+      }
+
+      void decref() const
+      {
+	assert(refcount>0);
+	--refcount;
+
+	if(refcount == 0)
+	  delete this;
+      }
+    };
+
+    const impl *realNode;
+
+  public:
+    typedef unsigned int size_type;
+
+    wtree_node(const Val &val,
+	       const wtree_node &left, const wtree_node &right)
+      :realNode(new impl(val, left, right))
+    {
+    }
+
+    wtree_node(const Val &val)
+      :realNode(new impl(val, wtree_node(), wtree_node()))
+    {
+    }
+
+    /** Takes possession of the given reference (the caller should
+     *  incref() if necessary).
+     */
+    wtree_node(const impl *_realNode)
+      :realNode(_realNode)
+    {
+    }
+
+    wtree_node()
+      :realNode(NULL)
+    {
+    }
+
+    wtree_node(const wtree_node &other)
+      :realNode(other.realNode)
+    {
+      if(realNode != NULL)
+	realNode->incref();
+    }
+
+    ~wtree_node()
+    {
+      if(realNode != NULL)
+	realNode->decref();
+    }
+
+    wtree_node getLeft() const
+    {
+      return realNode->getLeftChild();
+    }
+
+    wtree_node getRight() const
+    {
+      return realNode->getRightChild();
+    }
+
+    size_type size() const
+    {
+      if(realNode == NULL)
+	return 0;
+      else
+	return realNode->getSize();
+    }
+
+    wtree_node &operator=(const wtree_node &other)
+    {
+      if(other.realNode != NULL)
+	other.realNode->incref();
+      if(realNode != NULL)
+	realNode->decref();
+
+      realNode = other.realNode;
+
+      return *this;
+    }
+
+    /** Pointer comparison. */
+    bool operator==(const wtree_node &other) const
+    {
+      return realNode == other.realNode;
+    }
+
+    /** Pointer comparison. */
+    bool operator!=(const wtree_node &other) const
+    {
+      return realNode != other.realNode;
+    }
+
+    bool empty() const
+    {
+      return realNode == NULL;
+    }
+
+    bool isValid() const
+    {
+      return realNode != NULL;
+    }
+
+    /** \return the value of this node. */
+    const Val &getVal() const
+    {
+      return realNode->getVal();
+    }
+
+    // Tree management routines:
+
+    /** Perform a 'left rotate' operation on this node.  Requires
+     *  that the right child is not \b null.
+     */
+    wtree_node left_rotate_single() const
+    {
+      wtree_node right = getRight(), left = getLeft();
+      wtree_node right_left = right.getLeft(), right_right = right.getRight();
+
+      return wtree_node(right.getVal(),
+			wtree_node(getVal(), left, right_left),
+			right_right);
+    }
+
+    /** Perform a 'right rotate' operation on this node.  Requires
+     *  that the left child is not \b null.
+     */
+    wtree_node right_rotate_single() const
+    {
+      wtree_node right = getRight(), left = getLeft();
+      wtree_node left_left = left.getLeft(), left_right = left.getRight();
+
+      return wtree_node(left.getVal(),
+			left_left,
+			wtree_node(getVal(), left_right, right));
+    }
+
+    /** Perform a 'double left rotate' operation on this node.
+     *  Requires that the right child not be \b null and that
+     *  its left child is also not \b null.
+     */
+    wtree_node left_rotate_double() const
+    {
+      wtree_node right = getRight(), left = getLeft();
+      wtree_node right_right = right.getRight(), right_left = left.getLeft();
+      wtree_node right_left_left = right_left.getLeft();
+      wtree_node right_left_right = right_left.getRight();
+
+      return wtree_node(right_left.getVal(),
+			wtree_node(getVal(), left, right_left_left),
+			wtree_node(right.getVal(), right_left_right,
+				   right_right));
+    }
+
+    /** Perform a 'double right rotate' operation on this node.
+     *  Requires that the left child not be \b null and that
+     *  its right child is also not \b null.
+     */
+    wtree_node right_rotate_double() const
+    {
+      wtree_node right = getRight(), left = getLeft();
+      wtree_node left_right = right.getRight(), left_left = left.getLeft();
+      wtree_node left_right_left = left_right.getLeft();
+      wtree_node left_right_right = left_right.getRight();
+
+      return wtree_node(left_right.getVal(),
+			wtree_node(left.getVal(), left_left, left_right_left),
+			wtree_node(getVal(), left_right_right, right));
+    }
+
+
+
+
+    /** Rebalance the given subtree, returning a new subtree
+     *  reference.  The subtree should be unbalanced by at most one
+     *  element (think inserting or deleting a single element).
+     *  Equivalent to T' in the paper.
+     */ 
+    wtree_node rebalance() const
+    {
+      wtree_node left = getLeft(), right = getRight();
+      size_type left_size = left.size();
+      size_type right_size = right.size();
+
+      // If one subtree is empty and the other contains at most one
+      // element, there is nothing to do.
+      if(left_size + right_size < 2)
+	return *this;
+      else if(left_size * w < right_size)
+	{
+	  // The right tree is too heavy.  As explained in the paper,
+	  // a single rotation is guaranteed sufficient if its outer
+	  // (right) child is larger than its inner child; otherwise a
+	  // double rotation is guaranteed sufficient.
+	  wtree_node right_left = right.getLeft(), right_right = right.getRight();
+	  if(right_left.size() < right_right.size())
+	    return left_rotate_single();
+	  else
+	    return left_rotate_double();
+	}
+      else if(right_size * w < left_size)
+	{
+	  // Dual of above.
+	  wtree_node left_left = left.getLeft(), left_right = left.getRight();
+	  if(left_right.size() < left_left.size())
+	    return right_rotate_single();
+	  else
+	    return right_rotate_double();
+	}
+      else
+	// Nothing to do; the tree is already balanced.
+	return *this;
+    }
+  };
+
+  /** An entire weighted tree.
+   */
+  template<typename Val, typename Compare = std::less<Val>, int w = 4 >
+  class wtree
+  {
+  public:
+    typedef Val value_type;
+    typedef wtree_node<Val, w> node;
+    typedef typename node::size_type size_type;
+
+    Compare value_compare;
+
+    /** An iterator over a wtree.  Note that the lack of parent
+     *  pointers (necessary to allow full memory sharing) forces the
+     *  iterator class to allocate!  I don't recommend using iterators
+     *  except for the purpose of spitting the tree out for debugging.
+     */
+    class const_iterator
+    {
+      typedef std::pair<bool, node > path_entry;
+
+      std::vector<path_entry> path;
+    public:
+      const_iterator()
+      {
+      }
+
+      const_iterator(const node &root)
+      {
+	if(root.isValid())
+	  {
+	    path.push_back(path_entry(false, root));
+	    while(path.back().second.getLeft().isValid())
+	      path.push_back(path_entry(false, path.back().second.getLeft()));
+	  }
+      }
+
+      const Val &operator*() const
+      {
+	return path.back().second.getVal();
+      }
+
+      const Val *operator->() const
+      {
+	return &path.back().second.getVal();
+      }
+
+      const_iterator &operator=(const const_iterator &other)
+      {
+	path = other.path;
+	return *this;
+      }
+
+      bool operator==(const const_iterator &other) const
+      {
+	return path == other.path;
+      }
+
+      bool operator!=(const const_iterator &other) const
+      {
+	return path != other.path;
+      }
+
+      const_iterator &operator++()
+      {
+	assert(!path.empty());
+
+	if(!path.back().first)
+	  {
+	    path.back().first = true;
+	    path.push_back(path_entry(false, path.back().second.getRight()));
+
+	    while(!path.empty() && path.back().second.isValid())
+	      path.push_back(path_entry(false, path.back().second.getLeft()));
+	  }
+
+	// Clear out any invalid nodes or nodes that already fired.
+	while(!path.empty() && (!path.back().second.isValid() || path.back().first))
+	  path.pop_back();
+
+	// Now either the path is empty, or we're at a node that's
+	// valid and hasn't fired yet (meaning we just finished
+	// descending into its left subtree, so we should stop and
+	// visit it).
+
+	return *this;
+      }
+    };
+  private:
+    /** Root of the tree. */
+    node root;
+
+    /** An 'insert' based on tree nodes.  Returns a rebalanced tree
+     *  containing the given information; doesn't update existing
+     *  nodes with equivalent keys.
+     */
+    node insert(const node &n, const Val &x) const
+    {
+      if(n.empty()) return node(x, node(), node());
+      else if(value_compare(x, n.getVal()))
+	return node(n.getVal(),
+		    insert(n.getLeft(), x),
+		    n.getRight()).rebalance();
+      else if(value_compare(n.getVal(), x))
+	return node(n.getVal(),
+		    n.getLeft(),
+		    insert(n.getRight(), x)).rebalance();
+      else
+	return n;
+    }
+
+    /** An 'insert' based on tree nodes.  Returns a rebalanced tree
+     *  containing the given information, updating existing nodes with
+     *  equivalent keys.
+     */
+    node insertUpdate(const node &n, const Val &x) const
+    {
+      if(n.empty()) return node(x, node(), node());
+      else if(value_compare(x, n.getVal()))
+	return node(n.getVal(),
+		    insert(n.getLeft(), x),
+		    n.getRight()).rebalance();
+      else if(value_compare(n.getVal(), x))
+	return node(n.getVal(),
+		    n.getLeft(),
+		    insert(n.getRight(), x)).rebalance();
+      else
+	return node(x, n.getLeft(), n.getRight());
+    }
+
+    wtree(const node &n)
+      :root(n)
+    {
+    }
+  public:
+    /** Construct an empty tree. */
+    wtree()
+    {
+    }
+
+    /** Insert an element into a tree, returning a new tree.  This is
+     *  not a member function, to stress that it does NOT modify the
+     *  old tree; instead, it returns a new tree containing the
+     *  element in addition to the elements of the old tree.
+     */
+    static wtree insert(const wtree &old, const Val &x)
+    {
+      return old.insert(old.root, x);
+    }
+
+    /** Like insert, but updates existing equivalent elements. */
+    static wtree insertUpdate(const wtree &old, const Val &x)
+    {
+      return old.insertUpdate(old.root, x);
+    }
+
+    const_iterator begin() const
+    {
+      return const_iterator(root);
+    }
+
+    const_iterator end() const
+    {
+      return const_iterator();
+    }
+
+    size_type size() const
+    {
+      return root.size();
+    }
+
+    int empty() const
+    {
+      return root.empty();
+    }
+  };
+};
+
+#endif

Modified: branches/aptitude-0.3/aptitude/tests/Makefile.am
==============================================================================
--- branches/aptitude-0.3/aptitude/tests/Makefile.am	(original)
+++ branches/aptitude-0.3/aptitude/tests/Makefile.am	Fri Aug 26 23:42:37 2005
@@ -9,9 +9,12 @@
 
 EXTRADIST = data
 
+# Note: test_apt_universe is not built by default because it takes way
+# too long.  Of course, ideally this would be done in a less ad-hoc
+# way...
 test_SOURCES = \
 	main.cc \
-	test_apt_universe.cc \
 	test_misc.cc \
 	test_resolver.cc \
-	test_tags.cc
\ No newline at end of file
+	test_tags.cc \
+	test_wtree.cc \

Added: branches/aptitude-0.3/aptitude/tests/test_wtree.cc
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/tests/test_wtree.cc	Fri Aug 26 23:42:37 2005
@@ -0,0 +1,264 @@
+// test_wtree.cc
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   This program is free software; you can redistribute it and/or
+//   modify it under the terms of the GNU General Public License as
+//   published by the Free Software Foundation; either version 2 of
+//   the License, or (at your option) any later version.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//   General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <src/generic/immset.h>
+
+using imm::wtree;
+using imm::wtree_node;
+
+class WTreeTest : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(WTreeTest);
+
+  CPPUNIT_TEST(testNodeRotate);
+  CPPUNIT_TEST(testInsertIterate);
+
+  CPPUNIT_TEST_SUITE_END();
+public:
+  typedef wtree_node<int> int_node;
+
+  // Simple test of the rotate facilities.
+  void testNodeRotate()
+  {
+    int_node a(5,
+	       int_node(3, int_node(2), int_node(4)),
+	       int_node(7, int_node(6), int_node(8)));
+
+    int_node a_left = a.getLeft();
+    int_node a_right = a.getRight();
+
+    CPPUNIT_ASSERT(a_left.isValid());
+    CPPUNIT_ASSERT(a_right.isValid());
+
+    int_node a_left_left = a_left.getLeft();
+    int_node a_left_right = a_left.getRight();
+    int_node a_right_left = a_right.getLeft();
+    int_node a_right_right = a_right.getRight();
+
+    CPPUNIT_ASSERT(a_left_left.isValid());
+    CPPUNIT_ASSERT(a_left_right.isValid());
+    CPPUNIT_ASSERT(a_right_left.isValid());
+    CPPUNIT_ASSERT(a_right_right.isValid());
+
+    CPPUNIT_ASSERT(a_left_left.getVal() == 2);
+    CPPUNIT_ASSERT(a_left.getVal() == 3);
+    CPPUNIT_ASSERT(a_left_right.getVal() == 4);
+    CPPUNIT_ASSERT(a.getVal() == 5);
+    CPPUNIT_ASSERT(a_right_left.getVal() == 6);
+    CPPUNIT_ASSERT(a_right.getVal() == 7);
+    CPPUNIT_ASSERT(a_right_right.getVal() == 8);
+
+
+
+    int_node b = a.left_rotate_single();
+
+    CPPUNIT_ASSERT(b.size() == a.size());
+
+    CPPUNIT_ASSERT(b.isValid());
+
+    int_node b_left = b.getLeft();
+    int_node b_right = b.getRight();
+
+    CPPUNIT_ASSERT(b_left.isValid());
+    CPPUNIT_ASSERT(b_right.isValid());
+
+    int_node b_left_left = b_left.getLeft();
+    int_node b_left_right = b_left.getRight();
+
+    CPPUNIT_ASSERT(b_left_left.isValid());
+    CPPUNIT_ASSERT(b_left_right.isValid());
+
+    int_node b_left_left_left = b_left_left.getLeft();
+    int_node b_left_left_right = b_left_left.getRight();
+
+    CPPUNIT_ASSERT(b_left_left_left.isValid());
+    CPPUNIT_ASSERT(b_left_left_right.isValid());
+
+
+    CPPUNIT_ASSERT(b_left_left_left.getVal() == 2);
+    CPPUNIT_ASSERT(b_left_left.getVal() == 3);
+    CPPUNIT_ASSERT(b_left_left_right.getVal() == 4);
+    CPPUNIT_ASSERT(b_left.getVal() == 5);
+    CPPUNIT_ASSERT(b_left_right.getVal() == 6);
+    CPPUNIT_ASSERT(b.getVal() == 7);
+    CPPUNIT_ASSERT(b_right.getVal() == 8);
+  }
+
+  void testInsertIterate()
+  {
+    wtree<int> t;
+
+    CPPUNIT_ASSERT(t.begin() == t.end());
+    CPPUNIT_ASSERT(t.empty());
+    CPPUNIT_ASSERT(t.size() == 0);
+
+
+
+    t = wtree<int>::insert(t, 5);
+
+    CPPUNIT_ASSERT(!t.empty());
+    CPPUNIT_ASSERT(t.size() == 1);
+
+    wtree<int>::const_iterator i = t.begin();
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 5);
+
+    ++i;
+    CPPUNIT_ASSERT(i == t.end());
+
+
+
+
+    t = wtree<int>::insert(t, 3);
+    CPPUNIT_ASSERT(!t.empty());
+    CPPUNIT_ASSERT(t.size() == 2);
+
+    i = t.begin();
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 3);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 5);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i == t.end());
+
+
+
+    t = wtree<int>::insert(t, 8);
+    CPPUNIT_ASSERT(!t.empty());
+    CPPUNIT_ASSERT(t.size() == 3);
+
+    i = t.begin();
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 3);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 5);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 8);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i == t.end());
+
+
+
+    t = wtree<int>::insert(t, 7);
+    CPPUNIT_ASSERT(!t.empty());
+    CPPUNIT_ASSERT(t.size() == 4);
+
+    i = t.begin();
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 3);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 5);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 7);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 8);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i == t.end());
+
+
+    t = wtree<int>::insert(t, 3);
+    CPPUNIT_ASSERT(!t.empty());
+    CPPUNIT_ASSERT(t.size() == 4);
+
+    i = t.begin();
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 3);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 5);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 7);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 8);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i == t.end());
+
+
+
+    t = wtree<int>::insertUpdate(t, 3);
+    CPPUNIT_ASSERT(!t.empty());
+    CPPUNIT_ASSERT(t.size() == 4);
+
+    i = t.begin();
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 3);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 5);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 7);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i != t.end());
+    CPPUNIT_ASSERT(*i == 8);
+
+    ++i;
+
+    CPPUNIT_ASSERT(i == t.end());
+  }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WTreeTest);



More information about the Aptitude-svn-commit mailing list