Return-Path: X-Original-To: apmail-subversion-commits-archive@minotaur.apache.org Delivered-To: apmail-subversion-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id BB881D722 for ; Tue, 31 Jul 2012 18:06:29 +0000 (UTC) Received: (qmail 30955 invoked by uid 500); 31 Jul 2012 18:06:29 -0000 Delivered-To: apmail-subversion-commits-archive@subversion.apache.org Received: (qmail 30929 invoked by uid 500); 31 Jul 2012 18:06:29 -0000 Mailing-List: contact commits-help@subversion.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@subversion.apache.org Delivered-To: mailing list commits@subversion.apache.org Received: (qmail 30921 invoked by uid 99); 31 Jul 2012 18:06:29 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 31 Jul 2012 18:06:29 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 31 Jul 2012 18:06:14 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 3045823888EA; Tue, 31 Jul 2012 18:05:29 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1367697 [1/5] - in /subversion/branches/inheritable-props: ./ notes/ notes/directory-index/ subversion/bindings/javahl/native/ subversion/bindings/javahl/src/org/apache/subversion/javahl/ subversion/bindings/swig/ subversion/bindings/swig/... Date: Tue, 31 Jul 2012 18:05:24 -0000 To: commits@subversion.apache.org From: pburba@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120731180529.3045823888EA@eris.apache.org> Author: pburba Date: Tue Jul 31 18:05:21 2012 New Revision: 1367697 URL: http://svn.apache.org/viewvc?rev=1367697&view=rev Log: On the inheritable-props branch: Sync with ^/subversion/trunk through r1367696. Added: subversion/branches/inheritable-props/subversion/tests/cmdline/wc_tests.py - copied unchanged from r1367696, subversion/trunk/subversion/tests/cmdline/wc_tests.py Modified: subversion/branches/inheritable-props/ (props changed) subversion/branches/inheritable-props/CHANGES subversion/branches/inheritable-props/COMMITTERS subversion/branches/inheritable-props/INSTALL subversion/branches/inheritable-props/configure.ac subversion/branches/inheritable-props/get-deps.sh subversion/branches/inheritable-props/notes/authz_policy.txt subversion/branches/inheritable-props/notes/directory-index/dirindex.py subversion/branches/inheritable-props/notes/directory-index/schema.sql subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.cpp subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.h subversion/branches/inheritable-props/subversion/bindings/javahl/native/JNIUtil.cpp subversion/branches/inheritable-props/subversion/bindings/javahl/native/SVNClient.cpp subversion/branches/inheritable-props/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java subversion/branches/inheritable-props/subversion/bindings/swig/core.i subversion/branches/inheritable-props/subversion/bindings/swig/include/svn_containers.swg subversion/branches/inheritable-props/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c subversion/branches/inheritable-props/subversion/include/private/svn_mergeinfo_private.h subversion/branches/inheritable-props/subversion/include/private/svn_skel.h subversion/branches/inheritable-props/subversion/include/private/svn_wc_private.h subversion/branches/inheritable-props/subversion/include/svn_client.h subversion/branches/inheritable-props/subversion/include/svn_fs.h subversion/branches/inheritable-props/subversion/include/svn_mergeinfo.h subversion/branches/inheritable-props/subversion/include/svn_ra.h subversion/branches/inheritable-props/subversion/include/svn_wc.h subversion/branches/inheritable-props/subversion/libsvn_client/client.h subversion/branches/inheritable-props/subversion/libsvn_client/diff.c subversion/branches/inheritable-props/subversion/libsvn_client/externals.c subversion/branches/inheritable-props/subversion/libsvn_client/merge.c subversion/branches/inheritable-props/subversion/libsvn_client/mergeinfo.c subversion/branches/inheritable-props/subversion/libsvn_client/mergeinfo.h subversion/branches/inheritable-props/subversion/libsvn_client/ra.c subversion/branches/inheritable-props/subversion/libsvn_client/switch.c subversion/branches/inheritable-props/subversion/libsvn_fs/fs-loader.c subversion/branches/inheritable-props/subversion/libsvn_fs_base/lock.c subversion/branches/inheritable-props/subversion/libsvn_fs_fs/fs_fs.c subversion/branches/inheritable-props/subversion/libsvn_fs_fs/rep-cache.c subversion/branches/inheritable-props/subversion/libsvn_fs_fs/tree.c subversion/branches/inheritable-props/subversion/libsvn_fs_util/fs-util.c subversion/branches/inheritable-props/subversion/libsvn_ra/ra_loader.h subversion/branches/inheritable-props/subversion/libsvn_ra_serf/serf.c subversion/branches/inheritable-props/subversion/libsvn_repos/load-fs-vtable.c subversion/branches/inheritable-props/subversion/libsvn_repos/log.c subversion/branches/inheritable-props/subversion/libsvn_repos/rev_hunt.c subversion/branches/inheritable-props/subversion/libsvn_subr/deprecated.c subversion/branches/inheritable-props/subversion/libsvn_subr/mergeinfo.c subversion/branches/inheritable-props/subversion/libsvn_subr/simple_providers.c subversion/branches/inheritable-props/subversion/libsvn_subr/skel.c subversion/branches/inheritable-props/subversion/libsvn_wc/adm_files.c subversion/branches/inheritable-props/subversion/libsvn_wc/adm_ops.c subversion/branches/inheritable-props/subversion/libsvn_wc/cleanup.c subversion/branches/inheritable-props/subversion/libsvn_wc/conflicts.c subversion/branches/inheritable-props/subversion/libsvn_wc/crop.c subversion/branches/inheritable-props/subversion/libsvn_wc/externals.c subversion/branches/inheritable-props/subversion/libsvn_wc/lock.c subversion/branches/inheritable-props/subversion/libsvn_wc/merge.c subversion/branches/inheritable-props/subversion/libsvn_wc/props.c subversion/branches/inheritable-props/subversion/libsvn_wc/status.c subversion/branches/inheritable-props/subversion/libsvn_wc/update_editor.c subversion/branches/inheritable-props/subversion/libsvn_wc/wc-queries.sql subversion/branches/inheritable-props/subversion/libsvn_wc/wc.h subversion/branches/inheritable-props/subversion/libsvn_wc/wc_db.c subversion/branches/inheritable-props/subversion/libsvn_wc/wc_db.h subversion/branches/inheritable-props/subversion/libsvn_wc/wc_db_pristine.c subversion/branches/inheritable-props/subversion/libsvn_wc/workqueue.c subversion/branches/inheritable-props/subversion/libsvn_wc/workqueue.h subversion/branches/inheritable-props/subversion/po/de.po subversion/branches/inheritable-props/subversion/svn/notify.c subversion/branches/inheritable-props/subversion/svndumpfilter/main.c subversion/branches/inheritable-props/subversion/svnlook/main.c subversion/branches/inheritable-props/subversion/svnrdump/load_editor.c subversion/branches/inheritable-props/subversion/tests/cmdline/basic_tests.py subversion/branches/inheritable-props/subversion/tests/cmdline/depth_tests.py subversion/branches/inheritable-props/subversion/tests/cmdline/merge_reintegrate_tests.py subversion/branches/inheritable-props/subversion/tests/cmdline/merge_tests.py subversion/branches/inheritable-props/subversion/tests/cmdline/update_tests.py subversion/branches/inheritable-props/subversion/tests/libsvn_client/client-test.c subversion/branches/inheritable-props/subversion/tests/libsvn_subr/dirent_uri-test.c subversion/branches/inheritable-props/subversion/tests/libsvn_subr/mergeinfo-test.c subversion/branches/inheritable-props/subversion/tests/libsvn_subr/skel-test.c subversion/branches/inheritable-props/subversion/tests/libsvn_wc/op-depth-test.c subversion/branches/inheritable-props/tools/dev/gdb-py/svndbg/printers.py subversion/branches/inheritable-props/tools/dev/unix-build/Makefile.svn subversion/branches/inheritable-props/win-tests.py Propchange: subversion/branches/inheritable-props/ ------------------------------------------------------------------------------ Merged /subversion/trunk:r1360861-1367696 Modified: subversion/branches/inheritable-props/CHANGES URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/CHANGES?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/CHANGES (original) +++ subversion/branches/inheritable-props/CHANGES Tue Jul 31 18:05:21 2012 @@ -48,6 +48,41 @@ http://svn.apache.org/repos/asf/subversi * +Version 1.7.6 +(XX YYY 2012, from /branches/1.7.x) +http://svn.apache.org/repos/asf/subversion/tags/1.7.6 + + User-visible changes: + - Client- and server-side bugfixes: + + - Client-side bugfixes: + * Fix "svn status -u --depth empty FILE" (r1348822, r1349215) + * Fix example output of 'svn help status' (issue #3962) + * svn propset of svn:eol-style might not notice related text changes (r1353572) + * sort output of 'svn propget -R' (r1355699) + * sort output of 'svn proplist' (r1355698) + * sort output of 'svn status' (r1341012) + * avoid a filestat per working copy find operation (r1340556) + * optimize 'svn upgrade' performance on large working copies (r1342984) + * allow 'file:///C:\repos' style arguments on Windows, like 1.6 (r1346765) + * fix ra_serf against Subversion 1.2 servers (r1349367) + * fix 'svn upgrade' on working copies with certain tree conflicts (r1345482) + + - Server-side bugfixes: + * partial sync drops properties when converting to adds (issue #4184) + * replaying a copy and delete of an unreadable child fails (issue #4121) + + - Other tool improvements and bugfixes: + + Developer-visible changes: + - General: + * fix running tests against httpd 2.4 (r1291594) + + - Bindings: + * JavaHL: Don't assert on some invalid input (r1354626, r1354652) + * JavaHL: Add missing new in 1.7 notifications (r1351772) + + Version 1.7.5 (17 May 2012, from /branches/1.7.x) http://svn.apache.org/repos/asf/subversion/tags/1.7.5 Modified: subversion/branches/inheritable-props/COMMITTERS URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/COMMITTERS?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/COMMITTERS [UTF-8] (original) +++ subversion/branches/inheritable-props/COMMITTERS [UTF-8] Tue Jul 31 18:05:21 2012 @@ -39,7 +39,7 @@ Blanket commit access: pburba Paul Burba glasser David Glasser lgo Lieven Govaerts - hwright Hyrum Wright + hwright Hyrum Wright vgeorgescu Vlad Georgescu kameshj Kamesh Jayachandran markphip Mark Phippard Modified: subversion/branches/inheritable-props/INSTALL URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/INSTALL?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/INSTALL (original) +++ subversion/branches/inheritable-props/INSTALL Tue Jul 31 18:05:21 2012 @@ -96,29 +96,23 @@ I. INTRODUCTION These diff streams are used everywhere -- over the network, in the repository, and in the client's working copy. - * libserf or libneon (OPTIONAL for client) + * libserf (OPTIONAL for client) - The Serf and Neon libraries both allow the Subversion client - to send HTTP requests. This is necessary if you want your - client to access a repository served by the Apache HTTP - server. There is an alternate 'svnserve' server as well, - though, and clients automatically know how to speak the - svnserve protocol. Thus it's not strictly necessary for your - client to be able to speak HTTP... though we still recommend - that your client be built to speak both HTTP and svnserve - protocols. Your client can be compiled against either - libserf or libneon (or both), as they offer competing - implementations. + The Serf library allows the Subversion client to send HTTP + requests. This is necessary if you want your client to access + a repository served by the Apache HTTP server. There is an + alternate 'svnserve' server as well, though, and clients + automatically know how to speak the svnserve protocol. + Thus it's not strictly necessary for your client to be able + to speak HTTP... though we still recommend that your client + be built to speak both HTTP and svnserve protocols. * OpenSSL (OPTIONAL for client and server) OpenSSL enables your client to access SSL-encrypted https:// - URLs (using libserf or libneon) in addition to unencrypted - http:// URLs. To use SSL with Subversion's WebDAV server, - Apache needs to be compiled with OpenSSL as well. - - The Neon library can use the GnuTLS library as an alternative - to OpenSSL. + URLs (using libserf) in addition to unencrypted http:// URLs. + To use SSL with Subversion's WebDAV server, Apache needs to be + compiled with OpenSSL as well. * Berkeley DB (OPTIONAL for client and server) @@ -177,10 +171,10 @@ I. INTRODUCTION team has created a script that downloads the minimal prerequisite libraries (Apache Portable Runtime, Sqlite, and Zlib). The script, 'get-deps.sh', is available in the same directory as this file. - When run, it will place 'apr', 'apr-util', 'serf', 'neon', - 'zlib', and 'sqlite-amalgamation' directories directly into your - unpacked Subversion distribution, where they will be automatically - configured and built by Subversion's build process. + When run, it will place 'apr', 'apr-util', 'serf', 'zlib', and + 'sqlite-amalgamation' directories directly into your unpacked Subversion + distribution, where they will be automatically configured and built by + Subversion's build process. Note: there are optional dependencies (such as openssl, swig, and httpd) which get-deps.sh does not download and Subversion does not attempt to @@ -313,44 +307,22 @@ I. INTRODUCTION newer. The autogen.sh script knows about that. - 5. An HTTP client library: serf or neon. (OPTIONAL) + 5. Serf library 1.1 or newer (http://code.google.com/p/serf/) (OPTIONAL) If you want your client to be able to speak to an Apache server (via a http:// or https:// URL), you must link against - at least one of these libraries. Though optional, we strongly - recommend this. + serf. Though optional, we strongly recommend this. - (If you link against both, Subversion will use ra_serf by - default. Add "http-library = neon" to the [global] section of - your ~/.subversion/servers file to use ra_neon instead.) - - a. Serf library 1.1.0 or newer (http://code.google.com/p/serf/) - - In order to use ra_serf, you must install serf, and run - Subversion's ./configure with the argument --with-serf. If - serf is installed in a non-standard place, you should use + In order to use ra_serf, you must install serf, and run Subversion's + ./configure with the argument --with-serf. If serf is installed in a + non-standard place, you should use --with-serf=/path/to/serf/install - instead. - - For more information on serf and Subversion's ra_serf, see - the file subversion/libsvn_ra_serf/README. - - b. Neon library 0.25 through 0.29 (http://www.webdav.org/neon/) - - In order to use ra_neon, you must install neon, and run - Subversion's ./configure with the argument --with-neon. - Subversion's configuration mechanism should then detect the - installed Neon. If it does not, you may need to set the - LDFLAGS environment variable when you run "./configure", or - specify Neon's location by passing the "--with-neon=" - option to "./configure". Look for the "neon-config" script - in a "bin/" subdirectory of the target of "--with-neon". - For example, if you pass "--with-neon=/usr/local/myneon/", - then there should be a file - "/usr/local/myneon/bin/neon-config". + instead. + For more information on serf and Subversion's ra_serf, see the file + subversion/libsvn_ra_serf/README. 6. OpenSSL (OPTIONAL) @@ -358,28 +330,19 @@ I. INTRODUCTION ### finding OpenSSL, but we may need more docco here. and w.r.t ### zlib. - The Serf and Neon libraries have support for SSL encryption by - relying on the OpenSSL library. + The Serf library has support for SSL encryption by relying on the + OpenSSL library. a. Using OpenSSL on the client through Serf - b. Using OpenSSL on the client through Neon - - When Neon is created with this dependency, then the Subversion - client inherits the ability to support SSL connections. Neon - also has support for sending compressed data using the zlib - library which a Subversion client can take advantage of. - - On Unix systems, to build Neon with OpenSSL, you need OpenSSL + On Unix systems, to build Serf with OpenSSL, you need OpenSSL installed on your system, and you must add "--with-ssl" as a "./configure" parameter. If your OpenSSL installation is hard - for Neon to find, you may need to use "--with-libs=/path/to/lib" + for Serf to find, you may need to use "--with-libs=/path/to/lib" in addition. In particular, on Red Hat (but not Fedora Core) it is necessary to specify "--with-libs=/usr/kerberos" for OpenSSL to be found. You can also specify a path to the zlib library - using "--with-libs". Consult the Neon documentation for more - information on how to use these parameters and versions of - libraries you need. + using "--with-libs". Under Windows, you can specify the paths to these libraries by passing the options --with-zlib and --with-openssl to gen-make.py. @@ -651,7 +614,6 @@ II. INSTALLATION db*.i386.rpm (Version 4.0.14 or greater; version 4.3.27 or 4.2.52 is preferred however) expat (Comes with RedHat) - neon (Version 0.25.5) After downloading, install it (as root user): @@ -683,7 +645,7 @@ II. INSTALLATION # rm -f /usr/local/lib/libsvn* # rm -f /usr/local/lib/libapr* # rm -f /usr/local/lib/libexpat* - # rm -f /usr/local/lib/libneon* + # rm -f /usr/local/lib/libserf* Start the process by running "autogen.sh": @@ -849,11 +811,6 @@ II. INSTALLATION needed to compile Apache or APR. Note that this is the actual awk program, not an installer - just rename it to awk.exe and it is ready to use. - * Neon 0.26.1 or higher, downloaded from - http://www.webdav.org/neon/neon-0.26.1.tar.gz which is required - for building the client components. Neon is included in the zip file - distribution. (0.25.0+ compiles, but does not properly support all - HTTP auth types.) * Apache apr, apr-util, and optionally apr-iconv libraries, version 0.9.12 or later. Included in both the Subversion dependencies ZIP file and the Apache 2 source zip. If you are building from a Subversion @@ -907,7 +864,7 @@ II. INSTALLATION E.2 Notes - The Neon library supports secure connections with OpenSSL and + The Serf library supports secure connections with OpenSSL and on-the-wire compression with zlib. If you want to use the secure connections feature, you should pass the option "--with-openssl" to the gen-make.py script. See Section I.11 for @@ -955,9 +912,8 @@ II. INSTALLATION server dso modules and are using Visual Studio 6. You must build and install it from source if you are not using Visual Studio 6 and want to build and/or test the server modules. - * If you checked out Subversion from the repository then extract neon - into SVN\src-trunk\neon, the zip file source distribution includes - neon. + * If you checked out Subversion from the repository then install the serf + sources into SVN\src-trunk\serf. * If you want BDB backend support, extract the Berkeley DB files into SVN\src-trunk\db4-win32. It's a good idea to add SVN\src-trunk\db4-win32\bin to your PATH, so that Subversion can find Modified: subversion/branches/inheritable-props/configure.ac URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/configure.ac?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/configure.ac (original) +++ subversion/branches/inheritable-props/configure.ac Tue Jul 31 18:05:21 2012 @@ -1192,7 +1192,8 @@ SVN_CHECK_CTYPESGEN dnl decide whether we want to link against the RA/FS libraries AC_ARG_ENABLE(runtime-module-search, AS_HELP_STRING([--enable-runtime-module-search], - [Turn on dynamic loading of RA/FS libraries]), + [Turn on dynamic loading of RA/FS libraries including + third-party FS libraries]), [ if test "$enableval" = "yes"; then use_dso=yes Modified: subversion/branches/inheritable-props/get-deps.sh URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/get-deps.sh?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/get-deps.sh (original) +++ subversion/branches/inheritable-props/get-deps.sh Tue Jul 31 18:05:21 2012 @@ -48,7 +48,7 @@ APACHE_MIRROR=http://archive.apache.org/ # helpers usage() { echo "Usage: $0" - echo "Usage: $0 [ apr | neon | serf | zlib | sqlite ] ..." + echo "Usage: $0 [ apr | serf | zlib | sqlite ] ..." exit $1 } Modified: subversion/branches/inheritable-props/notes/authz_policy.txt URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/notes/authz_policy.txt?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/notes/authz_policy.txt (original) +++ subversion/branches/inheritable-props/notes/authz_policy.txt Tue Jul 31 18:05:21 2012 @@ -111,6 +111,21 @@ WHAT USERS SHOULD EXPECT FROM PATH-BASED This situation is quite annoying for people who can't read all the changed-paths. + Notice that for the purposes of gating read and write access to + revision properties, Subversion never considers the user's *write* + access to the changed-paths. To understand the reason behind this, + it helps to understand why revprop access is gated at all. + Subversion assumes that revprops for a given revision -- especially + the log message (svn:log) property -- are likely to reveal paths + modified in that revision. It is precisely because Subversion + tries not to reveal unreadable paths to users that revprop access + is limited as described above. So as long as the user has the + requisite read access to the changed-paths, it's okay if he or she + lacks write access to one or more of those paths when attempting to + set or change revprops -- the information Subversion is trying to + protect through its revprop access control is considered safe to + reveal to that user. + 6. KNOWN LEAKAGE OF UNREADABLE PATHS Modified: subversion/branches/inheritable-props/notes/directory-index/dirindex.py URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/notes/directory-index/dirindex.py?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/notes/directory-index/dirindex.py (original) +++ subversion/branches/inheritable-props/notes/directory-index/dirindex.py Tue Jul 31 18:05:21 2012 @@ -15,44 +15,49 @@ # specific language governing permissions and limitations # under the License. +from __future__ import division, with_statement -import collections import datetime import logging +import re import sqlite3 +import unicodedata class Error(Exception): def __init__(self, msg, *args, **kwargs): - opcode = kwargs.pop("action", None) - if opcode is not None: - msg = Dirent._opname(opcode) + msg + action = kwargs.pop("action", None) + if action is not None: + msg = "%s %s" % (NodeRev._opname(action), msg) super(Error, self).__init__(msg, *args, **kwargs) -class SQL(object): +class SQLclass(object): """Named index of SQL schema definitions and statements. Parses "schema.sql" and creates a class-level attribute for each script and statement in that file. """ - @classmethod - def _load_statements(cls): + def __init__(self): import cStringIO import pkgutil import re comment_rx = re.compile(r"\s*--.*$") - header_rx = re.compile(r"^---(STATEMENT|SCRIPT)" + header_rx = re.compile(r"^---(?PSTATEMENT|SCRIPT)" r"\s+(?P[_A-Z]+)$") + kind = None name = None content = None def record_current_statement(): if name is not None: - setattr(cls, name, content.getvalue()) + if kind == "SCRIPT": + self.__record_script(name, content.getvalue()) + else: + self.__record_statement(name, content.getvalue()) schema = cStringIO.StringIO(pkgutil.get_data(__name__, "schema.sql")) for line in schema: @@ -63,6 +68,7 @@ class SQL(object): header = header_rx.match(line) if header: record_current_statement() + kind = header.group("kind") name = header.group("name") content = cStringIO.StringIO() continue @@ -75,159 +81,554 @@ class SQL(object): content.write(line) content.write("\n") record_current_statement() -SQL._load_statements() + class __statement(object): + __slots__ = ("execute", "query") + def __init__(self, sql, query): + self.execute = sql._execute + self.query = query + + def __call__(self, cursor, **kwargs): + if len(kwargs): + return self.execute(cursor, self.query, kwargs) + return self.execute(cursor, self.query) + + def __record_statement(self, name, statement): + setattr(self, name, self.__statement(self, statement)) + + class __script(object): + __slots__ = ("execute", "query") + def __init__(self, sql, query): + self.execute = sql._executescript + self.query = query + + def __call__(self, cursor): + return self.execute(cursor, self.query) + + def __record_script(self, name, script): + setattr(self, name, self.__script(self, script)) + + LOGLEVEL = (logging.NOTSET + logging.DEBUG) // 2 + if logging.getLevelName(LOGLEVEL) == 'Level %s' % LOGLEVEL: + logging.addLevelName(LOGLEVEL, 'SQL') + + def _log(self, *args, **kwargs): + return logging.log(self.LOGLEVEL, *args, **kwargs) -class SQLobject(object): + __stmt_param_rx = re.compile(r':([a-z]+)') + def _execute(self, cursor, statement, parameters=None): + if parameters is not None: + fmt = statement.replace("%", "%%") + fmt = self.__stmt_param_rx.sub(r'%(\1)r', fmt) + self._log("EXECUTE: " + fmt, parameters) + return cursor.execute(statement, parameters) + else: + self._log("EXECUTE: %s", statement) + return cursor.execute(statement) + + def _executescript(self, cursor, script): + self._log("EXECUTE: %s", script) + return cursor.executescript(script) +SQL = SQLclass() + + +class SQLobject(dict): """Base for ORM abstractions.""" - __slots__ = () def __init__(self, **kwargs): - for name, val in kwargs.items(): - setattr(self, name, val) - for name in self.__slots__: - if not hasattr(self, name): - setattr(self, name, None) - - def _put(self, cursor): - raise NotImplementedError("SQLobject._insert") + super(SQLobject, self).__init__(**kwargs) + for name in self._columns: + super(SQLobject, self).setdefault(name, None) + + def __getattr__(self, name): + return self.__getitem__(name) + + def __setattr__(self, name, value): + return self.__setitem__(name, value) + + def __delattr__(self, name): + return self.__delitem__(name) + + def _put(self, _cursor): + self._put_statement(_cursor, **self) + if self.id is None: + self.id = _cursor.lastrowid + else: + assert self.id == _cursor.lastrowid @classmethod - def _get(self, cursor, pkey): - raise NotImplementedError("SQLobject._insert") + def _get(self, _cursor, pkey): + self._get_statement(_cursor, id=pkey) + return cls._from_row(_cursor.fetchone()) @classmethod def _from_row(cls, row): if row is not None: - return cls(**dict((col, row[col]) for col in row.keys())) + return cls(**row) return None - LOGLEVEL = (logging.NOTSET + logging.DEBUG) // 2 - if logging.getLevelName(LOGLEVEL) == 'Level %s' % LOGLEVEL: - logging.addLevelName(LOGLEVEL, 'SQL') + def _clone(self): + return self.__class__(**self) - @classmethod - def _log(cls, *args, **kwargs): - return logging.log(cls.LOGLEVEL, *args, **kwargs) - @classmethod - def _execute(cls, cursor, statement, parameters=None): - if parameters is not None: - fmt = statement.replace("%", "%%").replace("?", "%r") - cls._log("EXECUTE: " + fmt, *parameters) - return cursor.execute(statement, parameters) - else: - cls._log("EXECUTE: %s", statement) - return cursor.execute(statement) +class Txn(SQLobject): + """O/R mapping for the "txn" table.""" + _columns = ("id", "treeid", "revision", "created", "author", "state") + _put_statement = SQL.TXN_INSERT + _get_statement = SQL.TXN_GET -class Revent(SQLobject): - """O/R mapping for the "revision" table.""" + # state + TRANSIENT = "T" + PERMANENT = "P" + DEAD = "D" - __slots__ = ("version", "created", "author", "log") + def __init__(self, **kwargs): + super(Txn, self).__init__(**kwargs) + if self.state is None: + self.state = self.TRANSIENT + + def __str__(self): + return "%d/%d %c %s" % (self.revision, self.treeid, + self.state, self.created) + + @property + def _committed(self): + return self.state == self.PERMANENT + + @property + def _uncommitted(self): + return self.state == self.TRANSIENT + + @property + def _dead(self): + return self.state == self.DEAD + + @staticmethod + def _now(): + now = datetime.datetime.utcnow() + return (now.strftime("%Y%m%dT%H%M%S.%%03dZ") + % ((now.microsecond + 500) // 1000)) def _put(self, cursor): if self.created is None: - now = datetime.datetime.utcnow() - self.created = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ") - self._execute(cursor, SQL.INSERT_REVISION_RECORD, - [self.version, self.created, self.author, self.log]) + self.created = self._now() + super(Txn, self)._put(cursor) + if self.treeid is None: + SQL.TXN_UPDATE_INITIAL_TREEID(cursor, id = self.id) + self.treeid = self.id @classmethod - def _get(cls, cursor, pkey): - cursor.execute(SQL.GET_REVENT_BY_VERSION, [pkey]) + def _find_newest(cls, cursor): + SQL.TXN_FIND_NEWEST(cursor) return cls._from_row(cursor.fetchone()) + @classmethod + def _find_by_revision(cls, cursor, revision): + SQL.TXN_FIND_BY_REVISION(cursor, revision = revision) + return cls._from_row(cursor.fetchone()) -class Strent(SQLobject): - """O/R mapping for the "strindex" table.""" + @classmethod + def _find_by_revision_timestamp(cls, cursor, revision, created): + SQL.TXN_FIND_BY_REVISION_AND_TIMESTAMP( + cursor, revision = revision, created = creted) + return cls._from_row(cursor.fetchone()) - __slots__ = ("strid", "content") + def _commit(self, cursor, revision): + assert self._uncommitted + now = self._now() + SQL.TXN_COMMIT(cursor, id = self.id, + revision = revision, created = now) + self.revision = revision + self.created = now + self.state = self.PERMANENT + + def _abort(self, cursor): + assert self._uncommitted + SQL.TXN_ABORT(cursor, id = self.id) + self.state = self.DEAD + + def _cleanup(self, cursor): + assert self._dead + SQL.TXN_CLEANUP(cursor, id = self.id) + + +class Branch(SQLobject): + """O/R mapping for the "branch" table.""" + + _columns = ("id", "treeid", "nodeid", "origin", "state") + _put_statement = SQL.BRANCH_INSERT + _get_statement = SQL.BRANCH_GET + + # state + TRANSIENT = "T" + PERMANENT = "P" + + def __init__(self, **kwargs): + super(Branch, self).__init__(**kwargs) + if self.state is None: + self.state = self.TRANSIENT def _put(self, cursor): - self._execute(cursor, SQL.INSERT_STRINDEX_RECORD, [self.content]) - self.strid = cursor.lastrowid + super(Branch, self)._put(cursor) + if self.nodeid is None: + SQL.BRANCH_UPDATE_INITIAL_NODEID(cursor, id = self.id) + self.nodeid = self.id @classmethod - def _get(cls, cursor, pkey): - cls._execute(cursor, SQL.GET_STRENT_BY_STRID, [pkey]) - return cls._from_row(cursor.fetchone()) + def _update_treeid(cls, cursor, new_txn, old_txn): + SQL.BRANCH_UPDATE_TREEID(cursor, + new_treeid = new_txn.treeid, + old_treeid = old_txn.treeid) @classmethod - def _find(cls, cursor, content): - cls._execute(cursor, SQL.GET_STRENT_BY_CONTENT, [content]) - return cls._from_row(cursor.fetchone()) + def _history(cls, cursor, nodeid): + SQL.BRANCH_HISTORY(cursor, nodeid = nodeid) + for row in cursor: + yield cls._from_row(row) + + @classmethod + def _commit(cls, cursor, txn): + SQL.BRANCH_COMMIT(cursor, treeid = txn.treeid) + + @classmethod + def _cleanup(cls, cursor, txn): + SQL.BRANCH_CLEANUP(cursor, treeid = txn.treeid) -class Dirent(SQLobject): - """O/R mapping for a virtual non-materialized view representing - a join of the "dirindex" and "strindex" tables.""" - - __slots__ = ("rowid", "origin", "pathid", "version", - "kind", "opcode", "subtree", - "abspath") +class NodeRev(SQLobject): + """O/R mapping for the noderev/string/nodeview table.""" - # Kinds + _columns = ("id", "treeid", "nodeid", "origin", "parent", "branch", + "nameid", "name", "denameid", "dename", + "kind", "opcode", "state") + _put_statement = SQL.NODEREV_INSERT + _get_statement = SQL.NODEVIEW_GET + + # kind DIR = "D" FILE = "F" - # Opcodes + # opcode ADD = "A" REPLACE = "R" MODIFY = "M" DELETE = "D" RENAME = "N" + BRANCH = "B" + LAZY = "L" + BREPLACE = "X" + LAZY_BREPLACE = "Z" + + # state + TRANSIENT = "T" + PERMANENT = "P" + + def __init__(self, **kwargs): + super(NodeRev, self).__init__(**kwargs) + if self.state is None: + self.state = self.TRANSIENT + + def __str__(self): + return "%d %c %s%s" % (self.treeid, self.opcode, self.name, + self._isdir and '/' or '') # Opcode names __opnames = {ADD: "add", REPLACE: "replace", MODIFY: "modify", DELETE: "delete", - RENAME: "rename"} + RENAME: "rename", + BRANCH: "branch", + LAZY: "branch", + BREPLACE: "branch/replace", + LAZY_BREPLACE: "branch/replace"} @classmethod - def _opname(cls, opcode): + def _opname(cls, change): return cls.__opnames.get(opcode) @property def _deleted(self): return (self.opcode == self.DELETE) - def __str__(self): - return "%d %c%c%c %c %s" % ( - self.version, - self.subtree and "(" or " ", - self.opcode, - self.subtree and ")" or " ", - self.kind, self.abspath) + @property + def _lazy(self): + return (self.opcode in (self.LAZY, self.LAZY_BREPLACE)) + + @property + def _transient(self): + return (self.state == self.TRANSIENT) + + @property + def _isdir(self): + return (self.kind == self.DIR) + + @staticmethod + def __stringid(cursor, val): + SQL.STRING_FIND(cursor, val = val) + row = cursor.fetchone() + if row is not None: + return row['id'] + SQL.STRING_INSERT(cursor, val = val) + return cursor.lastrowid def _put(self, cursor): - strent = Strent._find(cursor, self.abspath) - if strent is None: - strent = Strent(content = self.abspath) - strent._put(cursor) - self._execute(cursor, SQL.INSERT_DIRINDEX_RECORD, - [self.origin, strent.strid, self.version, - self.kind, self.opcode,self.subtree]) - self.rowid = cursor.lastrowid - self.pathid = strent.strid + if self.nameid is None: + assert self.name is not None + self.nameid = self.__stringid(cursor, self.name) + if self.denameid is None: + if self.dename == self.name: + self.denameid = self.nameid + else: + assert self.dename is not None + self.denameid = self.__stringid(cursor, self.dename) + super(NodeRev, self)._put(cursor) @classmethod - def _get(cls, cursor, pkey): - cls._execute(cursor, SQL.GET_DIRENT_BY_ROWID, [pkey]) - return cls._from_row(cursor.fetchone()) + def _update_treeid(cls, cursor, new_txn, old_txn): + SQL.NODEREV_UPDATE_TREEID(cursor, + new_treeid = new_txn.treeid, + old_treeid = old_txn.treeid) + + def _delazify(self, cursor): + assert self._lazy and self._isdir + opcode = self.opcode == self.LAZY and self.BRANCH or self.BREPLACE + SQL.NODEREV_UPDATE_OPCODE(cursor, id = self.id, opcode = opcode) + self.opcode = opcode + + @classmethod + def _commit(cls, cursor, txn): + SQL.NODEREV_COMMIT(cursor, treeid = txn.treeid) @classmethod - def _find(cls, cursor, abspath, version): - cls._execute(cursor, - SQL.GET_DIRENT_BY_ABSPATH_AND_VERSION, - [abspath, version]) + def _cleanup(cls, cursor, txn): + SQL.NODEREV_CLEANUP(cursor, treeid = txn.treeid) + + @classmethod + def __find(cls, cursor, parent, name, txn): + if txn.state != txn.PERMANENT: + if parent is None: + finder = SQL.NODEVIEW_FIND_TRANSIENT_ROOT + else: + finder = SQL.NODEVIEW_FIND_TRANSIENT_BY_NAME + else: + if parent is None: + finder = SQL.NODEVIEW_FIND_ROOT + else: + finder = SQL.NODEVIEW_FIND_BY_NAME + finder(cursor, name = name, parent = parent, treeid = txn.treeid) return cls._from_row(cursor.fetchone()) + @classmethod + def _find(cls, cursor, parent, name, txn): + return cls.__find(cursor, parent, cls.__normtext(name), txn) + + @classmethod + def _commonprefix(cls, *args): + args = [arg.split('/') for arg in args] + prefix = [] + arglen = min(len(parts) for parts in args) + while arglen > 0: + same = set(cls.__normtext(parts[0]) for parts in args) + if len(same) > 1: + break + for parts in args: + del parts[0] + prefix.append(same.pop()) + arglen -= 1 + return '/'.join(prefix), ['/'.join(parts) for parts in args] + + @classmethod + def _lookup(cls, cursor, track, relpath, txn): + if track is None or track.path is None: + # Lookup from root + track = Track() + parent = cls.__find(cursor, None, "", txn) + if not relpath: + track.close(parent) + return track + track.append(parent) + else: + assert track.found + track = Track(track) + if not relpath: + track.close() + return track + parent = track.noderev + parts = cls.__normtext(relpath).split("/") + for name in parts[:-1]: + if not parent._isdir: + raise Error("ENOTDIR: " + track.path) + while parent._lazy: + parent = cls._get(cursor, id = parent.origin) + node = cls.__find(cursor, parent.branch, name, txn) + if node is None: + raise Error("ENODIR: " + track.path + '/' + name) + parent = node + track.append(parent) + while parent._lazy: + parent = cls._get(cursor, id = parent.origin) + track.close(cls.__find(cursor, parent.branch, parts[-1], txn)) + return track + + def _count_successors(self, cursor): + SQL.NODEREV_COUNT_SUCCESSORS(cursor, origin = self.id) + return int(cursor.fetchone()[0]) + + def _listdir(self, cursor, txn): + assert self._isdir + if txn.state != txn.PERMANENT: + lister = SQL.NODEVIEW_LIST_TRANSIENT_DIRECTORY + else: + lister = SQL.NODEVIEW_LIST_DIRECTORY + lister(cursor, parent = self.id, treeid = txn.treeid) + for row in cursor: + yield self._from_row(row) + + def _bubbledown(self, cursor, txn): + assert txn._uncommitted + assert self._lazy and self._isdir and self.origin is not None + originmap = dict() + origin = self + while origin._lazy: + origin = self._get(cursor, id = origin.origin) + for node in origin._listdir(cursor, txn): + newnode = node._branch(cursor, self, txn) + originmap[newnode.origin] == newnode + self._delazify(cursor) + return originmap + + def _branch(self, cursor, parent, txn, replaced=False): + assert txn._uncommitted + branch = Branch(treeid = txn.treeid, + nodeid = self.nodeid, + origin = self.branch) + branch._put(cursor) + if self._isdir: + opcode = replaced and self.LAZY_BREPLACE or self.LAZY + else: + opcode = replaced and self.BREPLACE or self.BRANCH + node = self._revise(opcode, txn) + node.parent = parent.id + node.branch = branch.id + node._put(cursor) + return node + + def _revise(self, opcode, txn): + assert txn._uncommitted + noderev = NodeRev._clone(self) + noderev.treeid = txn.treeid + noderev.opcode = opcode + + __readonly = frozenset(("name",)) + def __setitem__(self, key, value): + if key in self.__readonly: + raise Error("NodeRev.%s is read-only" % key) + if key == "dename": + name = self.__normtext(value) + value = self.__text(value) + super(NodeRev, self).__setitem__("name", name) + super(NodeRev, self).__setitem__(key, value) + + def __getitem__(self, key): + if key == "dename": + dename = super(NodeRev, self).__getitem__(key) + if dename is not None: + return dename + key = "name" + return super(NodeRev, self).__getitem__(key) + + @classmethod + def __text(cls, name): + if not isinstance(name, unicode): + return name.decode("UTF-8") + return name + + @classmethod + def __normtext(cls, name): + return unicodedata.normalize('NFC', cls.__text(name)) + + +class Track(object): + __slots__ = ("nodelist", "noderev", "lazy") + def __init__(self, other=None): + if other is None: + self.nodelist = list() + self.noderev = None + self.lazy = None + else: + self.nodelist = list(other.nodelist) + self.noderev = other.noderev + self.lazy = other.lazy + + def __str__(self): + return "%c%c %r" % ( + self.found and self.noderev.kind or '-', + self.lazy is not None and 'L' or '-', + self.path) + + @property + def found(self): + return (self.noderev is not None) + + @property + def parent(self): + if self.noderev is not None: + index = len(self.nodelist) - 2; + else: + index = len(self.nodelist) - 1; + if index >= 0: + return self.nodelist[index] + return None + + @property + def path(self): + if len(self.nodelist): + return '/'.join(n.name for n in self.nodelist[1:]) + return None + + @property + def open(self): + return not isinstance(self.nodelust, tuple) + + def append(self, noderev): + if self.lazy is None and noderev._lazy: + self.lazy = len(self.nodelist) + self.nodelist.append(noderev) + + def close(self, noderev=None): + if noderev is not None: + self.append(noderev) + self.noderev = noderev + self.nodelist = tuple(self.nodelist) + + def bubbledown(self, cursor, txn): + if self.lazy is None: + return + closed = not self.open + if closed: + self.nodelist = list(self.nodelist) + tracklen = len(self.nodelist) + index = self.lazy + node = self.nodelist[index] + originmap = node._bubbledown(cursor, txn) + while index < tracklen: + node = originmap[self.nodelist[index].id] + self.nodelist[index] = node + if node._isdir: + originmap = node._bubbledown(cursor, txn) + else: + originmap = None + index += 1 + self.lazy = None + if closed: + self.close() + class Index(object): def __init__(self, database): - self.conn = sqlite3.connect(database, isolation_level = "IMMEDIATE") + self.conn = sqlite3.connect(database, isolation_level = "DEFERRED") self.conn.row_factory = sqlite3.Row self.cursor = self.conn.cursor() self.cursor.execute("PRAGMA page_size = 4096") @@ -236,337 +637,194 @@ class Index(object): self.cursor.execute("PRAGMA case_sensitive_like = ON") self.cursor.execute("PRAGMA encoding = 'UTF-8'") - @staticmethod - def normpath(abspath): - return abspath.rstrip("/") - - @staticmethod - def subtree_pattern(abspath): - return (abspath.rstrip("/") - .replace("#", "##") - .replace("%", "#%") - .replace("_", "#_")) + "/%" - def initialize(self): try: - SQLobject._log("%s", SQL.CREATE_SCHEMA) - self.cursor.executescript(SQL.CREATE_SCHEMA) + SQL.CREATE_SCHEMA(self.cursor) + SQL._execute( + self.cursor, + "UPDATE txn SET created = :created WHERE id = 0", + {"created": Txn._now()}) self.commit() - finally: + except: self.rollback() + raise + + def begin(self): + SQL._execute(self.cursor, "BEGIN") def commit(self): - SQLobject._log("COMMIT") - return self.conn.commit() + SQL._log("COMMIT") + self.conn.commit() def rollback(self): - SQLobject._log("ROLLBACK") - return self.conn.rollback() + SQL._log("ROLLBACK") + self.conn.rollback() def close(self): self.rollback() - SQLobject._log("CLOSE") - return self.conn.close() + SQL._log("CLOSE") + self.conn.close() - def get_revision(self, version): - return Revent._get(self.cursor, version) + def get_txn(self, revision=None): + if revision is None: + return Txn._find_newest(self.cursor) + return Txn._find_by_revision(self.cursor, revision) + + def new_txn(self, revision, created=None, author=None, base_txn = None): + assert base_txn is None or base_txn.revision == revision + txn = Txn(revision = revision, created = created, author = author, + treeid = base_txn is not None and base_txn.treeid or None) + txn._put(self.cursor) + return txn + + def commit_txn(self, txn, revision): + txn._commit(self.cursor, revision) + NodeRev._commit(self.cursor, txn) + Branch._commit(self.cursor, txn) + + def abort_txn(self, txn): + txn._abort(self.cursor) + NodeRev._cleanup(self.cursor, txn) + Branch._cleanup(self.cursor, txn) + txn._cleanup(self.cursor) + + def listdir(self, txn, noderev): + # FIXME: Query seems OK but no results returned? + return noderev._listdir(self.conn.cursor(), txn) + + def lookup(self, txn, track=None, relpath=""): + return NodeRev._lookup(self.cursor, track, relpath, txn) + + def __add(self, txn, track, name, kind, opcode, origintrack=None): + assert kind in (NodeRev.FILE, NodeRev.DIR) + assert opcode in (NodeRev.ADD, NodeRev.REPLACE) + if not txn._uncommitted: + raise Error("EREADONLY: txn " + str(txn)) + if not track.found: + raise Error("ENOENT: " + track.path) + if not track.noderev._isdir: + raise Error("ENOTDIR: " + track.path) + + parent = track.noderev + oldnode = NodeRev._find(self.cursor, parent.id, name, txn) + if opcode == NodeRev.ADD and oldnode is not None: + raise Error("EEXIST: " + track.path + '/' + name) + + if origintrack is not None: + # Treat add as copy + if not origintrack.found: + raise Error("ENOENT: (origin) " + origintrack.path) + origin = origintrack.noderev + if origin.kind != kind: + raise Error("ENOTSAME: origin %c -> copy %c" + % (origin.kind, kind)) + ### Rename detection heuristics here ... + rename = False + else: + origin = None + rename = False - def new_revision(self, version, created=None, author=None, log=None): - revent = Revent(version = version, - created = created, - author = author, - log = log) - revent._put(self.cursor) - return revent - - def insert(self, dirent): - assert isinstance(dirent, Dirent) - dirent._put(self.cursor) - return dirent - - def lookup(self, abspath, version): - SQLobject._execute( - self.cursor, - SQL.LOOKUP_ABSPATH_AT_REVISION, - [abspath, version]) - row = self.cursor.fetchone() - if row is not None: - dirent = Dirent._from_row(row) - if not dirent._deleted: - return dirent - return None + if rename: + raise NotImplementedError("Rename detection heuristics") - def subtree(self, abspath, version): - SQLobject._execute( - self.cursor, - SQL.LIST_SUBTREE_AT_REVISION, - [version, self.subtree_pattern(abspath)]) - for row in self.cursor: - yield Dirent._from_row(row) - - def predecessor(self, dirent): - assert isinstance(dirent, Dirent) - if dirent.origin is None: - return None - return Dirent._get(self.cursor, dirent.origin) - - def successors(self, dirent): - assert isinstance(dirent, Dirent) - SQLobject._execute( - self.cursor, - SQL.LIST_DIRENT_SUCCESSORS, - [dirent.rowid]) - for row in self.cursor: - yield Dirent._from_row(row) - - -class Revision(object): - def __init__(self, index, version, - created=None, author=None, log=None): - self.index = index - self.version = version - self.revent = index.get_revision(version) - self.__created = created - self.__author = author - self.__log = log - self.__context = None - index.rollback() + track = Track(track) + track.bubbledown(self.cursor, txn) + if oldnode: + if parent.id != track.noderev.id: + # Bubbledown changed the track + parent = track.noderev + oldnode = NodeRev._find(self.cursor, parent.id, name, txn) + assert oldnode is not None + tombstone = oldnode._revise(oldnode.DELETE, txn) + tombstone.parent = parent.id + tombstone._put(self.cursor) + parent = track.noderev + if origin is not None: + newnode = origin._branch(self.cursor, parent.id, txn, + replaced = (oldnode is not None)) + else: + branch = Branch(treeid = txn.treeid) + branch._put(self.cursor) + newnode = NodeRev(treeid = txn.treeid, + nodeid = branch.nodeid, + branch = branch.id, + parent = parent.id, + kind = kind, + opcode = opcode) + newnode.dename = name + newnode._put(self.cursor) + track.close(newnode) + return track + + def add(self, txn, track, name, kind, origintrack=None): + return self.__add(txn, track, name, kind, NodeRev.ADD, origintrack) + + def replace(self, txn, track, name, kind, origintrack=None): + return self.__add(txn, track, name, kind, NodeRev.REPLACE, origintrack) + +# def modify(self, txn, track): +# if not txn._uncommitted +# raise Error("EREADONLY: txn " + str(txn)) +# if not track.found: +# raise Error("ENOENT: " + track.path) - class __Context(object): - def __init__(self, version, connection): - self.version = version - self.conn = connection - self.cursor = connection.cursor() - SQLobject._execute(self.cursor, SQL.CREATE_TRANSACTION_CONTEXT) - - def clear(self): - SQLobject._execute(self.cursor, SQL.REMOVE_TRANSACTION_CONTEXT) - - def __iter__(self): - SQLobject._execute(self.cursor, SQL.LIST_TRANSACTION_RECORDS) - for row in self.cursor: - dirent = Dirent._from_row(row) - dirent.version = self.version - yield dirent - - def lookup(self, abspath): - SQLobject._execute(self.cursor, - SQL.GET_TRANSACTION_RECORD, - [abspath]) - row = self.cursor.fetchone() - if row is not None: - dirent = Dirent._from_row(row) - dirent.version = self.version - return dirent - return None - - def remove(self, abspath, purge=False): - target = self.lookup(abspath) - if not target: - raise Error("txn context: remove nonexistent " + abspath) - logging.debug("txn context: remove %s", abspath) - SQLobject._execute(self.cursor, - SQL.REMOVE_TRANSACTION_RECORD, - [abspath]) - if purge: - logging.debug("txn context: purge %s/*", abspath) - SQLobject._execute(self.cursor, - SQL.REMOVE_TRANSACTION_SUBTREE, - [Index.subtree_pattern(abspath)]) - - def record(self, dirent, replace=False, purge=False): - target = self.lookup(dirent.abspath) - if target is not None: - if not replace: - raise Error("txn context: record existing " - + dirent.abspath) - elif not target.subtree: - raise Error("txn context: replace conflict " - + dirent.abspath) - self.remove(target.abspath, purge and target.kind == Dirent.DIR) - SQLobject._execute(self.cursor, - SQL.INSERT_TRANSACTION_RECORD, - [dirent.origin, dirent.abspath, - dirent.kind, dirent.opcode, dirent.subtree]) - - def __enter__(self): - if self.revent is not None: - raise Error("revision is read-only") - self.__context = self.__Context(self.version, self.index.conn) - SQLobject._execute(self.index.cursor, "BEGIN") - self.revent = self.index.new_revision( - self.version, self.__created, self.__author, self.__log) - return self - def __exit__(self, exc_type, exc_value, traceback): - try: - if exc_type is None: - for dirent in self.__context: - self.index.insert(dirent) - logging.debug("insert: %s", dirent) - self.index.commit() - else: - self.index.rollback() - except: - self.index.rollback() - raise - finally: - self.__context.clear() - self.__context = None - - def __record(self, dirent, replace=False, purge=False): - self.__context.record(dirent, replace, purge) - logging.debug("record: %s", dirent) - - def __check_writable(self, opcode): - if self.__context is None: - raise Error(" requires a transaction", action=opcode) - - def __check_not_root(self, abspath, opcode): - if abspath.rstrip("/") == "": - raise Error(" not allowed on /", action=opcode) - - def __find_target(self, abspath, opcode): - target = self.__context.lookup(abspath) - if target is not None: - if not target.subtree: - raise Error(" overrides explicit " + abspath, action=opcode) - return target, target.origin - target = self.index.lookup(abspath, self.version - 1) - if target is None: - raise Error(" target does not exist: " + abspath, action=opcode) - return target, target.rowid - def lookup(self, abspath): - try: - return self.index.lookup(self.index.normpath(abspath), - self.version) - finally: - if self.__context is None: - self.index.rollback() - - def __add(self, opcode, abspath, kind, frompath, fromver): - origin = None - if frompath is not None: - frompath = self.index.normpath(frompath) - fromver = int(fromver) - origin = self.index.lookup(frompath, fromver) - if origin is None: - raise Error(" source does not exist: " + frompath, action=opcode) - if origin.kind != kind: - raise Error(" changes the source object kind", action=opcode) - origin = origin.rowid - dirent = Dirent(origin = origin, - abspath = abspath, - version = self.version, - kind = kind, - opcode = opcode, - subtree = 0) - self.__record(dirent, - replace=(opcode == Dirent.REPLACE), - purge=(opcode == Dirent.REPLACE)) - if frompath is not None and dirent.kind == Dirent.DIR: - prefix = dirent.abspath - offset = len(frompath) - for source in list(self.index.subtree(frompath, fromver)): - abspath = prefix + source.abspath[offset:] - self.__record(Dirent(origin = source.rowid, - abspath = abspath, - version = self.version, - kind = source.kind, - opcode = opcode, - subtree = 1)) - - def add(self, abspath, kind, frompath=None, fromver=None): - opcode = Dirent.ADD - abspath = self.index.normpath(abspath) - self.__check_writable(opcode) - self.__check_not_root(abspath, opcode) - return self.__add(opcode, abspath, kind, frompath, fromver) - - def replace(self, abspath, kind, frompath=None, fromver=None): - opcode = Dirent.REPLACE - abspath = self.index.normpath(abspath) - self.__check_writable(opcode) - self.__check_not_root(abspath, opcode) - self.__find_target(abspath, opcode) - return self.__add(opcode, abspath, kind, frompath, fromver) - - def modify(self, abspath): - opcode = Dirent.MODIFY - abspath = self.index.normpath(abspath) - self.__check_writable(opcode) - target, origin = self.__find_target(abspath, opcode) - dirent = Dirent(origin = origin, - abspath = abspath, - version = self.version, - kind = target.kind, - opcode = opcode, - subtree = 0) - self.__record(dirent, replace=True) - - def delete(self, abspath): - opcode = Dirent.DELETE - abspath = self.index.normpath(abspath) - self.__check_writable(opcode) - self.__check_not_root(abspath, opcode) - target, origin = self.__find_target(abspath, opcode) - dirent = Dirent(origin = origin, - abspath = abspath, - version = self.version, - kind = target.kind, - opcode = opcode, - subtree = 0) - self.__record(dirent, replace=True, purge=True) - if target.version < self.version and dirent.kind == Dirent.DIR: - for source in self.index.subtree(abspath, self.version - 1): - self.__record(Dirent(origin = source.rowid, - abspath = source.abspath, - version = self.version, - kind = source.kind, - opcode = opcode, - subtree = 1)) +class Tree(object): + def __init__(self, index): + self.index = index + self.context = None + index.rollback() def simpletest(database): ix = Index(database) ix.initialize() - with Revision(ix, 1) as rev: - rev.add(u'/A', Dirent.DIR) - rev.add(u'/A/B', Dirent.DIR) - rev.add(u'/A/B/c', Dirent.FILE) - with Revision(ix, 2) as rev: - rev.add(u'/A/B/d', Dirent.FILE) - with Revision(ix, 3) as rev: - rev.add(u'/X', Dirent.DIR, u'/A', 1) - rev.add(u'/X/B/d', Dirent.FILE, u'/A/B/d', 2) - with Revision(ix, 4) as rev: - # rev.rename(u'/X/B/d', u'/X/B/x') - rev.delete(u'/X/B/d') - rev.add(u'/X/B/x', Dirent.FILE, u'/X/B/d', 3) - with Revision(ix, 5) as rev: - rev.delete(u'/A') - - for r in (0, 1, 2, 3, 4, 5): - print "Revision: %d" % r - for dirent in list(ix.subtree('/', r)): - origin = ix.predecessor(dirent) - if origin is None: - print " " + str(dirent) - else: - print " %-17s <- %s" % (dirent, origin) - - dirent = ix.lookup('/A/B/c', 4) - print "/A/B/c@4 -> %s@%d" % (dirent.abspath, dirent.version) - for succ in ix.successors(dirent): - print "%11s %s %s@%d" % ( - "", succ._deleted and "x_x" or "-->", - succ.abspath, succ.version) - ix.close() + try: + print "Lookup root" + tx = ix.get_txn() + print "transaction:", tx + root = ix.lookup(tx) + print "root track:", root + print "root noderev", root.noderev + + print "Add A/foo" + tx = ix.new_txn(0) + print "transaction:", tx + parent = ix.add(tx, root, "A", NodeRev.DIR) + print "A track:", parent + print "A noderev", parent.noderev + + node = ix.add(tx, parent, "foo", NodeRev.FILE) + print "foo track:", node + print "foo noderev", node.noderev + ix.commit_txn(tx, 1) + ix.commit() + + print "List contents" + tx = ix.get_txn() + print "transaction:", tx + root = ix.lookup(tx) + print str(root.noderev) + for n1 in ix.listdir(tx, root.noderev): + print " ", str(n1) + if n1._isdir: + for n2 in ix.listdir(tx, n1): + print " ", str(n2) + + print "Lookup A" + track = ix.lookup(tx, None, "A") + print str(track.noderev) + + print "Lookup A/foo" + track = ix.lookup(tx, None, "A/foo") + print str(track.noderev) + finally: + ix.close() def loggedsimpletest(database): import sys - logging.basicConfig(level=logging.DEBUG, #SQLobject.LOGLEVEL, + logging.basicConfig(level=SQL.LOGLEVEL, stream=sys.stderr) simpletest(database) Modified: subversion/branches/inheritable-props/notes/directory-index/schema.sql URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/notes/directory-index/schema.sql?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/notes/directory-index/schema.sql (original) +++ subversion/branches/inheritable-props/notes/directory-index/schema.sql Tue Jul 31 18:05:21 2012 @@ -19,165 +19,305 @@ ---SCRIPT CREATE_SCHEMA -DROP TABLE IF EXISTS dirindex; -DROP TABLE IF EXISTS strindex; -DROP TABLE IF EXISTS revision; - --- Revision record - -CREATE TABLE revision ( - version integer NOT NULL PRIMARY KEY, - created timestamp NOT NULL, - author varchar NULL, - log varchar NULL -); - --- Path lookup table - -CREATE TABLE strindex ( - strid integer NOT NULL PRIMARY KEY, - content varchar NOT NULL UNIQUE -); - --- Versioned directory tree - -CREATE TABLE dirindex ( - -- unique id of this node revision, used for - -- predecessor/successor links - rowid integer NOT NULL PRIMARY KEY, - - -- link to this node's immediate predecessor - origin integer NULL REFERENCES dirindex(rowid), - - -- absolute (repository) path - pathid integer NOT NULL REFERENCES strindex(strid), - - -- revision number - version integer NOT NULL REFERENCES revision(version), - - -- node kind (D = dir, F = file, etc.) - kind character(1) NOT NULL, +DROP VIEW IF EXISTS nodeview; +DROP TABLE IF EXISTS noderev; +DROP TABLE IF EXISTS string; +DROP TABLE IF EXISTS branch; +DROP TABLE IF EXISTS txn; + + +-- Transactions +CREATE TABLE txn ( + -- transaction number + id integer NOT NULL PRIMARY KEY, + + -- the version of the tree associated with this transaction; + -- initially the same as id, but may refer to the originator + -- transaction when tracking revprop changes and/or modified trees + -- (q.v., obliterate) + treeid integer NULL REFERENCES txn(id), + + -- the revision that this transaction represents; for uncommitted + -- transactions, the revision in which it was created + revision integer NULL, + + -- creation date, independent of the svn:date property + created timestamp NOT NULL, + + -- transaction author, independent of the svn:author property; may + -- be null if the repository allows anonymous modifications + author varchar NULL, + + -- transaction state + -- T = transient (uncommitted), P = permanent (committed), D = dead + state character(1) NOT NULL DEFAULT 'T', - -- the operation that produced this entry: - -- A = add, R = replace, M = modify, D = delete, N = rename - opcode character(1) NOT NULL, + -- sanity check: enumerated value validation + CONSTRAINT enumeration_validation CHECK (state IN ('T', 'P', 'D')) - -- the index entry is the result of an implicit subtree operation - subtree boolean NOT NULL + -- other attributes: + -- revision properties ); -CREATE UNIQUE INDEX dirindex_versioned_tree ON dirindex(pathid, version DESC); -CREATE INDEX dirindex_successor_list ON dirindex(origin); -CREATE INDEX dirindex_operation ON dirindex(opcode); - --- Repository root - -INSERT INTO revision (version, created, author, log) - VALUES (0, 'EPOCH', NULL, NULL); -INSERT INTO strindex (strid, content) VALUES (0, '/'); -INSERT INTO dirindex (rowid, origin, pathid, version, kind, opcode, subtree) - VALUES (0, NULL, 0, 0, 'D', 'A', 0); - - ----STATEMENT INSERT_REVISION_RECORD - -INSERT INTO revision (version, created, author, log) - VALUES (?, ?, ?, ?); - ----STATEMENT GET_REVENT_BY_VERSION - -SELECT * FROM revision WHERE version = ?; - ----STATEMENT INSERT_STRINDEX_RECORD - -INSERT INTO strindex (content) VALUES (?); ----STATEMENT GET_STRENT_BY_STRID +CREATE INDEX txn_revision_idx ON txn(revision); -SELECT * FROM strindex WHERE strid = ?; ----STATEMENT GET_STRENT_BY_CONTENT +-- Branches -- unique forks in the nodes' history +CREATE TABLE branch ( + -- branch identifier + id integer NOT NULL PRIMARY KEY, -SELECT * FROM strindex WHERE content = ?; + -- the transaction in which the branch was created + treeid integer NOT NULL REFERENCES txn(id), ----STATEMENT INSERT_DIRINDEX_RECORD + -- the node to which this branch belongs; refers to the initial + -- branch of the node + nodeid integer NULL REFERENCES branch(id), -INSERT INTO dirindex (origin, pathid, version, kind, opcode, subtree) - VALUES (?, ?, ?, ?, ?, ?); + -- the source branch from which this branch was forked + origin integer NULL REFERENCES branch(id), ----STATEMENT GET_DIRENT_BY_ROWID + -- mark branches in uncommitted transactions so that they can be + -- ignored by branch traversals + -- T = transient (uncommitted), P = permanent (committed) + state character(1) NOT NULL DEFAULT 'T', -SELECT dirindex.*, strindex.content FROM dirindex - JOIN strindex ON dirindex.pathid = strindex.strid -WHERE dirindex.rowid = ?; + -- sanity check: enumerated value validation + CONSTRAINT enumeration_validation CHECK (state IN ('T', 'P')), ----STATEMENT GET_DIRENT_BY_ABSPATH_AND_VERSION - -SELECT dirindex.*, strindex.content AS abspath FROM dirindex - JOIN strindex ON dirindex.pathid = strindex.strid -WHERE abspath = ? AND dirindex.version = ?; - ----STATEMENT LOOKUP_ABSPATH_AT_REVISION - -SELECT dirindex.*, strindex.content AS abspath FROM dirindex - JOIN strindex ON dirindex.pathid = strindex.strid -WHERE abspath = ? AND dirindex.version <= ? -ORDER BY abspath ASC, dirindex.version DESC -LIMIT 1; - ----STATEMENT LIST_SUBTREE_AT_REVISION - -SELECT dirindex.*, strindex.content AS abspath FROM dirindex - JOIN strindex ON dirindex.pathid = strindex.strid - JOIN (SELECT pathid, MAX(version) AS maxver FROM dirindex - WHERE version <= ? GROUP BY pathid) - AS filtered - ON dirindex.pathid == filtered.pathid - AND dirindex.version == filtered.maxver -WHERE abspath LIKE ? ESCAPE '#' - AND dirindex.opcode <> 'D' -ORDER BY abspath ASC; - ----STATEMENT LIST_DIRENT_SUCCESSORS - -SELECT dirindex.*, strindex.content AS abspath FROM dirindex - JOIN strindex ON dirindex.pathid = strindex.strid -WHERE dirindex.origin = ? -ORDER BY abspath ASC, dirindex.version ASC; - - --- Temporary transaction - ----SCRIPT CREATE_TRANSACTION_CONTEXT - -CREATE TEMPORARY TABLE txncontext ( - origin integer NULL, - abspath varchar NOT NULL UNIQUE, - kind character(1) NOT NULL, - opcode character(1) NOT NULL, - subtree boolean NOT NULL + -- sanity check: ye can't be yer own daddy + CONSTRAINT genetic_diversity CHECK (id <> origin) ); ----SCRIPT REMOVE_TRANSACTION_CONTEXT - -DROP TABLE IF EXISTS temp.txncontext; - ----STATEMENT INSERT_TRANSACTION_RECORD - -INSERT INTO temp.txncontext (origin, abspath, kind, opcode, subtree) - VALUES (?, ?, ?, ?, ?); - ----STATEMENT GET_TRANSACTION_RECORD +CREATE INDEX branch_txn_idx ON branch(treeid); +CREATE INDEX branch_node_idx ON branch(nodeid); -SELECT * FROM temp.txncontext WHERE abspath = ?; ----STATEMENT REMOVE_TRANSACTION_RECORD - -DELETE FROM temp.txncontext WHERE abspath = ?; - ----STATEMENT REMOVE_TRANSACTION_SUBTREE +-- File names -- lookup table of strings +CREATE TABLE string ( + id integer NOT NULL PRIMARY KEY, + val varchar NOT NULL UNIQUE +); -DELETE FROM temp.txncontext WHERE abspath LIKE ? ESCAPE '#'; ----STATEMENT LIST_TRANSACTION_RECORDS +-- Node revisions -- DAG of versioned node changes +CREATE TABLE noderev ( + -- node revision identifier + id integer NOT NULL PRIMARY KEY, + + -- the transaction in which the node was changed + treeid integer NOT NULL REFERENCES txn(id), + + -- the node identifier; a new node will get the ID of its initial + -- branch + nodeid integer NOT NULL REFERENCES branch(id), + + -- this node revision's immediate predecessor + origin integer NULL REFERENCES noderev(id), + + -- the parent (directory) of this node revision -- tree graph + parent integer NULL REFERENCES branch(id), + + -- the branch that this node revision belongs to -- history graph + branch integer NOT NULL REFERENCES branch(id), + + -- the indexable, NFC-normalized name of this noderev within its parent + nameid integer NOT NULL REFERENCES string(id), + + -- the original, denormalized, non-indexable name + denameid integer NOT NULL REFERENCES string(id), + + -- the node kind; immutable within the node + -- D = directory, F = file, etc. + kind character(1) NOT NULL, + + -- the change that produced this node revision + -- A = added, D = deleted, M = modified, N = renamed, R = replaced + -- B = branched (added + origin <> null) + -- L = lazy branch, indicates that child lookup should be performed + -- on the origin (requires kind=D + added + origin <> null) + -- X = replaced by branch (R + B) + -- Z = lazy replace by branch (Like L but implies X instead of B) + opcode character(1) NOT NULL, + + -- mark noderevs of uncommitted transactions so that they can be + -- ignored by tree traversals + -- T = transient (uncommitted), P = permanent (committed) + state character(1) NOT NULL DEFAULT 'T', + + -- sanity check: enumerated value validation + CONSTRAINT enumeration_validation CHECK ( + kind IN ('D', 'F') + AND state IN ('T', 'P') + AND opcode IN ('A', 'D', 'M', 'N', 'R', 'B', 'L', 'X', 'Z')), + + -- sanity check: only directories can be lazy + CONSTRAINT lazy_copies_make_more_work CHECK ( + opcode NOT IN ('B', 'L', 'X', 'Z') + OR (opcode IN ('B', 'X') AND origin IS NOT NULL) + OR (opcode IN ('L', 'Z') AND kind = 'D' AND origin IS NOT NULL)), + + -- sanity check: ye can't be yer own daddy + CONSTRAINT genetic_diversity CHECK (id <> origin), + + -- sanity check: ye can't be yer own stepdaddy, either + CONSTRAINT escher_avoidance CHECK (parent <> branch) + + -- other attributes: + -- versioned properties + -- contents reference +); -SELECT * FROM temp.txncontext ORDER BY abspath ASC; +CREATE UNIQUE INDEX noderev_tree_idx ON noderev(parent,nameid,treeid,opcode); +CREATE INDEX noderev_txn_idx ON noderev(treeid); +CREATE INDEX nodefev_node_idx ON noderev(nodeid); +CREATE INDEX noderev_successor_idx ON noderev(origin); + + +CREATE VIEW nodeview AS + SELECT + noderev.*, + ns.val AS name, + ds.val AS dename + FROM + noderev JOIN string AS ns ON noderev.nameid = ns.id + JOIN string AS ds ON noderev.denameid = ds.id; + + +-- Root directory + +INSERT INTO txn (id, treeid, revision, created, state) + VALUES (0, 0, 0, 'EPOCH', 'P'); +INSERT INTO branch (id, treeid, nodeid, state) VALUES (0, 0, 0, 'P'); +INSERT INTO string (id, val) VALUES (0, ''); +INSERT INTO noderev (id, treeid, nodeid, branch, + nameid, denameid, kind, opcode, state) + VALUES (0, 0, 0, 0, 0, 0, 'D', 'A', 'P'); + + +---STATEMENT TXN_INSERT +INSERT INTO txn (treeid, revision, created, author) + VALUES (:treeid, :revision, :created, :author); + +---STATEMENT TXN_UPDATE_INITIAL_TREEID +UPDATE txn SET treeid = :id WHERE id = :id; + +---STATEMENT TXN_GET +SELECT * FROM txn WHERE id = :id; + +---STATEMENT TXN_FIND_NEWEST +SELECT * FROM txn WHERE state = 'P' ORDER BY id DESC LIMIT 1; + +---STATEMENT TXN_FIND_BY_REVISION +SELECT * FROM txn WHERE revision = :revision AND state = 'P' +ORDER BY id DESC LIMIT 1; + +---STATEMENT TXN_FIND_BY_REVISION_AND_TIMESTAMP +SELECT * FROM txn +WHERE revision = :revision AND created <= :created AND state = 'P' +ORDER BY id DESC LIMIT 1; + +---STATEMENT TXN_COMMIT +UPDATE txn SET + revision = :revision, + created = :created, + state = 'P' +WHERE id = :id; + +---STATEMENT TXN_ABORT +UPDATE txn SET state = 'D' WHERE id = :id; + +---STATEMENT TXN_CLEANUP +DELETE FROM txn WHERE id = :id; + +---STATEMENT BRANCH_INSERT +INSERT INTO branch (nodeid, treeid, origin) + VALUES (:nodeid, :treeid, :origin); + +---STATEMENT BRANCH_UPDATE_INITIAL_NODEID +UPDATE branch SET nodeid = :id WHERE id = :id; + +---STATEMENT BRANCH_UPDATE_TREEID +UPDATE branch SET treeid = :new_treeid WHERE treeid = :old_treeid; + +---STATEMENT BRANCH_GET +SELECT * FROM branch WHERE id = :id; + +---STATEMENT BRANCH_HISTORY +SELECT * from branch WHERE nodeid = :nodeid ORDER BY id ASC; + +---STATEMENT BRANCH_COMMIT +UPDATE branch SET state = 'P' WHERE treeid = :treeid; + +---STATEMENT BRANCH_CLEANUP +DELETE FROM branch WHERE treeid = :treeid; + +---STATEMENT STRING_INSERT +INSERT INTO string (val) VALUES (:val); + +---STATEMENT STRING_FIND +SELECT * FROM string WHERE val = :val; + +---STATEMENT NODEREV_INSERT +INSERT INTO noderev (nodeid, treeid, origin, parent, branch, + nameid, denameid, kind, opcode) + VALUES (:nodeid, :treeid, :origin, :parent, :branch, + :nameid, :denameid, :kind, :opcode); + +---STATEMENT NODEREV_UPDATE_TREEID +UPDATE noderev SET treeid = :new_treeid WHERE treeid = :old_treeid; + +---STATEMENT NODEREV_UPDATE_OPCODE +UPDATE noderev SET opcode = :opcode WHERE id = :id; + +---STATEMENT NODEVIEW_GET +SELECT * FROM nodeview WHERE id = :id; + +---STATEMENT NODEREV_COUNT_SUCCESSORS +SELECT COUNT(id) FROM noderev WHERE origin = :origin; + +---STATEMENT NODEREV_COMMIT +UPDATE noderev SET state = 'P' WHERE treeid = :treeid; + +---STATEMENT NODEREV_CLEANUP +DELETE FROM noderev WHERE treeid = :treeid; + +---STATEMENT NODEVIEW_FIND_ROOT +SELECT * FROM nodeview +WHERE parent IS NULL AND name = '' + AND treeid <= :treeid AND state = 'P' +ORDER BY treeid DESC LIMIT 1; + +---STATEMENT NODEVIEW_FIND_BY_NAME +SELECT * FROM nodeview +WHERE parent = :parent AND name = :name + AND treeid <= :treeid AND state = 'P' +ORDER BY treeid DESC LIMIT 1; + +---STATEMENT NODEVIEW_FIND_TRANSIENT_ROOT +SELECT * FROM nodeview +WHERE parent IS NULL AND name = '' + AND (treeid < :treeid AND state = 'P' OR treeid = :treeid) +ORDER BY treeid DESC LIMIT 1; + +---STATEMENT NODEVIEW_FIND_TRANSIENT_BY_NAME +SELECT * FROM nodeview +WHERE parent = :parent AND name = :name + AND (treeid < :treeid AND state = 'P' OR treeid = :treeid) +ORDER BY treeid DESC LIMIT 1; + +---STATEMENT NODEVIEW_LIST_DIRECTORY +SELECT * FROM nodeview + JOIN (SELECT nameid, MAX(treeid) AS treeid FROM noderev + WHERE treeid <= :treeid AND state = 'P') AS filter + ON nodeview.nameid = filter.nameid AND nodeview.treeid = filter.treeid +WHERE parent = :parent AND opcode <> 'D' +ORDER BY nodeview.name ASC; + +---STATEMENT NODEVIEW_LIST_TRANSIENT_DIRECTORY +SELECT * FROM nodeview + JOIN (SELECT nameid, MAX(treeid) AS treeid FROM noderev + WHERE treeid < :treeid AND state = 'P' OR treeid = :treeid) AS filter + ON nodeview.nameid = filter.name AND nodeview.treeid = filter.treeid +WHERE parent = :parent AND opcode <> 'D' +ORDER BY nodeview.name ASC; Modified: subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.cpp URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.cpp?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.cpp (original) +++ subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.cpp Tue Jul 31 18:05:21 2012 @@ -911,7 +911,7 @@ CreateJ::CommitInfo(const svn_commit_inf } jobject -CreateJ::RevisionRangeList(apr_array_header_t *ranges) +CreateJ::RevisionRangeList(svn_rangelist_t *ranges) { JNIEnv *env = JNIUtil::getEnv(); Modified: subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.h URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.h?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.h (original) +++ subversion/branches/inheritable-props/subversion/bindings/javahl/native/CreateJ.h Tue Jul 31 18:05:21 2012 @@ -74,7 +74,7 @@ class CreateJ CommitInfo(const svn_commit_info_t *info); static jobject - RevisionRangeList(apr_array_header_t *ranges); + RevisionRangeList(svn_rangelist_t *ranges); static jobject StringSet(apr_array_header_t *strings); Modified: subversion/branches/inheritable-props/subversion/bindings/javahl/native/JNIUtil.cpp URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/bindings/javahl/native/JNIUtil.cpp?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/subversion/bindings/javahl/native/JNIUtil.cpp (original) +++ subversion/branches/inheritable-props/subversion/bindings/javahl/native/JNIUtil.cpp Tue Jul 31 18:05:21 2012 @@ -37,9 +37,13 @@ #include #include "svn_pools.h" +#include "svn_fs.h" +#include "svn_ra.h" +#include "svn_utf.h" #include "svn_wc.h" #include "svn_dso.h" #include "svn_path.h" +#include "svn_cache_config.h" #include #include "svn_private_config.h" #ifdef WIN32 @@ -175,6 +179,19 @@ bool JNIUtil::JNIGlobalInit(JNIEnv *env) apr_allocator_max_free_set(allocator, 1); } + svn_utf_initialize(g_pool); /* Optimize character conversions */ + svn_fs_initialize(g_pool); /* Avoid some theoretical issues */ + svn_ra_initialize(g_pool); + + /* We shouldn't fill the JVMs memory with FS cache data unless explictly + requested. */ + { + svn_cache_config_t settings = *svn_cache_config_get(); + settings.cache_size = 0; + settings.file_handle_count = 0; + settings.single_threaded = FALSE; + svn_cache_config_set(&settings); + } #ifdef ENABLE_NLS #ifdef WIN32 @@ -240,6 +257,8 @@ bool JNIUtil::JNIGlobalInit(JNIEnv *env) } #endif + svn_error_set_malfunction_handler(svn_error_raise_on_malfunction); + // Build all mutexes. g_finalizedObjectsMutex = new JNIMutex(g_pool); if (isExceptionThrown()) Modified: subversion/branches/inheritable-props/subversion/bindings/javahl/native/SVNClient.cpp URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/bindings/javahl/native/SVNClient.cpp?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/subversion/bindings/javahl/native/SVNClient.cpp (original) +++ subversion/branches/inheritable-props/subversion/bindings/javahl/native/SVNClient.cpp Tue Jul 31 18:05:21 2012 @@ -760,7 +760,7 @@ SVNClient::getMergeinfo(const char *targ jstring jpath = JNIUtil::makeJString((const char *) path); jobject jranges = - CreateJ::RevisionRangeList((apr_array_header_t *) val); + CreateJ::RevisionRangeList((svn_rangelist_t *) val); env->CallVoidMethod(jmergeinfo, addRevisions, jpath, jranges); Modified: subversion/branches/inheritable-props/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java (original) +++ subversion/branches/inheritable-props/subversion/bindings/javahl/src/org/apache/subversion/javahl/ClientNotifyInformation.java Tue Jul 31 18:05:21 2012 @@ -552,9 +552,14 @@ public class ClientNotifyInformation ext /** Operation failed because a node is obstructed */ failed_obstructed ("failed by obstruction"), - /** Conflict resolver is starting/ending. */ + /** Conflict resolver is starting. */ conflict_resolver_starting ("conflict resolver starting"), - conflict_resolver_done ("conflict resolver done"); + + /** Conflict resolver is done. */ + conflict_resolver_done ("conflict resolver done"), + + /** Operation left local modifications. */ + left_local_modifications ("left local modifications"); /** * The description of the action. Modified: subversion/branches/inheritable-props/subversion/bindings/swig/core.i URL: http://svn.apache.org/viewvc/subversion/branches/inheritable-props/subversion/bindings/swig/core.i?rev=1367697&r1=1367696&r2=1367697&view=diff ============================================================================== --- subversion/branches/inheritable-props/subversion/bindings/swig/core.i (original) +++ subversion/branches/inheritable-props/subversion/bindings/swig/core.i Tue Jul 31 18:05:21 2012 @@ -259,34 +259,34 @@ /* ----------------------------------------------------------------------- input rangelist */ -%apply apr_array_header_t *RANGELIST { - apr_array_header_t *rangeinput, - const apr_array_header_t *rangelist, - apr_array_header_t *from, - apr_array_header_t *to, - apr_array_header_t *changes, - apr_array_header_t *eraser, - apr_array_header_t *whiteboard, - apr_array_header_t *rangelist1, - apr_array_header_t *rangelist2 +%apply svn_rangelist_t *RANGELIST { + svn_rangelist_t *rangeinput, + const svn_rangelist_t *rangelist, + svn_rangelist_t *from, + svn_rangelist_t *to, + svn_rangelist_t *changes, + svn_rangelist_t *eraser, + svn_rangelist_t *whiteboard, + svn_rangelist_t *rangelist1, + svn_rangelist_t *rangelist2 } /* ----------------------------------------------------------------------- output rangelist */ -%apply apr_array_header_t **RANGELIST { - apr_array_header_t **rangelist, - apr_array_header_t **inheritable_rangelist, - apr_array_header_t **deleted, - apr_array_header_t **added, - apr_array_header_t **output +%apply svn_rangelist_t **RANGELIST { + svn_rangelist_t **rangelist, + svn_rangelist_t **inheritable_rangelist, + svn_rangelist_t **deleted, + svn_rangelist_t **added, + svn_rangelist_t **output } /* ----------------------------------------------------------------------- input and output rangelist */ -%apply apr_array_header_t **RANGELIST_INOUT { - apr_array_header_t **rangelist_inout +%apply svn_rangelist_t **RANGELIST_INOUT { + svn_rangelist_t **rangelist_inout } /* ----------------------------------------------------------------------- @@ -1145,15 +1145,15 @@ svn_swig_mergeinfo_sort(apr_hash_t **mer } static svn_error_t * -svn_swig_rangelist_merge(apr_array_header_t **rangelist_inout, - apr_array_header_t *changes, +svn_swig_rangelist_merge(svn_rangelist_t **rangelist_inout, + svn_rangelist_t *changes, apr_pool_t *pool) { return svn_rangelist_merge(rangelist_inout, changes, pool); } static svn_error_t * -svn_swig_rangelist_reverse(apr_array_header_t **rangelist_inout, +svn_swig_rangelist_reverse(svn_rangelist_t **rangelist_inout, apr_pool_t *pool) { return svn_rangelist_reverse(*rangelist_inout, pool);