Catching up trunk at r1405190.
git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/compressed-pristines@1413258 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/CHANGES b/CHANGES
index 46b631e..0440540 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,7 +4,7 @@
User-visible changes:
- General:
- * remove extraneous externals output from 'svn status -q' (issue #1935)
+ *
- Major new features:
*
@@ -12,6 +12,7 @@
- Minor new features and improvements:
* new 'svnadmin load --revision' load filtering support (issue #3734)
* new 'commit --include-externals' option (related to issues #1167, #3563)
+ * remove extraneous externals output from 'svn status -q' (issue #1935)
* new 'svnadmin hotcopy --incremental' support for FSFS (issue #3815)
* reject some attempts to merge between unrelated branches (r1215273)
* support GPG agent for password storage on UNIX-like platforms (r1150783)
@@ -25,9 +26,14 @@
* 'svn diff' can compare arbitrary files and directories (r1310291, et al)
* ra_serf avoids re-downloading content present in pristine store (r1333936)
* 'svn mergeinfo' now honors the --revision (-r) option (issue #4199)
- * new --search and --isearch options for 'svn log' (r1354666, -830)
+ * 'svn mergeinfo' now shows a summary graph by default (issue #4239)
+ * new --search and --search-and options for 'svn log' (r1354666, -83518)
* new built-in interactive text conflict merge tool (r1357864, et al)
* new 'svn --version --verbose' option shows runtime environment info
+ * new 'svnadmin freeze' subcommand (r1376228)
+ * 'svndumpfilter' now supports --delta dumpfiles (r1351009, -3745)
+ * client version info now reported to commit hooks (issue #4124)
+ * 'svn merge' now uses reintegrate mode automatically when needed
- Client-side bugfixes:
*
@@ -46,7 +52,46 @@
* fix inconsistent handling of log revs without changed paths (issue #3694)
- Bindings:
- *
+ * star-imports in swig-py only import 'svn_*' symbols (r1303375)
+
+
+Version 1.7.7
+(09 Oct 2012, from /branches/1.7.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.7.7
+ User-visible changes
+ - Client- and server-side bugfixes:
+ * fix memory read bug (r137614)
+ * update Chinese translation
+
+ - Client-side bugfixes:
+ * fix issues with applying Git patch files (r1374800, et al)
+ * fix status does not descend into dir externals after upgrade (issue #4016)
+ * fix file externals don't update with old mod_dav_svn (issue #4224)
+ * fix external diff tool duplicates Index: lines with 'svn diff' (r1380697)
+ * fix GNOME keyring library fails with very old glib (r1378847)
+ * fix unknown password stores in config file cause error (r1375052)
+ * fix assertions in ra_serf running against server root (r1365519, et al)
+ * fix ra_serf checkout/export aborts early on Windows (issue #4174)
+
+ - Server-side bugfixes:
+ * fix an assert with SVNAutoVersioning in mod_dav_svn (issue #4231)
+ * fix unbounded memory use with SVNPathAuthz short_circuit (r1387943)
+ * fix svndumpfilter exclude --targets requires leading slash (issue #4234)
+ * fix connection ttl for memcache should be 50 seconds (r1391641)
+ * stabilize order of paths in dumpfiles with APR 1.4.6 (r1344864, et al)
+
+ Developer-visible changes:
+ - General:
+ * print "All tests successful" at the end of 'make check' (r1375089)
+ * fix sandbox violation in a test (r1371282)
+ * fix tests fail when running within a format 30 WC (r1391188, et al)
+ * fix return value of svn_client_update4() incorrect (r1380295)
+ * fix make check summary missing test failures (r1390965)
+ * fix build does not fail when apache httpd is not available (r1374198)
+
+ - Bindings:
+ * fix swig-pl build fails with swig 2.0.7 and newer. (r1389658)
+ * fix swig-py runtime problems with swig 2.0.5 and newer (r1351117)
Version 1.7.6
@@ -487,6 +532,20 @@
* fixed setting binary properties in JavaHL (issue #3770)
* fix type mapping of svn_txdelta_window_t in python bindings (issue #3688)
+Version 1.6.19
+(10 Sep 2012, from /branches/1.6.x)
+http://svn.apache.org/repos/asf/subversion/tags/1.6.19
+
+ - Client-side bugfixes:
+ * handle missing svn:date reported by svnserve gracefully (r1306111)
+
+ - Server-side bugfixes:
+ * fix possible server hang if a hook script fails to start (r1330410)
+ * fix write-through proxy commit regression introduced in 1.6.17 (r1088602)
+ * partial sync drops properties when converting to adds (issue #4184)
+
+ - Developer-visible changes:
+ * fix the testsuite to avoid FAILs on APR hash order (r1230714, et al)
Version 1.6.18
(29 Mar 2012, from /branches/1.6.x)
diff --git a/COMMITTERS b/COMMITTERS
index b840919..870f810 100644
--- a/COMMITTERS
+++ b/COMMITTERS
@@ -41,7 +41,7 @@
lgo Lieven Govaerts <lgo@mobsol.be>
hwright Hyrum Wright <hyrum@hyrumwright.org>
vgeorgescu Vlad Georgescu <vgeorgescu@gmail.com>
- kameshj Kamesh Jayachandran <kamesh@collab.net>
+ kameshj Kamesh Jayachandran <kamesh.jayachandran@gmail.com>
markphip Mark Phippard <mphippard@collab.net>
arfrever Arfrever Frehtes Taifersar Arahesis <arfrever.fta@gmail.com>
stsp Stefan Sperling <stsp@elego.de>
@@ -55,7 +55,7 @@
jwhitlock Jeremy Whitlock <jcscoobyrs@gmail.com>
sbutler Stephen Butler <sbutler@elego.de>
dannas Daniel Näslund <dannas@dannas.name>
- stefan2 Stefan Fuhrmann <stefanfuhrmann@alice-dsl.de>
+ stefan2 Stefan Fuhrmann <stefan.fuhrmann@wandisco.com>
jcorvel Johan Corveleyn <jcorvel@gmail.com>
trent Trent Nelson <trent@snakebite.org>
@@ -82,6 +82,7 @@
kon Kalle Olavi Niemitalo <kon@iki.fi> (psvn.el)
rassilon Bill Tutt <bill@tutts.org> (Win32, COM, issue-1003-dev br.)
pll Paul lussier <p.lussier@comcast.net> (releases)
+ rdonch Роман Донченко <dpb@corrigendum.ru> (Swig-Python b.)
Commit access for specific areas:
@@ -94,7 +95,6 @@
joeswatosh Joe Swatosh <joe.swatosh@gmail.com> (Swig-Ruby b.)
jrvernooij Jelmer Vernooij <jelmer@samba.org> (Python bindings)
sage Sage LaTorra <sagelt@gmail.com> (Ctypes-Python b.)
- rdonch Роман Донченко <dpb@corrigendum.ru> (Swig-Python b.)
vmpn Vladimir Berezniker <vmpn@hitechman.com> (JavaHL bindings)
Packages:
diff --git a/Makefile.in b/Makefile.in
index 690d16a..4f1b871 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -250,6 +250,7 @@
INSTALL_MOD_SHARED = @APXS@ -i -S LIBEXECDIR="$(APACHE_LIBEXECDIR)" @MOD_ACTIVATION@
INSTALL_DATA = $(INSTALL) -m 644
INSTALL_LOCALE = $(INSTALL_DATA)
+INSTALL_APACHE_MODS = @INSTALL_APACHE_MODS@
### this isn't correct yet
INSTALL_SWIG_PY = $(INSTALL_LIB)
@@ -280,6 +281,19 @@
compileall.compile_dir("$(DESTDIR)$(swig_pydir_extra)", 1, \
"$(swig_pydir_extra)");'
+# export an env variable so that the tests can run without being installed
+TEST_SHLIB_VAR_SWIG_PY=\
+ if [ "@SVN_APR_SHLIB_PATH_VAR@" == "DYLD_LIBRARY_PATH" ]; then \
+ for d in $(SWIG_PY_DIR)/libsvn_swig_py $(SWIG_PY_DIR)/../../../libsvn_*; do \
+ if [ -n "$$DYLD_LIBRARY_PATH" ]; then \
+ @SVN_APR_SHLIB_PATH_VAR@="$$@SVN_APR_SHLIB_PATH_VAR@:$$d/.libs"; \
+ else \
+ @SVN_APR_SHLIB_PATH_VAR@="$$d/.libs"; \
+ fi; \
+ done; \
+ export @SVN_APR_SHLIB_PATH_VAR@; \
+ fi;
+
# The path to generated and complementary source files for the SWIG
# bindings.
SWIG_PL_DIR = $(abs_builddir)/subversion/bindings/swig/perl
@@ -310,6 +324,19 @@
$(INSTALL_DATA) "$$i" $(DESTDIR)$(SWIG_RB_SITE_LIB_DIR)/svn; \
done
+# export an env variable so that the tests can run without being installed
+TEST_SHLIB_VAR_SWIG_RB=\
+ if [ "@SVN_APR_SHLIB_PATH_VAR@" == "DYLD_LIBRARY_PATH" ]; then \
+ for d in $(SWIG_PY_DIR)/libsvn_swig_rb $(SWIG_PY_DIR)/../../../libsvn_*; do \
+ if [ -n "$$DYLD_LIBRARY_PATH" ]; then \
+ @SVN_APR_SHLIB_PATH_VAR@="$$@SVN_APR_SHLIB_PATH_VAR@:$$d/.libs"; \
+ else \
+ @SVN_APR_SHLIB_PATH_VAR@="$$d/.libs"; \
+ fi; \
+ done; \
+ export @SVN_APR_SHLIB_PATH_VAR@; \
+ fi;
+
APXS = @APXS@
PYTHON = @PYTHON@
@@ -790,6 +817,7 @@
swig-py: autogen-swig-py copy-swig-py
check-swig-py: swig-py
+ $(TEST_SHLIB_VAR_SWIG_PY) \
cd $(SWIG_PY_DIR); \
$(PYTHON) $(SWIG_PY_SRC_DIR)/tests/run_all.py
@@ -812,6 +840,7 @@
swig-rb: autogen-swig-rb
check-swig-rb: swig-rb svnserve
+ $(TEST_SHLIB_VAR_SWIG_RB) \
cd $(SWIG_RB_DIR); \
$(RUBY) -I $(SWIG_RB_SRC_DIR) \
$(SWIG_RB_SRC_DIR)/test/run-test.rb \
diff --git a/NOTICE b/NOTICE
index 38bf48b..4450bc3 100644
--- a/NOTICE
+++ b/NOTICE
@@ -16,3 +16,8 @@
This product includes software developed by Markus Kuhn under a permissive
license, see LICENSE.
+This software contains code derived from the RSA Data Security
+Inc. MD5 Message-Digest Algorithm, including various
+modifications by Spyglass Inc., Carnegie Mellon University, and
+Bell Communications Research, Inc (Bellcore).
+
diff --git a/build.conf b/build.conf
index 3b29fbb..32faf3f 100644
--- a/build.conf
+++ b/build.conf
@@ -64,9 +64,9 @@
bdb-test-scripts =
-swig-python-opts = -python -classic
-swig-perl-opts = -perl -nopm -noproxy
-swig-ruby-opts = -ruby
+swig-python-opts = $(CPPFLAGS) -python -classic
+swig-perl-opts = $(CPPFLAGS) -perl -nopm -noproxy
+swig-ruby-opts = $(CPPFLAGS) -ruby
swig-languages = python perl ruby
swig-dirs =
subversion/bindings/swig/python
@@ -83,6 +83,27 @@
#
# BUILD TARGETS
#
+# Target parameters:
+# description - optional build target description
+# type - the target type, defines how to build it
+# when - the name of an autoconf-substed variable that muset be
+# defined to either "true" or "false", that determines
+# whether this target should be built and installed.
+# path - relative path to target sources
+# sources - explicit list of target sources
+# install - the installation group/type
+# manpages - the man pages associated with this target
+# libs - libraries that this target depends on
+# nonlibs - dependencies that are not linked into the target
+# lang - bindings for language $(lang)
+# msvc-libs - additional libraries to link with on Windows
+# msvc-export - additional list of files to expose in dsp/vc(x)proj
+# msvc-static - visual studio target produces only a static lib
+# add-deps - expands to additional autoconf-defined dependencies
+# add-install-deps - like add-deps, but for the install step
+# external-lib - expands to additional autoconf-defined libs
+# external-project - visual studio project to depend on
+#
# The subversion command-line client
[svn]
@@ -327,6 +348,7 @@
# Subversion plugin for Apache's mod_dav
[mod_dav_svn]
description = Subversion plug-in for the Apache DAV module
+when = INSTALL_APACHE_MODS
type = apache-mod
path = subversion/mod_dav_svn
sources = *.c reports/*.c posts/*.c
@@ -337,6 +359,7 @@
[mod_authz_svn]
description = Subversion path-based authorization module for Apache
+when = INSTALL_APACHE_MODS
type = apache-mod
path = subversion/mod_authz_svn
nonlibs = mod_dav_svn apr aprutil
@@ -346,6 +369,7 @@
[mod_dontdothat]
description = Apache Httpd module to block certain kinds of Apache Subversion requests
+when = INSTALL_APACHE_MODS
type = apache-mod
path = tools/server-side/mod_dontdothat
nonlibs = mod_dav_svn apr aprutil
@@ -1156,7 +1180,7 @@
ra-local-test
svndiff-test vdelta-test
entries-dump atomic-ra-revprop-change wc-lock-tester wc-incomplete-tester
- diff diff3 diff4
+ diff diff3 diff4 fsfs-reorg svn-bench
client-test
conflict-data-test db-test pristine-store-test entries-compat-test
op-depth-test dirent_uri-test wc-queries-test
@@ -1205,6 +1229,13 @@
# ----------------------------------------------------------------------------
# Contrib and tools
+[fsfs-reorg]
+type = exe
+path = tools/server-side
+sources = fsfs-reorg.c
+install = tools
+libs = libsvn_delta libsvn_subr apr
+
[diff]
type = exe
path = tools/diff
@@ -1226,6 +1257,13 @@
install = tools
libs = libsvn_diff libsvn_subr apriconv apr
+[svn-bench]
+type = exe
+path = tools/client-side/svn-bench
+install = tools
+libs = libsvn_client libsvn_wc libsvn_ra libsvn_subr libsvn_delta
+ apriconv apr
+
[svnauthz-validate]
description = Authz config file validator
type = exe
diff --git a/build/ac-macros/apache.m4 b/build/ac-macros/apache.m4
index 64e477a..324ea0c 100644
--- a/build/ac-macros/apache.m4
+++ b/build/ac-macros/apache.m4
@@ -128,6 +128,7 @@
APACHE_LIBEXECDIR="$withval"
])
+INSTALL_APACHE_MODS=false
if test -n "$APXS" && test "$APXS" != "no"; then
APXS_CC="`$APXS -q CC`"
APACHE_INCLUDES="$APACHE_INCLUDES -I$APXS_INCLUDE"
@@ -140,6 +141,7 @@
BUILD_APACHE_RULE=apache-mod
INSTALL_APACHE_RULE=install-mods-shared
+ INSTALL_APACHE_MODS=true
case $host in
*-*-cygwin*)
@@ -157,6 +159,7 @@
AC_SUBST(APACHE_LDFLAGS)
AC_SUBST(APACHE_INCLUDES)
AC_SUBST(APACHE_LIBEXECDIR)
+AC_SUBST(INSTALL_APACHE_MODS)
# there aren't any flags that interest us ...
#if test -n "$APXS" && test "$APXS" != "no"; then
diff --git a/build/ac-macros/berkeley-db.m4 b/build/ac-macros/berkeley-db.m4
index e2abb81..f43dc34 100644
--- a/build/ac-macros/berkeley-db.m4
+++ b/build/ac-macros/berkeley-db.m4
@@ -175,13 +175,24 @@
svn_check_berkeley_db_minor=$2
svn_check_berkeley_db_patch=$3
- # Extract only the -ldb.* flag from the libs supplied by apu-config
- # Otherwise we get bit by the fact that expat might not be built yet
- # Or that it resides in a non-standard location which we would have
- # to compensate with using something like -R`$apu_config --prefix`/lib.
- #
- if test -z "$SVN_DB_LIBS"; then
- SVN_DB_LIBS=["`$apu_config --libs | $SED -e 's/.*\(-ldb[^[:space:]]*\).*/\1/' | $EGREP -- '-ldb[^[:space:]]*'`"]
+ if test -z "$SVN_DB_LIBS"; then
+ # We pass --dbm-libs here since Debian has modified apu-config not
+ # to return -ldb unless --dbm-libs is passed. This may also produce
+ # extra output beyond -ldb but since we're only filtering for -ldb
+ # it won't matter to us. However, --dbm-libs was added to apu-config
+ # in 1.3.8 so it's possible the version we have doesn't support it
+ # so fallback without it if we get an error.
+ svn_db_libs_prefiltered=["`$apu_config --libs --dbm-libs`"]
+ if test $? -ne 0; then
+ svn_db_libs_prefiltered=["`$apu_config --libs`"]
+ fi
+
+ # Extract only the -ldb.* flag from the libs supplied by apu-config
+ # Otherwise we get bit by the fact that expat might not be built yet
+ # Or that it resides in a non-standard location which we would have
+ # to compensate with using something like -R`$apu_config --prefix`/lib.
+ #
+ SVN_DB_LIBS=["`echo \"$svn_db_libs_prefiltered\" | $SED -e 's/.*\(-ldb[^[:space:]]*\).*/\1/' | $EGREP -- '-ldb[^[:space:]]*'`"]
fi
CPPFLAGS="$SVN_DB_INCLUDES $SVN_APRUTIL_INCLUDES $CPPFLAGS"
diff --git a/build/ac-macros/macosx.m4 b/build/ac-macros/macosx.m4
index 81e70d6..0a60b5c 100644
--- a/build/ac-macros/macosx.m4
+++ b/build/ac-macros/macosx.m4
@@ -19,6 +19,32 @@
dnl
dnl Mac OS X specific checks
+dnl SVN_LIB_MACHO_ITERATE
+dnl Check for _dyld_image_name and _dyld_image_header availability
+AC_DEFUN(SVN_LIB_MACHO_ITERATE,
+[
+ AC_MSG_CHECKING([for Mach-O dynamic module iteration functions])
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+ #include <mach-o/dyld.h>
+ #include <mach-o/loader.h>
+ int check(void) {
+ const struct mach_header *header = _dyld_get_image_header(0);
+ const char *name = _dyld_get_image_name(0);
+ if (name && header) return 1;
+ return 0;
+ }
+ ]],[[]])],[have_macho_iterate=yes],[have_macho_iterate=no])
+
+ if test "$have_macho_iterate" = "yes"; then
+ AC_DEFINE([SVN_HAVE_MACHO_ITERATE], [1],
+ [Is Mach-O low-level _dyld API available?])
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ fi
+])
+
dnl SVN_LIB_MACOS_PLIST
dnl Assign variables for Mac OS property list support
AC_DEFUN(SVN_LIB_MACOS_PLIST,
diff --git a/build/ac-macros/serf.m4 b/build/ac-macros/serf.m4
index 76766d8..ff0f6f6 100644
--- a/build/ac-macros/serf.m4
+++ b/build/ac-macros/serf.m4
@@ -19,50 +19,101 @@
dnl
dnl SVN_LIB_SERF(min_major_num, min_minor_num, min_micro_num)
dnl
-dnl Check configure options and assign variables related to
-dnl the serf library.
+dnl Search for a suitable version of serf. min_major_num,
+dnl min_minor_num, and min_micro_num are used to determine
+dnl if the serf library is at least that version.
+dnl
+dnl If a --with-serf option (no argument) or --with-serf=yes
+dnl option is passed, then a search for serf on the system will be
+dnl performed with pkg-config. If --with-serf=yes was actually passed
+dnl then we error if we can't actually find serf.
+dnl
+dnl If a --with-serf=PREFIX option is passed search for a suitable
+dnl serf installed on the system under that PREFIX. We will
+dnl error if we can't find serf.
+dnl
+dnl If a --with-serf=no option is passed then no search will be
+dnl conducted.
+dnl
+dnl If the search for serf fails, set svn_lib_serf to no, otherwise set
+dnl it to yes.
dnl
AC_DEFUN(SVN_LIB_SERF,
[
serf_found=no
+ serf_required=no
+ serf_skip=no
serf_check_major="$1"
serf_check_minor="$2"
serf_check_patch="$3"
+ serf_check_version="$1.$2.$3"
AC_ARG_WITH(serf,AS_HELP_STRING([--with-serf=PREFIX],
- [Serf HTTP client library]),
+ [Serf HTTP client library (enabled by default if found)]),
[
if test "$withval" = "yes" ; then
- AC_MSG_ERROR([--with-serf requires an argument.])
- elif test "$withval" != "no" ; then
- AC_MSG_NOTICE([serf library configuration])
- serf_prefix=$withval
- for serf_major in serf-2 serf-1; do
- if ! test -d $serf_prefix/include/$serf_major; then continue; fi
- save_cppflags="$CPPFLAGS"
- CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$serf_prefix/include/$serf_major"
- AC_CHECK_HEADERS(serf.h,[
- save_ldflags="$LDFLAGS"
- LDFLAGS="$LDFLAGS -L$serf_prefix/lib"
- AC_CHECK_LIB($serf_major, serf_context_create,[
- AC_TRY_COMPILE([
+ serf_required=yes
+ elif test "$withval" = "no" ; then
+ serf_skip=yes
+ else
+ SVN_SERF_PREFIX_CONFIG()
+ fi
+ ])
+
+ if test "$serf_skip" = "no" ; then
+ if test "$serf_found" = "no" ; then
+ SVN_SERF_PKG_CONFIG()
+ fi
+
+ AC_MSG_CHECKING([was serf enabled])
+ if test "$serf_found" = "yes"; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ SVN_DOWNLOAD_SERF()
+ if test "$serf_required" = "yes"; then
+ AC_MSG_ERROR([Serf was explicitly enabled but an appropriate version was not found.])
+ fi
+ fi
+ fi
+
+ svn_lib_serf=$serf_found
+
+ AC_SUBST(SVN_SERF_INCLUDES)
+ AC_SUBST(SVN_SERF_LIBS)
+])
+
+dnl SVN_SERF_PREFIX_CONFIG()
+dnl Use user provided prefix to try and detect and configure serf
+AC_DEFUN(SVN_SERF_PREFIX_CONFIG,
+[
+ AC_MSG_NOTICE([serf library configuration via prefix])
+ serf_required=yes
+ serf_prefix=$withval
+ for serf_major in serf-2 serf-1; do
+ if ! test -d $serf_prefix/include/$serf_major; then continue; fi
+ save_cppflags="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$serf_prefix/include/$serf_major"
+ AC_CHECK_HEADERS(serf.h,[
+ save_ldflags="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -L$serf_prefix/lib"
+ AC_CHECK_LIB($serf_major, serf_context_create,[
+ AC_TRY_COMPILE([
#include <stdlib.h>
#include "serf.h"
],[
#if ! SERF_VERSION_AT_LEAST($serf_check_major, $serf_check_minor, $serf_check_patch)
-#error Serf version too old: need $serf_check_major.$serf_check_minor.$serf_check_patch
+#error Serf version too old: need $serf_check_version
#endif
-], [serf_found=yes], [AC_MSG_WARN([Serf version too old: need $serf_check_major.$serf_check_minor.$serf_check_patch])
- serf_found=no])], ,
- $SVN_APRUTIL_LIBS $SVN_APR_LIBS -lz)
- LDFLAGS="$save_ldflags"])
- CPPFLAGS="$save_cppflags"
- test $serf_found = yes && break
- done
- fi
- ])
+], [serf_found=yes], [AC_MSG_WARN([Serf version too old: need $serf_check_version])
+ serf_found=no])], ,
+ $SVN_APRUTIL_LIBS $SVN_APR_LIBS -lz)
+ LDFLAGS="$save_ldflags"])
+ CPPFLAGS="$save_cppflags"
+ test $serf_found = yes && break
+ done
if test $serf_found = "yes"; then
SVN_SERF_INCLUDES="-I$serf_prefix/include/$serf_major"
@@ -73,9 +124,45 @@
LDFLAGS="$LDFLAGS -L$serf_prefix/lib"
fi
fi
+])
- svn_lib_serf=$serf_found
+dnl SVN_SERF_PKG_CONFIG()
+dnl Use pkg-config to try and detect and configure serf
+AC_DEFUN(SVN_SERF_PKG_CONFIG,
+[
+ AC_MSG_NOTICE([serf library configuration via pkg-config])
+ if test -n "$PKG_CONFIG"; then
+ for serf_major in serf-2 serf-1; do
+ AC_MSG_CHECKING([for $serf_major library])
+ if $PKG_CONFIG $serf_major --exists; then
+ AC_MSG_RESULT([yes])
+ AC_MSG_CHECKING([serf library version])
+ SERF_VERSION=`$PKG_CONFIG $serf_major --modversion`
+ AC_MSG_RESULT([$SERF_VERSION])
+ AC_MSG_CHECKING([serf version is suitable])
+ if $PKG_CONFIG $serf_major --atleast-version=$serf_check_version; then
+ AC_MSG_RESULT([yes])
+ serf_found=yes
+ SVN_SERF_INCLUDES=[`$PKG_CONFIG $serf_major --cflags | $SED -e 's/-D[^ ]*//g'`]
+ SVN_SERF_LIBS=`$PKG_CONFIG $serf_major --libs`
+ else
+ AC_MSG_RESULT([no])
+ AC_MSG_WARN([Serf version too old: need $serf_check_version])
+ fi
+ else
+ AC_MSG_RESULT([no])
+ fi
+ done
+ fi
+])
- AC_SUBST(SVN_SERF_INCLUDES)
- AC_SUBST(SVN_SERF_LIBS)
+dnl SVN_DOWNLOAD_SERF()
+dnl no serf found, print out a message telling the user what to do
+AC_DEFUN(SVN_DOWNLOAD_SERF,
+[
+ echo ""
+ echo "An appropriate version of serf could not be found, so libsvn_ra_serf"
+ echo "will not be built. If you want to build libsvn_ra_serf, please"
+ echo "install serf $serf_check_version or newer."
+ echo ""
])
diff --git a/build/ac-macros/sqlite.m4 b/build/ac-macros/sqlite.m4
index 60da23f..540fcf9 100644
--- a/build/ac-macros/sqlite.m4
+++ b/build/ac-macros/sqlite.m4
@@ -189,10 +189,15 @@
SQLITE_VERSION_OKAY
#endif],
[AC_MSG_RESULT([amalgamation found and is okay])
+ _SVN_SQLITE_DSO_LIBS
AC_DEFINE(SVN_SQLITE_INLINE, 1,
[Defined if svn should use the amalgamated version of sqlite])
SVN_SQLITE_INCLUDES="-I`dirname $sqlite_amalg`"
- SVN_SQLITE_LIBS="-ldl -lpthread"
+ if test -n "$svn_sqlite_dso_ldflags"; then
+ SVN_SQLITE_LIBS="$svn_sqlite_dso_ldflags -lpthread"
+ else
+ SVN_SQLITE_LIBS="-lpthread"
+ fi
svn_lib_sqlite="yes"],
[AC_MSG_RESULT([unsupported amalgamation SQLite version])])
fi
@@ -244,3 +249,50 @@
echo ""
AC_MSG_ERROR([Subversion requires SQLite])
])
+
+dnl _SVN_SQLITE_DSO_LIBS() dnl Find additional libraries that the
+dnl sqlite amalgamation code should link in order to load
+dnl shared libraries. Copied from build/libtool.m4
+AC_DEFUN(_SVN_SQLITE_DSO_LIBS,
+[
+ case $host_os in
+ beos* | mingw* | pw32* | cegcc* | cygwin*)
+ svn_sqlite_dso_ldflags=
+ ;;
+
+ darwin*)
+ # if libdl is installed we need to link against it
+ AC_CHECK_LIB([dl], [dlopen],
+ [lt_cv_dlopen="dlopen" svn_sqlite_dso_ldflags="-ldl"],[
+ svn_sqlite_dso_ldflags=
+ ])
+ ;;
+
+ *)
+ AC_CHECK_FUNC([shl_load],
+ [svn_sqlite_dso_ldflags=],
+ [AC_CHECK_LIB([dld], [shl_load],
+ [svn_sqlite_dso_ldflags="-ldld"],
+ [AC_CHECK_FUNC([dlopen],
+ [svn_sqlite_dso_ldflags=],
+ [AC_CHECK_LIB([dl], [dlopen],
+ [svn_sqlite_dso_ldflags="-ldl"],
+ [AC_CHECK_LIB([svld], [dlopen],
+ [svn_sqlite_dso_ldflags="-lsvld"],
+ [AC_CHECK_LIB([dld], [dld_link],
+ [svn_sqlite_dso_ldflags="-ldld"])
+ ])
+ ])
+ ])
+ ])
+ ])
+ ;;
+ esac
+
+ AC_MSG_CHECKING([additional libraries for sqlite])
+ if test -n "$svn_sqlite_dso_ldflags"; then
+ AC_MSG_RESULT(${svn_sqlite_dso_ldflags})
+ else
+ AC_MSG_RESULT(none)
+ fi
+])
diff --git a/build/generator/gen_base.py b/build/generator/gen_base.py
index 3495377..a3eff86 100644
--- a/build/generator/gen_base.py
+++ b/build/generator/gen_base.py
@@ -300,15 +300,16 @@
globals()[_dt] = _dt
class DependencyNode:
- def __init__(self, filename):
+ def __init__(self, filename, when = None):
self.filename = filename
+ self.when = when
def __str__(self):
return self.filename
class ObjectFile(DependencyNode):
- def __init__(self, filename, compile_cmd = None):
- DependencyNode.__init__(self, filename)
+ def __init__(self, filename, compile_cmd = None, when = None):
+ DependencyNode.__init__(self, filename, when)
self.compile_cmd = compile_cmd
self.source_generated = 0
@@ -362,6 +363,7 @@
self.name = name
self.gen_obj = gen_obj
self.desc = options.get('description')
+ self.when = options.get('when')
self.path = options.get('path', '')
self.add_deps = options.get('add-deps', '')
self.add_install_deps = options.get('add-install-deps', '')
@@ -434,7 +436,7 @@
else:
raise GenError('ERROR: unknown file extension on ' + src)
- ofile = ObjectFile(objname, self.compile_cmd)
+ ofile = ObjectFile(objname, self.compile_cmd, self.when)
# object depends upon source
self.gen_obj.graph.add(DT_OBJECT, ofile, SourceFile(src, reldir))
@@ -554,7 +556,7 @@
else:
raise GenError('ERROR: unknown file extension on ' + src)
- ofile = ObjectFile(objname, self.compile_cmd)
+ ofile = ObjectFile(objname, self.compile_cmd, self.when)
# object depends upon source
self.gen_obj.graph.add(DT_OBJECT, ofile, SourceFile(src, reldir))
@@ -699,7 +701,8 @@
class_pkg_list = self.package.split('.')
class_pkg = build_path_join(*class_pkg_list)
class_file = ObjectFile(build_path_join(self.classes, class_pkg,
- class_name + self.objext))
+ class_name + self.objext),
+ self.when)
class_file.source_generated = 1
class_file.class_name = class_name
hfile = HeaderFile(class_header, self.package + '.' + class_name,
@@ -759,7 +762,7 @@
else:
raise GenError('ERROR: unknown file extension on "' + src + '"')
- ofile = ObjectFile(objname, self.compile_cmd)
+ ofile = ObjectFile(objname, self.compile_cmd, self.when)
sfile = SourceFile(src, reldir)
sfile.sourcepath = sourcepath
@@ -1126,6 +1129,10 @@
# of <>/"" convention.
return hdrs
+class FileInfo:
+ def __init__(self, filename, when):
+ self.filename = filename
+ self.when = when
def _sorted_files(graph, area):
"Given a list of targets, sort them based on their dependencies."
@@ -1163,9 +1170,9 @@
s = graph.get_sources(DT_LINK, t.name)
for d in s:
if d not in targets:
- files.append(d.filename)
+ files.append(FileInfo(d.filename, d.when))
else:
- files.append(t.filename)
+ files.append(FileInfo(t.filename, t.when))
# don't consider this target any more
targets.remove(t)
diff --git a/build/generator/gen_make.py b/build/generator/gen_make.py
index 22ffe6e..c39d3e8 100644
--- a/build/generator/gen_make.py
+++ b/build/generator/gen_make.py
@@ -289,6 +289,7 @@
add_deps=target_ob.add_deps,
objects=objects,
deps=deps,
+ when=target_ob.when,
)
data.target.append(ezt_target)
@@ -375,11 +376,11 @@
def apache_file_to_eztdata(file):
# cd to dirname before install to work around libtool 1.4.2 bug.
- dirname, fname = build_path_splitfile(file)
+ dirname, fname = build_path_splitfile(file.filename)
base, ext = os.path.splitext(fname)
name = base.replace('mod_', '')
- return _eztdata(fullname=file, dirname=dirname,
- name=name, filename=fname)
+ return _eztdata(fullname=file.filename, dirname=dirname,
+ name=name, filename=fname, when=file.when)
if area == 'apache-mod':
data.areas.append(ezt_area)
@@ -396,7 +397,8 @@
# ### TODO: This is a hack. See discussion here:
# ### http://mid.gmane.org/20120316191639.GA28451@daniel3.local
- apache_files = [t.filename for t in inst_targets
+ apache_files = [gen_base.FileInfo(t.filename, t.when)
+ for t in inst_targets
if isinstance(t, gen_base.TargetApacheMod)]
files = [f for f in files if f not in apache_files]
@@ -404,9 +406,9 @@
ezt_area.apache_files.append(apache_file_to_eztdata(file))
for file in files:
# cd to dirname before install to work around libtool 1.4.2 bug.
- dirname, fname = build_path_splitfile(file)
- ezt_file = _eztdata(dirname=dirname, fullname=file,
- filename=fname)
+ dirname, fname = build_path_splitfile(file.filename)
+ ezt_file = _eztdata(dirname=dirname, fullname=file.filename,
+ filename=fname, when=file.when)
if area == 'locale':
lang, objext = os.path.splitext(fname)
installdir = '$(DESTDIR)$(%sdir)/%s/LC_MESSAGES' % (area_var, lang)
@@ -456,6 +458,7 @@
for objname, sources in obj_deps:
dep = _eztdata(name=str(objname),
+ when=objname.when,
deps=list(map(str, sources)),
cmd=objname.compile_cmd,
source=str(sources[0]))
diff --git a/build/generator/gen_win.py b/build/generator/gen_win.py
index f5d2939..f1558ea 100644
--- a/build/generator/gen_win.py
+++ b/build/generator/gen_win.py
@@ -1615,7 +1615,6 @@
"Item class for holding po file info"
def __init__(self, base):
self.po = base + '.po'
- self.spo = base + '.spo'
self.mo = base + '.mo'
# MSVC paths always use backslashes regardless of current platform
diff --git a/build/generator/templates/build_locale.ezt b/build/generator/templates/build_locale.ezt
index 3712bea..ab7d201 100644
--- a/build/generator/templates/build_locale.ezt
+++ b/build/generator/templates/build_locale.ezt
@@ -22,11 +22,8 @@
mkdir ..\..\%1\mo
set exitcode=0
[for pofiles]echo Running msgfmt on [pofiles.po]...
-python ..\..\build\strip-po-charset.py [pofiles.po] [pofiles.spo]
+msgfmt.exe -c -o ..\..\%1\mo\[pofiles.mo] [pofiles.po]
if errorlevel 1 goto err
-msgfmt.exe -c -o ..\..\%1\mo\[pofiles.mo] [pofiles.spo]
-if errorlevel 1 goto err
-del [pofiles.spo]
[end]
goto end
@rem **************************************************************************
diff --git a/build/generator/templates/makefile.ezt b/build/generator/templates/makefile.ezt
index 2e23196..4f37d92 100644
--- a/build/generator/templates/makefile.ezt
+++ b/build/generator/templates/makefile.ezt
@@ -112,7 +112,7 @@
[else][target.varname]_DEPS = [target.add_deps][for target.objects] [target.objects][end][for target.deps] [target.deps][end]
[target.varname]_OBJECTS =[for target.objnames] [target.objnames][end]
[target.filename]: $([target.varname]_DEPS)
- cd [target.path] && [target.link_cmd] $([target.varname]_LDFLAGS) -o [target.basename] [target.undefined_flag] $([target.varname]_OBJECTS)[for target.libs] [target.libs][end] $(LIBS)
+ [if-any target.when]if $([target.when]) ; then [else][end]cd [target.path] && [target.link_cmd] $([target.varname]_LDFLAGS) -o [target.basename] [target.undefined_flag] $([target.varname]_OBJECTS)[for target.libs] [target.libs][end] $(LIBS)[if-any target.when] ; else echo "fake" > [target.filename] ; fi[else][end]
[end][end][end]
########################################
@@ -127,13 +127,13 @@
########################################
[for areas]
[is areas.type "apache-mod"]install-mods-shared:[for areas.files] [areas.files.fullname][end][for areas.files]
- cd [areas.files.dirname] ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n [areas.files.name] [areas.files.filename][end]
+ [if-any areas.files.when]if $([areas.files.when]) ; then [else][end]cd [areas.files.dirname] ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n [areas.files.name] [areas.files.filename][if-any areas.files.when] ; fi[else][end][end]
[else]install-[areas.type]: [for areas.files][if-index areas.files first][else] [end][areas.files.fullname][end] [for areas.apache_files] [areas.apache_files.fullname][end]
$(MKDIR) $(DESTDIR)$([areas.varname]dir)[for areas.files][is areas.type "locale"]
$(MKDIR) [areas.files.installdir]
- cd [areas.files.dirname] ; $(INSTALL_[areas.uppervar]) [areas.files.filename] [areas.files.installdir]/$(PACKAGE_NAME)[areas.files.objext][else]
- cd [areas.files.dirname] ; $(INSTALL_[areas.uppervar]) [areas.files.filename] $(DESTDIR)[areas.files.install_fname][end][end][for areas.apache_files]
- cd [areas.apache_files.dirname] ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n [areas.apache_files.name] [areas.apache_files.filename][end]
+ [if-any areas.files.when]if $([areas.files.when]) ; then [else][end]cd [areas.files.dirname] ; $(INSTALL_[areas.uppervar]) [areas.files.filename] [areas.files.installdir]/$(PACKAGE_NAME)[areas.files.objext][if-any areas.files.when] ; fi[else][end][else]
+ [if-any areas.files.when]if $([areas.files.when]) ; then [else][end]cd [areas.files.dirname] ; $(INSTALL_[areas.uppervar]) [areas.files.filename] $(DESTDIR)[areas.files.install_fname][if-any areas.files.when] ; fi[else][end][end][end][for areas.apache_files]
+ [if-any areas.apache_files.when]if $([areas.apache_files.when]) ; then [else][end]cd [areas.apache_files.dirname] ; $(MKDIR) "$(APACHE_LIBEXECDIR)" ; $(INSTALL_MOD_SHARED) -n [areas.apache_files.name] [areas.apache_files.filename][if-any areas.apache_files.when] ; fi[else][end][end]
[if-any areas.extra_install] $(INSTALL_EXTRA_[areas.uppervar])
[end][end][end]
@@ -156,5 +156,5 @@
########################################
[for deps]
[deps.name]:[for deps.deps] [deps.deps][end][if-any deps.cmd]
- [deps.cmd] [if-any deps.generated][else]$(canonicalized_srcdir)[end][deps.source][end]
+ [if-any deps.when]if $([deps.when]) ; then [else][end][deps.cmd] [if-any deps.generated][else]$(canonicalized_srcdir)[end][deps.source][end][if-any deps.when] ; else echo "fake" > [deps.name] ; fi[else][end]
[end]
diff --git a/build/generator/templates/svn_locale.vcproj.ezt b/build/generator/templates/svn_locale.vcproj.ezt
index b7caef2..925ffd7 100644
--- a/build/generator/templates/svn_locale.vcproj.ezt
+++ b/build/generator/templates/svn_locale.vcproj.ezt
@@ -31,9 +31,9 @@
<Configurations>
[for platforms][for configs] <Configuration
Name="[configs]|[platforms]"
- OutputDirectory="..\..\[configs]\mo"
+ OutputDirectory="..\..\..\[configs]\mo"
BuildLogFile="$(IntDir)\BuildLog_$(ProjectName).htm"
- IntermediateDirectory="..\..\[configs]\mo"
+ IntermediateDirectory="..\..\..\[configs]\mo"
ConfigurationType="0"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="FALSE">
diff --git a/build/generator/templates/svn_locale.vcxproj.ezt b/build/generator/templates/svn_locale.vcxproj.ezt
index 33dc68d..323e8ba 100644
--- a/build/generator/templates/svn_locale.vcxproj.ezt
+++ b/build/generator/templates/svn_locale.vcxproj.ezt
@@ -32,7 +32,7 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
[for platforms][for configs] <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'" Label="Configuration">
- <ConfigurationType>Utility</ConfigurationType>
+ <ConfigurationType>Makefile</ConfigurationType>
<CLRSupport>false</CLRSupport>
<PlatformToolset>[toolset_version]</PlatformToolset>
</PropertyGroup>
@@ -44,8 +44,8 @@
</ImportGroup>
[end][end] <PropertyGroup Label="UserMacros" />
<PropertyGroup>
-[for configs][for platforms] <OutDir Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">..\..\[configs]\mo\</OutDir>
- <IntDir Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">..\..\[configs]\mo\</IntDir>
+[for configs][for platforms] <OutDir Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">..\..\..\[configs]\mo\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">..\..\..\[configs]\mo\</IntDir>
<NMakeBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">cmd /c build_locale.bat [configs]</NMakeBuildCommandLine>
<NMakeReBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">cmd /c build_locale.bat [configs]</NMakeReBuildCommandLine>
<NMakeCleanCommandLine Condition="'$(Configuration)|$(Platform)'=='[configs]|[platforms]'">cmd /c del $(OutDir)*.mo</NMakeCleanCommandLine>
diff --git a/build/run_tests.py b/build/run_tests.py
index d591592..ace6ec5 100755
--- a/build/run_tests.py
+++ b/build/run_tests.py
@@ -45,7 +45,7 @@
# A few useful constants
SVN_VER_MINOR = 8
-import os, re, subprocess, sys, imp
+import os, re, subprocess, sys, imp, threading
from datetime import datetime
import getopt
@@ -317,6 +317,12 @@
print("WARNING: no failures, but '%s' exists from a previous run."
% self.faillogfile)
+ # Summary.
+ if failed or xpassed or failed_list:
+ print("SUMMARY: Some tests failed.\n")
+ else:
+ print("SUMMARY: All tests successful.\n")
+
self._close_log()
return failed
@@ -377,12 +383,13 @@
# This has to be class-scoped for use in the progress_func()
self.dots_written = 0
def progress_func(completed):
+ if not self.log or self.dots_written >= dot_count:
+ return
dots = (completed * dot_count) / total
-
+ if dots > dot_count:
+ dots = dot_count
dots_to_write = dots - self.dots_written
- if self.log:
- os.write(sys.stdout.fileno(), '.' * dots_to_write)
-
+ os.write(sys.stdout.fileno(), '.' * dots_to_write)
self.dots_written = dots
tests_completed = 0
@@ -478,21 +485,28 @@
sys.stdout.flush()
sys.stderr.flush()
self.log.flush()
- old_stdout = os.dup(1)
- old_stderr = os.dup(2)
- os.dup2(self.log.fileno(), 1)
- os.dup2(self.log.fileno(), 2)
+ old_stdout = os.dup(sys.stdout.fileno())
+ old_stderr = os.dup(sys.stderr.fileno())
+ os.dup2(self.log.fileno(), sys.stdout.fileno())
+ os.dup2(self.log.fileno(), sys.stderr.fileno())
- # This has to be class-scoped for use in the progress_func()
+ # These have to be class-scoped for use in the progress_func()
self.dots_written = 0
+ self.progress_lock = threading.Lock()
def progress_func(completed, total):
+ """Report test suite progress. Can be called from multiple threads
+ in parallel mode."""
+ if not self.log:
+ return
dots = (completed * dot_count) / total
-
- dots_to_write = dots - self.dots_written
- if self.log:
+ if dots > dot_count:
+ dots = dot_count
+ self.progress_lock.acquire()
+ if self.dots_written < dot_count:
+ dots_to_write = dots - self.dots_written
+ self.dots_written = dots
os.write(old_stdout, '.' * dots_to_write)
-
- self.dots_written = dots
+ self.progress_lock.release()
serial_only = hasattr(prog_mod, 'serial_only') and prog_mod.serial_only
@@ -525,8 +539,8 @@
if self.log:
sys.stdout.flush()
sys.stderr.flush()
- os.dup2(old_stdout, 1)
- os.dup2(old_stderr, 2)
+ os.dup2(old_stdout, sys.stdout.fileno())
+ os.dup2(old_stderr, sys.stderr.fileno())
os.close(old_stdout)
os.close(old_stderr)
diff --git a/build/strip-po-charset.py b/build/strip-po-charset.py
deleted file mode 100755
index 43c5e5f..0000000
--- a/build/strip-po-charset.py
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env python
-#
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-#
-#
-# strip-po-charset.py
-#
-
-import sys
-
-def strip_po_charset(inp, out):
-
- out.write(inp.read().replace("\"Content-Type: text/plain; charset=UTF-8\\n\"\n",""))
-
-def main():
-
- if len(sys.argv) != 3:
- print("Usage: %s <input (po) file> <output (spo) file>" % sys.argv[0])
- print("")
- print("Unsupported number of arguments; 2 required.")
- sys.exit(1)
-
- strip_po_charset(open(sys.argv[1],'r'), open(sys.argv[2],'w'))
-
-if __name__ == '__main__':
- main()
diff --git a/configure.ac b/configure.ac
index b6a92e1..04996f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -479,8 +479,9 @@
[Defined if Cyrus SASL v2 is present on the system])
fi
-dnl Mac OS soecufuc features -------------------
+dnl Mac OS specific features -------------------
+SVN_LIB_MACHO_ITERATE
SVN_LIB_MACOS_PLIST
SVN_LIB_MACOS_KEYCHAIN
@@ -501,7 +502,7 @@
CPPFLAGS="$old_CPPFLAGS"
-dnl D-Bus (required for support for KWallet and GNOME Keyring) -------------------
+dnl D-Bus (required for support for KWallet) -------------------
if test -n "$PKG_CONFIG"; then
AC_MSG_CHECKING([for D-Bus .pc file])
@@ -544,9 +545,7 @@
[Disable support for GPG-Agent]),
[], [with_gpg_agent=yes])
AC_MSG_CHECKING([whether to support GPG-Agent])
-if test "$svn_enable_shared" != "yes"; then
- AC_MSG_RESULT([no (shared library support is disabled)])
-elif test "$with_gpg_agent" = "yes"; then
+if test "$with_gpg_agent" = "yes"; then
AC_MSG_RESULT([yes])
AC_DEFINE([SVN_HAVE_GPG_AGENT], [1],
[Is GPG Agent support enabled?])
@@ -560,50 +559,65 @@
AC_ARG_WITH(gnome_keyring,
AS_HELP_STRING([--with-gnome-keyring],
- [Enable use of GNOME Keyring for auth credentials]),
+ [Enable use of GNOME Keyring for auth credentials (enabled by default if found)]),
[with_gnome_keyring="$withval"],
- [with_gnome_keyring=no])
+ [with_gnome_keyring=auto])
+found_gnome_keyring=no
AC_MSG_CHECKING([whether to look for GNOME Keyring])
if test "$with_gnome_keyring" != "no"; then
AC_MSG_RESULT([yes])
if test "$svn_enable_shared" = "yes"; then
if test "$APR_HAS_DSO" = "yes"; then
if test -n "$PKG_CONFIG"; then
- if test "$HAVE_DBUS" = "yes"; then
- AC_MSG_CHECKING([for GLib and GNOME Keyring .pc files])
- if $PKG_CONFIG --exists glib-2.0 gnome-keyring-1; then
+ AC_MSG_CHECKING([for GLib and GNOME Keyring .pc files])
+ if $PKG_CONFIG --exists glib-2.0 gnome-keyring-1; then
+ AC_MSG_RESULT([yes])
+ old_CPPFLAGS="$CPPFLAGS"
+ SVN_GNOME_KEYRING_INCLUDES="`$PKG_CONFIG --cflags glib-2.0 gnome-keyring-1`"
+ CPPFLAGS="$CPPFLAGS $SVN_GNOME_KEYRING_INCLUDES"
+ AC_CHECK_HEADER(gnome-keyring.h, found_gnome_keyring=yes, found_gnome_keyring=no)
+ AC_MSG_CHECKING([for GNOME Keyring])
+ if test "$found_gnome_keyring" = "yes"; then
AC_MSG_RESULT([yes])
- old_CPPFLAGS="$CPPFLAGS"
- SVN_GNOME_KEYRING_INCLUDES="$DBUS_CPPFLAGS `$PKG_CONFIG --cflags glib-2.0 gnome-keyring-1`"
- CPPFLAGS="$CPPFLAGS $SVN_GNOME_KEYRING_INCLUDES"
- AC_CHECK_HEADER(gnome-keyring.h, with_gnome_keyring=yes, with_gnome_keyring=no)
- AC_MSG_CHECKING([for GNOME Keyring])
- if test "$with_gnome_keyring" = "yes"; then
- AC_MSG_RESULT([yes])
- AC_DEFINE([SVN_HAVE_GNOME_KEYRING], [1],
- [Is GNOME Keyring support enabled?])
- CPPFLAGS="$old_CPPFLAGS"
- SVN_GNOME_KEYRING_LIBS="$DBUS_LIBS `$PKG_CONFIG --libs glib-2.0 gnome-keyring-1`"
- else
- AC_MSG_RESULT([no])
- AC_MSG_ERROR([cannot find GNOME Keyring])
- fi
+ AC_DEFINE([SVN_HAVE_GNOME_KEYRING], [1],
+ [Is GNOME Keyring support enabled?])
+ CPPFLAGS="$old_CPPFLAGS"
+ SVN_GNOME_KEYRING_LIBS="`$PKG_CONFIG --libs glib-2.0 gnome-keyring-1`"
else
AC_MSG_RESULT([no])
- AC_MSG_ERROR([cannot find GLib and GNOME Keyring .pc files])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([cannot find GNOME Keyring])
+ fi
fi
else
- AC_MSG_ERROR([cannot find D-Bus])
+ AC_MSG_RESULT([no])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([cannot find GLib and GNOME Keyring .pc files.])
+ else
+ with_gnome_keyring=no
+ fi
fi
else
- AC_MSG_ERROR([cannot find pkg-config])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([cannot find pkg-config. GNOME Keyring requires this.])
+ else
+ with_gnome_keyring=no
+ fi
fi
else
- AC_MSG_ERROR([APR does not have support for DSOs])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([APR does not have support for DSOs. GNOME Keyring requires this.])
+ else
+ with_gnome_keyring=no
+ fi
fi
else
- AC_MSG_ERROR([--with-gnome-keyring conflicts with --disable-shared])
+ if test "$with_gnome_keyring" = "yes"; then
+ AC_MSG_ERROR([--with-gnome-keyring conflicts with --disable-shared])
+ else
+ with_gnome_keyring=no
+ fi
fi
else
AC_MSG_RESULT([no])
@@ -815,7 +829,7 @@
INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-kwallet-lib"
fi
-if test "$with_gnome_keyring" = "yes"; then
+if test "$found_gnome_keyring" = "yes"; then
BUILD_RULES="$BUILD_RULES gnome-keyring-lib"
INSTALL_RULES="`echo $INSTALL_RULES | $SED 's/install-lib/install-lib install-gnome-keyring-lib/'`"
INSTALL_STATIC_RULES="$INSTALL_STATIC_RULES install-gnome-keyring-lib"
@@ -1003,6 +1017,11 @@
CFLAGS="-Wno-system-headers $CFLAGS_KEEP"
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[]])], [CFLAGS_KEEP="$CFLAGS"])
+ dnl Put this flag behind -Wall:
+
+ CFLAGS="$CFLAGS_KEEP -Wno-format-nonliteral"
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[]])], [CFLAGS_KEEP="$CFLAGS"])
+
AC_LANG_POP([C])
CFLAGS="$CFLAGS_KEEP"
fi
diff --git a/contrib/client-side/emacs/dsvn.el b/contrib/client-side/emacs/dsvn.el
index 0eed9d0..521315b 100644
--- a/contrib/client-side/emacs/dsvn.el
+++ b/contrib/client-side/emacs/dsvn.el
@@ -360,21 +360,55 @@
(append svn-diff-args (split-string arg))
'diff-mode))
+(defun svn-add-unversioned-files-p (files)
+ "Ask the user whether FILES should be added; return the answer."
+ (let ((buf (get-buffer-create "*svn-unversioned-files*")))
+ (with-current-buffer buf
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (insert (mapconcat (lambda (f) f) files "\n"))
+ (setq buffer-read-only t)
+ (goto-char (point-min))
+ (save-selected-window
+ (pop-to-buffer buf)
+ (shrink-window-if-larger-than-buffer)
+ (let* ((n (length files))
+ (add-them (y-or-n-p
+ (if (= n 1)
+ "Add this item first? "
+ (format "Add these %d items first? " n)))))
+ (let ((win (get-buffer-window buf)))
+ (if win
+ (condition-case nil
+ (delete-window win)
+ (error nil))))
+ (bury-buffer buf)
+ add-them)))))
+
(defun svn-commit ()
"Commit changes to one or more files."
(interactive)
(save-some-buffers)
- (let ((status-buf (current-buffer))
- (commit-buf (get-buffer-create "*svn commit*"))
- (window-conf (and svn-restore-windows (current-window-configuration)))
- (listfun (lambda () (with-current-buffer log-edit-parent-buffer
- (svn-action-files)))))
- (log-edit 'svn-confirm-commit t
- (if (< emacs-major-version 23)
- listfun
- (list (cons 'log-edit-listfun listfun)))
- commit-buf)
- (set (make-local-variable 'saved-window-configuration) window-conf)))
+ (let ((unversioned-files (svn-action-files
+ (lambda (pos) (eq (svn-file-status pos) ?\?)))))
+ (if unversioned-files
+ (if (svn-add-unversioned-files-p unversioned-files)
+ (progn
+ (message "Adding unversioned items. Please re-commit when ready.")
+ (svn-run 'add unversioned-files "Adding files"))
+ (message "Files not added; nothing committed."))
+ (let ((status-buf (current-buffer))
+ (commit-buf (get-buffer-create "*svn commit*"))
+ (window-conf (and svn-restore-windows
+ (current-window-configuration)))
+ (listfun (lambda () (with-current-buffer log-edit-parent-buffer
+ (svn-action-files)))))
+ (log-edit 'svn-confirm-commit t
+ (if (< emacs-major-version 23)
+ listfun
+ (list (cons 'log-edit-listfun listfun)))
+ commit-buf)
+ (set (make-local-variable 'saved-window-configuration) window-conf)))))
(defun svn-confirm-commit ()
"Commit changes with the current buffer as commit message."
@@ -1643,27 +1677,24 @@
"Return a list of lists (FILE POS) to act on.
Optional argument PRED is a predicate function that is called with POS as
argument."
- (let ((files ())
- (pos (next-single-property-change (point-min) 'svn-file)))
- (while pos
- (when (and (get-text-property pos 'svn-mark)
- (or (not pred)
- (funcall pred pos)))
- (setq files (cons (list (get-text-property pos 'svn-file)
- pos)
- files)))
- (setq pos (next-single-property-change pos 'svn-file)))
- (if (null files)
- (let ((file (svn-getprop (point) 'file)))
- (unless file
- (error "No file on this line"))
- (when (and pred
- (not (funcall pred (line-beginning-position))))
- (error "Invalid file"))
- (list (list file
- (save-excursion
- (beginning-of-line)
- (point)))))
+ (let ((positions ()))
+ (let ((pos (next-single-property-change (point-min) 'svn-file)))
+ (while pos
+ (when (get-text-property pos 'svn-mark)
+ (setq positions (cons pos positions)))
+ (setq pos (next-single-property-change pos 'svn-file))))
+ (when (null positions)
+ (unless (svn-getprop (point) 'file)
+ (error "No file on this line"))
+ (setq positions (list (line-beginning-position))))
+
+ (let ((files ()))
+ (mapc (lambda (pos)
+ (when (or (not pred) (funcall pred pos))
+ (setq files (cons (list (get-text-property pos 'svn-file)
+ pos)
+ files))))
+ (reverse positions))
(reverse files))))
(defun svn-action-files (&optional pred)
diff --git a/contrib/server-side/fsfsfixer/fix-repo b/contrib/server-side/fsfsfixer/fix-repo
index 8c49089..e679df8 100755
--- a/contrib/server-side/fsfsfixer/fix-repo
+++ b/contrib/server-side/fsfsfixer/fix-repo
@@ -5,6 +5,7 @@
Backup your repository before running these scripts."
+THIS_DIR=`dirname "$0"`
REPO_DIR="$1"
START_REV="$2"
@@ -13,7 +14,7 @@
exit 1
fi
-YOUNGEST="$(svnlook youngest "$REPO_DIR")"
+YOUNGEST=`svnlook youngest "$REPO_DIR"`
if [ "$YOUNGEST" = "" ]; then
echo "$0: error running 'svnlook youngest $REPO_DIR'"
@@ -25,6 +26,6 @@
REV=$START_REV
while [ $REV -le $YOUNGEST ]; do
echo "=== r$REV"
- ./fixer/fix-rev.py "$REPO_DIR" "$REV"
+ "$THIS_DIR"/fixer/fix-rev.py "$REPO_DIR" "$REV"
REV=`expr $REV + 1`
done
diff --git a/notes/directory-index/dirindex.py b/notes/directory-index/dirindex.py
index 3b83156..b143e75 100644
--- a/notes/directory-index/dirindex.py
+++ b/notes/directory-index/dirindex.py
@@ -42,7 +42,6 @@
def __init__(self):
import cStringIO
import pkgutil
- import re
comment_rx = re.compile(r"\s*--.*$")
header_rx = re.compile(r"^---(?P<kind>STATEMENT|SCRIPT)"
@@ -215,7 +214,6 @@
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
@@ -253,49 +251,6 @@
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):
- 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 _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 _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 NodeRev(SQLobject):
"""O/R mapping for the noderev/string/nodeview table."""
@@ -330,8 +285,8 @@
self.state = self.TRANSIENT
def __str__(self):
- return "%d %c %s%s" % (self.treeid, self.opcode, self.name,
- self._isdir and '/' or '')
+ return "%d(%d) %c %s%s" % (self.id, self.treeid, self.opcode,
+ self.name, self._isdir and '/' or '')
# Opcode names
__opnames = {ADD: "add",
@@ -384,6 +339,10 @@
assert self.dename is not None
self.denameid = self.__stringid(cursor, self.dename)
super(NodeRev, self)._put(cursor)
+ if self.nodeid is None:
+ self.nodeid = self.id
+ if self.branch is None:
+ self.branch = self.id
@classmethod
def _update_treeid(cls, cursor, new_txn, old_txn):
@@ -472,10 +431,6 @@
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:
@@ -501,17 +456,13 @@
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.branch = None
node._put(cursor)
return node
@@ -519,7 +470,9 @@
assert txn._uncommitted
noderev = NodeRev._clone(self)
noderev.treeid = txn.treeid
+ noderev.origin = self.id
noderev.opcode = opcode
+ return noderev
__readonly = frozenset(("name",))
def __setitem__(self, key, value):
@@ -528,7 +481,10 @@
if key == "dename":
name = self.__normtext(value)
value = self.__text(value)
- super(NodeRev, self).__setitem__("name", name)
+ if name != self.name:
+ super(NodeRev, self).__setitem__("name", name)
+ super(NodeRev, self).__setitem__("nameid", None)
+ super(NodeRev, self).__setitem__("denameid", None)
super(NodeRev, self).__setitem__(key, value)
def __getitem__(self, key):
@@ -680,12 +636,10 @@
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):
@@ -743,11 +697,9 @@
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,
+ nodeid = None,
+ branch = None,
parent = parent.id,
kind = kind,
opcode = opcode)
@@ -777,6 +729,45 @@
index.rollback()
+__greek_tree = {
+ 'iota': 'file',
+ 'A': {
+ 'mu': 'file',
+ 'B': {
+ 'lambda': 'file',
+ 'E': {
+ 'alpha': 'file',
+ 'beta': 'file'},
+ 'F': 'dir'},
+ 'C': 'dir',
+ 'D': {
+ 'G': {
+ 'pi': 'file',
+ 'rho': 'file',
+ 'tau': 'file'},
+ 'H': {
+ 'chi': 'file',
+ 'psi': 'file',
+ 'omega': 'file'}
+ }
+ }
+ }
+def greektree(ix, tx):
+ def populate(track, items):
+ print 'Populating', track
+ for name, kind in items.iteritems():
+ if kind == 'file':
+ node = ix.add(tx, track, name, NodeRev.FILE)
+ else:
+ node = ix.add(tx, track, name, NodeRev.DIR)
+ print 'Added', node, 'node:', node.noderev
+ if isinstance(kind, dict):
+ populate(node, kind)
+
+ root = ix.lookup(tx)
+ populate(root, __greek_tree)
+
+
def simpletest(database):
ix = Index(database)
ix.initialize()
@@ -789,37 +780,34 @@
print "root track:", root
print "root noderev", root.noderev
- print "Add A/foo"
+ print 'Create greek tree'
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
+ greektree(ix, tx)
ix.commit_txn(tx, 1)
ix.commit()
+
+ def listdir(noderev, prefix):
+ for n in ix.listdir(tx, noderev):
+ print prefix, str(n)
+ if n._isdir:
+ listdir(n, prefix + " ")
+
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)
+ listdir(root.noderev, " ")
- print "Lookup A"
- track = ix.lookup(tx, None, "A")
- print str(track.noderev)
+ print "Lookup iota"
+ track = ix.lookup(tx, None, "iota")
+ print str(track), str(track.noderev)
- print "Lookup A/foo"
- track = ix.lookup(tx, None, "A/foo")
- print str(track.noderev)
+ print "Lookup A/D/H/psi"
+ track = ix.lookup(tx, None, "A/D/H/psi")
+ print str(track), str(track.noderev)
finally:
ix.close()
diff --git a/notes/directory-index/schema.sql b/notes/directory-index/schema.sql
index c52d213..0d72e3b 100644
--- a/notes/directory-index/schema.sql
+++ b/notes/directory-index/schema.sql
@@ -22,7 +22,6 @@
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;
@@ -61,36 +60,10 @@
CREATE INDEX txn_revision_idx ON txn(revision);
-
--- Branches -- unique forks in the nodes' history
-CREATE TABLE branch (
- -- branch identifier
- id integer NOT NULL PRIMARY KEY,
-
- -- the transaction in which the branch was created
- treeid integer NOT NULL REFERENCES txn(id),
-
- -- the node to which this branch belongs; refers to the initial
- -- branch of the node
- nodeid integer NULL REFERENCES branch(id),
-
- -- the source branch from which this branch was forked
- origin integer NULL REFERENCES branch(id),
-
- -- 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',
-
- -- sanity check: enumerated value validation
- CONSTRAINT enumeration_validation CHECK (state IN ('T', 'P')),
-
- -- sanity check: ye can't be yer own daddy
- CONSTRAINT genetic_diversity CHECK (id <> origin)
-);
-
-CREATE INDEX branch_txn_idx ON branch(treeid);
-CREATE INDEX branch_node_idx ON branch(nodeid);
+CREATE TRIGGER txn_ensure_treeid AFTER INSERT ON txn
+BEGIN
+ UPDATE txn SET treeid = NEW.id WHERE treeid IS NULL AND id = NEW.id;
+END;
-- File names -- lookup table of strings
@@ -108,18 +81,19 @@
-- 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),
+ -- the node identifier
+ -- a new node will get the ID of its initial noderev.id
+ nodeid integer NULL REFERENCES noderev(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),
+ parent integer NULL REFERENCES noderev(id),
-- the branch that this node revision belongs to -- history graph
- branch integer NOT NULL REFERENCES branch(id),
+ -- a new branch will get the ID of its initial noderev.id
+ branch integer NULL REFERENCES noderev(id),
-- the indexable, NFC-normalized name of this noderev within its parent
nameid integer NOT NULL REFERENCES string(id),
@@ -171,8 +145,15 @@
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_branch_idx ON noderev(branch);
CREATE INDEX noderev_successor_idx ON noderev(origin);
+CREATE TRIGGER noderev_ensure_node_and_branch AFTER INSERT ON noderev
+BEGIN
+ UPDATE noderev SET nodeid = NEW.id WHERE nodeid IS NULL AND id = NEW.id;
+ UPDATE noderev SET branch = NEW.id WHERE branch IS NULL AND id = NEW.id;
+END;
+
CREATE VIEW nodeview AS
SELECT
@@ -188,7 +169,6 @@
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)
@@ -199,9 +179,6 @@
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;
@@ -230,28 +207,6 @@
---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);
@@ -273,9 +228,6 @@
---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;
@@ -309,7 +261,8 @@
---STATEMENT NODEVIEW_LIST_DIRECTORY
SELECT * FROM nodeview
JOIN (SELECT nameid, MAX(treeid) AS treeid FROM noderev
- WHERE treeid <= :treeid AND state = 'P') AS filter
+ WHERE treeid <= :treeid AND state = 'P'
+ GROUP BY nameid) AS filter
ON nodeview.nameid = filter.nameid AND nodeview.treeid = filter.treeid
WHERE parent = :parent AND opcode <> 'D'
ORDER BY nodeview.name ASC;
@@ -317,7 +270,8 @@
---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
+ WHERE treeid < :treeid AND state = 'P' OR treeid = :treeid
+ GROUP BY nameid) AS filter
ON nodeview.nameid = filter.name AND nodeview.treeid = filter.treeid
WHERE parent = :parent AND opcode <> 'D'
ORDER BY nodeview.name ASC;
diff --git a/notes/fsfs b/notes/fsfs
index 00e7120..ea880b5 100644
--- a/notes/fsfs
+++ b/notes/fsfs
@@ -234,6 +234,16 @@
new revision which wasn't copied, or which was only partially
populated when it was copied.
+[ Update: as of 1.6, FSFS uses an optional SQLite DB, rep-cache.db, when
+rep-sharing is enabled. SQLite provides no guarantee that copying live
+databases will result in copies that are uncorrupt, or that are corrupt but
+will raise an error when accessed. 'svnadmin hotcopy' avoids the problem by
+establishing an appropriate SQLite lock (see svn_sqlite__hotcopy()). User
+code should either use an atomic filesystem snapshot (as with zfs/LVM),
+refrain from copying rep-cache.db, or stop all access to that file before
+copying it (for example, by disabling commits, by establishing a lock a la
+svn_sqlite__hotcopy(), or by using 'svnadmin freeze'). ]
+
The "svnadmin hotcopy" command avoids this problem by copying the
"current" file before copying the revision files. But a backup using
the hotcopy command isn't as efficient as a straight incremental
diff --git a/subversion/bindings/javahl/native/BlameCallback.cpp b/subversion/bindings/javahl/native/BlameCallback.cpp
index f0f84ad..5973707 100644
--- a/subversion/bindings/javahl/native/BlameCallback.cpp
+++ b/subversion/bindings/javahl/native/BlameCallback.cpp
@@ -61,11 +61,9 @@
apr_pool_t *pool)
{
if (baton)
- return ((BlameCallback *)baton)->singleLine(start_revnum, end_revnum,
- line_no, revision, rev_props,
- merged_revision,
- merged_rev_props, merged_path,
- line, local_change, pool);
+ return static_cast<BlameCallback *>(baton)->singleLine(start_revnum,
+ end_revnum, line_no, revision, rev_props, merged_revision,
+ merged_rev_props, merged_path, line, local_change, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/ChangelistCallback.cpp b/subversion/bindings/javahl/native/ChangelistCallback.cpp
index 5a9c9fc..d04ba32 100644
--- a/subversion/bindings/javahl/native/ChangelistCallback.cpp
+++ b/subversion/bindings/javahl/native/ChangelistCallback.cpp
@@ -53,7 +53,8 @@
apr_pool_t *pool)
{
if (baton)
- ((ChangelistCallback *)baton)->doChangelist(path, changelist, pool);
+ static_cast<ChangelistCallback *>(baton)->doChangelist(path, changelist,
+ pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/ClientContext.cpp b/subversion/bindings/javahl/native/ClientContext.cpp
index 38c8ed3..d9edb6a 100644
--- a/subversion/bindings/javahl/native/ClientContext.cpp
+++ b/subversion/bindings/javahl/native/ClientContext.cpp
@@ -71,7 +71,8 @@
env->DeleteLocalRef(jctx);
- SVN_JNI_ERR(svn_client_create_context(&m_context, pool.getPool()),
+ SVN_JNI_ERR(svn_client_create_context2(&m_context, NULL,
+ pool.getPool()),
);
/* Clear the wc_ctx as we don't want to maintain this unconditionally
@@ -323,7 +324,7 @@
svn_error_t *
ClientContext::checkCancel(void *cancelBaton)
{
- ClientContext *that = (ClientContext *)cancelBaton;
+ ClientContext *that = static_cast<ClientContext *>(cancelBaton);
if (that->m_cancelOperation)
return svn_error_create(SVN_ERR_CANCELLED, NULL,
_("Operation cancelled"));
diff --git a/subversion/bindings/javahl/native/CommitCallback.cpp b/subversion/bindings/javahl/native/CommitCallback.cpp
index e765a04..2ffe326 100644
--- a/subversion/bindings/javahl/native/CommitCallback.cpp
+++ b/subversion/bindings/javahl/native/CommitCallback.cpp
@@ -57,7 +57,7 @@
apr_pool_t *pool)
{
if (baton)
- return ((CommitCallback *)baton)->commitInfo(commit_info, pool);
+ return static_cast<CommitCallback *>(baton)->commitInfo(commit_info, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/CommitMessage.cpp b/subversion/bindings/javahl/native/CommitMessage.cpp
index 4f8db5f..a947f97 100644
--- a/subversion/bindings/javahl/native/CommitMessage.cpp
+++ b/subversion/bindings/javahl/native/CommitMessage.cpp
@@ -50,9 +50,9 @@
void *baton,
apr_pool_t *pool)
{
- if (baton && ((CommitMessage *)baton)->m_jcommitMessage)
- return ((CommitMessage *)baton)->getCommitMessage(log_msg, tmp_file,
- commit_items, pool);
+ if (baton && static_cast<CommitMessage *>(baton)->m_jcommitMessage)
+ return static_cast<CommitMessage *>(baton)->getCommitMessage(
+ log_msg, tmp_file, commit_items, pool);
*log_msg = NULL;
*tmp_file = NULL;
diff --git a/subversion/bindings/javahl/native/DiffSummaryReceiver.cpp b/subversion/bindings/javahl/native/DiffSummaryReceiver.cpp
index d9173e2..f4cf052 100644
--- a/subversion/bindings/javahl/native/DiffSummaryReceiver.cpp
+++ b/subversion/bindings/javahl/native/DiffSummaryReceiver.cpp
@@ -46,7 +46,7 @@
apr_pool_t *pool)
{
if (baton)
- return ((DiffSummaryReceiver *) baton)->onSummary(diff, pool);
+ return static_cast<DiffSummaryReceiver *>(baton)->onSummary(diff, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/ImportFilterCallback.cpp b/subversion/bindings/javahl/native/ImportFilterCallback.cpp
new file mode 100644
index 0000000..965dbb0
--- /dev/null
+++ b/subversion/bindings/javahl/native/ImportFilterCallback.cpp
@@ -0,0 +1,118 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file ImportFilterCallback.cpp
+ * @brief Implementation of the class ImportFilterCallback
+ */
+
+#include "ImportFilterCallback.h"
+#include "EnumMapper.h"
+#include "CreateJ.h"
+#include "JNIUtil.h"
+#include "svn_time.h"
+
+/**
+ * Create a ImportFilterCallback object
+ * @param jcallback the Java callback object.
+ */
+ImportFilterCallback::ImportFilterCallback(jobject jcallback)
+{
+ m_callback = jcallback;
+}
+
+/**
+ * Destroy a ImportFilterCallback object
+ */
+ImportFilterCallback::~ImportFilterCallback()
+{
+ // The m_callback does not need to be destroyed, because it is the passed
+ // in parameter to the Java SVNClient.list method.
+}
+
+svn_error_t *
+ImportFilterCallback::callback(void *baton,
+ svn_boolean_t *filtered,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ apr_pool_t *pool)
+{
+ if (baton)
+ return static_cast<ImportFilterCallback *>(baton)->doImportFilter(
+ filtered, local_abspath, dirent, pool);
+
+ return SVN_NO_ERROR;
+}
+
+/**
+ * Callback called for each directory entry.
+ */
+svn_error_t *
+ImportFilterCallback::doImportFilter(svn_boolean_t *filtered,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ apr_pool_t *pool)
+{
+ JNIEnv *env = JNIUtil::getEnv();
+
+ // Create a local frame for our references
+ env->PushLocalFrame(LOCAL_FRAME_SIZE);
+ if (JNIUtil::isJavaExceptionThrown())
+ return SVN_NO_ERROR;
+
+ // The method id will not change during the time this library is
+ // loaded, so it can be cached.
+ static jmethodID mid = 0;
+ if (mid == 0)
+ {
+ jclass clazz = env->FindClass(JAVA_PACKAGE"/callback/ImportFilterCallback");
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN(SVN_NO_ERROR);
+
+ mid = env->GetMethodID(clazz, "filter",
+ "(Ljava/lang/String;"
+ "L"JAVA_PACKAGE"/types/NodeKind;Z)Z");
+ if (JNIUtil::isJavaExceptionThrown() || mid == 0)
+ POP_AND_RETURN(SVN_NO_ERROR);
+ }
+
+ // convert the parameters to their Java relatives
+ jstring jpath = JNIUtil::makeJString(local_abspath);
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN(SVN_NO_ERROR);
+
+ jboolean jspecial = (dirent->special ? JNI_TRUE : JNI_FALSE);
+
+ jobject jkind = EnumMapper::mapNodeKind(dirent->kind);
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN(SVN_NO_ERROR);
+
+ // call the Java method
+ jboolean jfilter = env->CallBooleanMethod(m_callback, mid, jpath, jkind,
+ jspecial);
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN(SVN_NO_ERROR);
+
+ *filtered = jfilter ? TRUE : FALSE;
+
+ env->PopLocalFrame(NULL);
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/bindings/javahl/native/ImportFilterCallback.h b/subversion/bindings/javahl/native/ImportFilterCallback.h
new file mode 100644
index 0000000..4e5c152
--- /dev/null
+++ b/subversion/bindings/javahl/native/ImportFilterCallback.h
@@ -0,0 +1,62 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file ImportFilterCallback.h
+ * @brief Interface of the class ImportFilterCallback
+ */
+
+#ifndef IMPORT_FILTER_CALLBACK_H
+#define IMPORT_FILTER_CALLBACK_H
+
+#include <jni.h>
+#include "svn_client.h"
+
+/**
+ * This class holds a Java callback object, which will receive every
+ * directory entry for which the callback information is requested.
+ */
+class ImportFilterCallback
+{
+public:
+ ImportFilterCallback(jobject jcallback);
+ ~ImportFilterCallback();
+
+ static svn_error_t *callback(void *baton,
+ svn_boolean_t *filtered,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ apr_pool_t *scratch_pool);
+
+protected:
+ svn_error_t *doImportFilter(svn_boolean_t *filtered,
+ const char *local_abspath,
+ const svn_io_dirent2_t *dirent,
+ apr_pool_t *pool);
+
+private:
+ /**
+ * This a local reference to the Java object.
+ */
+ jobject m_callback;
+};
+
+#endif // IMPORT_FILTER_CALLBACK_H
diff --git a/subversion/bindings/javahl/native/InfoCallback.cpp b/subversion/bindings/javahl/native/InfoCallback.cpp
index 6ac44d1..8a9e375 100644
--- a/subversion/bindings/javahl/native/InfoCallback.cpp
+++ b/subversion/bindings/javahl/native/InfoCallback.cpp
@@ -52,7 +52,7 @@
apr_pool_t *pool)
{
if (baton)
- return ((InfoCallback *)baton)->singleInfo(path, info, pool);
+ return static_cast<InfoCallback *>(baton)->singleInfo(path, info, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/InputStream.cpp b/subversion/bindings/javahl/native/InputStream.cpp
index 4be93fe..b659a20 100644
--- a/subversion/bindings/javahl/native/InputStream.cpp
+++ b/subversion/bindings/javahl/native/InputStream.cpp
@@ -70,7 +70,7 @@
{
JNIEnv *env = JNIUtil::getEnv();
// An object of our class is passed in as the baton.
- InputStream *that = (InputStream*)baton;
+ InputStream *that = static_cast<InputStream *>(baton);
// The method id will not change during the time this library is
// loaded, so it can be cached.
diff --git a/subversion/bindings/javahl/native/ListCallback.cpp b/subversion/bindings/javahl/native/ListCallback.cpp
index 385bb1d..3402c7a 100644
--- a/subversion/bindings/javahl/native/ListCallback.cpp
+++ b/subversion/bindings/javahl/native/ListCallback.cpp
@@ -57,8 +57,8 @@
apr_pool_t *pool)
{
if (baton)
- return ((ListCallback *)baton)->doList(path, dirent, lock, abs_path,
- pool);
+ return static_cast<ListCallback *>(baton)->doList(
+ path, dirent, lock, abs_path, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/LogMessageCallback.cpp b/subversion/bindings/javahl/native/LogMessageCallback.cpp
index cdd71fc..804ccf3 100644
--- a/subversion/bindings/javahl/native/LogMessageCallback.cpp
+++ b/subversion/bindings/javahl/native/LogMessageCallback.cpp
@@ -57,7 +57,8 @@
apr_pool_t *pool)
{
if (baton)
- return ((LogMessageCallback *)baton)->singleMessage(log_entry, pool);
+ return static_cast<LogMessageCallback *>(baton)->singleMessage(
+ log_entry, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/OutputStream.cpp b/subversion/bindings/javahl/native/OutputStream.cpp
index ce80a95..ba5595c 100644
--- a/subversion/bindings/javahl/native/OutputStream.cpp
+++ b/subversion/bindings/javahl/native/OutputStream.cpp
@@ -76,7 +76,7 @@
JNIEnv *env = JNIUtil::getEnv();
// An object of our class is passed in as the baton.
- OutputStream *that = (OutputStream*)baton;
+ OutputStream *that = static_cast<OutputStream *>(baton);
// The method id will not change during the time this library is
// loaded, so it can be cached.
diff --git a/subversion/bindings/javahl/native/PatchCallback.cpp b/subversion/bindings/javahl/native/PatchCallback.cpp
index b958af6..0cff577 100644
--- a/subversion/bindings/javahl/native/PatchCallback.cpp
+++ b/subversion/bindings/javahl/native/PatchCallback.cpp
@@ -54,10 +54,9 @@
apr_pool_t *pool)
{
if (baton)
- return ((PatchCallback *)baton)->singlePatch(filtered,
- canon_path_from_patchfile,
- patch_abspath, reject_abspath,
- pool);
+ return static_cast<PatchCallback *>(baton)->singlePatch(
+ filtered, canon_path_from_patchfile, patch_abspath, reject_abspath,
+ pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/Prompter.cpp b/subversion/bindings/javahl/native/Prompter.cpp
index 4ce8244..d06c39e 100644
--- a/subversion/bindings/javahl/native/Prompter.cpp
+++ b/subversion/bindings/javahl/native/Prompter.cpp
@@ -427,7 +427,7 @@
svn_boolean_t may_save,
apr_pool_t *pool)
{
- Prompter *that = (Prompter*)baton;
+ Prompter *that = static_cast<Prompter *>(baton);
svn_auth_cred_simple_t *ret =
(svn_auth_cred_simple_t*)apr_pcalloc(pool, sizeof(*ret));
if (!that->prompt(realm, username, may_save ? true : false))
@@ -460,7 +460,7 @@
svn_boolean_t may_save,
apr_pool_t *pool)
{
- Prompter *that = (Prompter*)baton;
+ Prompter *that = static_cast<Prompter *>(baton);
svn_auth_cred_username_t *ret =
(svn_auth_cred_username_t*)apr_pcalloc(pool, sizeof(*ret));
const char *user = that->askQuestion(realm, _("Username: "), true,
@@ -484,7 +484,7 @@
svn_boolean_t may_save,
apr_pool_t *pool)
{
- Prompter *that = (Prompter*)baton;
+ Prompter *that = static_cast<Prompter *>(baton);
svn_auth_cred_ssl_server_trust_t *ret =
(svn_auth_cred_ssl_server_trust_t*)apr_pcalloc(pool, sizeof(*ret));
@@ -550,7 +550,7 @@
svn_boolean_t may_save,
apr_pool_t *pool)
{
- Prompter *that = (Prompter*)baton;
+ Prompter *that = static_cast<Prompter *>(baton);
svn_auth_cred_ssl_client_cert_t *ret =
(svn_auth_cred_ssl_client_cert_t*)apr_pcalloc(pool, sizeof(*ret));
const char *cert_file =
@@ -572,7 +572,7 @@
svn_boolean_t may_save,
apr_pool_t *pool)
{
- Prompter *that = (Prompter*)baton;
+ Prompter *that = static_cast<Prompter *>(baton);
svn_auth_cred_ssl_client_cert_pw_t *ret =
(svn_auth_cred_ssl_client_cert_pw_t*)apr_pcalloc(pool, sizeof(*ret));
const char *info = that->askQuestion(realm,
@@ -593,7 +593,7 @@
void *baton,
apr_pool_t *pool)
{
- Prompter *that = (Prompter *) baton;
+ Prompter *that = static_cast<Prompter *>(baton);
bool result = that->askYesNo(realmstring,
_("Store password unencrypted?"),
@@ -610,7 +610,7 @@
void *baton,
apr_pool_t *pool)
{
- Prompter *that = (Prompter *) baton;
+ Prompter *that = static_cast<Prompter *>(baton);
bool result = that->askYesNo(realmstring,
_("Store passphrase unencrypted?"),
diff --git a/subversion/bindings/javahl/native/ProplistCallback.cpp b/subversion/bindings/javahl/native/ProplistCallback.cpp
index a3bcee3..fd6732f 100644
--- a/subversion/bindings/javahl/native/ProplistCallback.cpp
+++ b/subversion/bindings/javahl/native/ProplistCallback.cpp
@@ -54,7 +54,8 @@
apr_pool_t *pool)
{
if (baton)
- return ((ProplistCallback *)baton)->singlePath(path, prop_hash, pool);
+ return static_cast<ProplistCallback *>(baton)->singlePath(
+ path, prop_hash, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/ReposNotifyCallback.cpp b/subversion/bindings/javahl/native/ReposNotifyCallback.cpp
index 2b3194f..a716653 100644
--- a/subversion/bindings/javahl/native/ReposNotifyCallback.cpp
+++ b/subversion/bindings/javahl/native/ReposNotifyCallback.cpp
@@ -49,7 +49,7 @@
apr_pool_t *pool)
{
if (baton)
- ((ReposNotifyCallback *)baton)->onNotify(notify, pool);
+ static_cast<ReposNotifyCallback *>(baton)->onNotify(notify, pool);
}
/**
diff --git a/subversion/bindings/javahl/native/SVNClient.cpp b/subversion/bindings/javahl/native/SVNClient.cpp
index 1e95caf..43b3c00 100644
--- a/subversion/bindings/javahl/native/SVNClient.cpp
+++ b/subversion/bindings/javahl/native/SVNClient.cpp
@@ -44,6 +44,7 @@
#include "StatusCallback.h"
#include "ChangelistCallback.h"
#include "ListCallback.h"
+#include "ImportFilterCallback.h"
#include "JNIByteArray.h"
#include "CommitMessage.h"
#include "EnumMapper.h"
@@ -553,7 +554,9 @@
void SVNClient::doImport(const char *path, const char *url,
CommitMessage *message, svn_depth_t depth,
bool noIgnore, bool ignoreUnknownNodeTypes,
- RevpropTable &revprops, CommitCallback *callback)
+ RevpropTable &revprops,
+ ImportFilterCallback *ifCallback,
+ CommitCallback *commitCallback)
{
SVN::Pool subPool(pool);
SVN_JNI_NULL_PTR_EX(path, "path", );
@@ -567,10 +570,11 @@
if (ctx == NULL)
return;
- SVN_JNI_ERR(svn_client_import4(intPath.c_str(), intUrl.c_str(), depth,
+ SVN_JNI_ERR(svn_client_import5(intPath.c_str(), intUrl.c_str(), depth,
noIgnore, ignoreUnknownNodeTypes,
revprops.hash(subPool),
- CommitCallback::callback, callback,
+ ImportFilterCallback::callback, ifCallback,
+ CommitCallback::callback, commitCallback,
ctx, subPool.getPool()), );
}
@@ -816,7 +820,8 @@
* Get a property.
*/
jbyteArray SVNClient::propertyGet(const char *path, const char *name,
- Revision &revision, Revision &pegRevision)
+ Revision &revision, Revision &pegRevision,
+ StringArray &changelists)
{
SVN::Pool subPool(pool);
SVN_JNI_NULL_PTR_EX(path, "path", NULL);
@@ -829,11 +834,11 @@
return NULL;
apr_hash_t *props;
- SVN_JNI_ERR(svn_client_propget4(&props, name,
+ SVN_JNI_ERR(svn_client_propget5(&props, NULL, name,
intPath.c_str(), pegRevision.revision(),
revision.revision(), NULL, svn_depth_empty,
- NULL, ctx, subPool.getPool(),
- subPool.getPool()),
+ changelists.array(subPool), ctx,
+ subPool.getPool(), subPool.getPool()),
NULL);
apr_hash_index_t *hi;
@@ -1187,7 +1192,6 @@
{
SVN::Pool subPool(pool);
SVN_JNI_NULL_PTR_EX(path, "path", );
- apr_pool_t *pool = subPool.getPool();
Path intPath(path, subPool);
SVN_JNI_ERR(intPath.error_occured(), );
@@ -1195,13 +1199,12 @@
if (ctx == NULL)
return;
- SVN_JNI_ERR(svn_client_blame5(intPath.c_str(), pegRevision.revision(),
- revisionStart.revision(),
- revisionEnd.revision(),
- svn_diff_file_options_create(pool),
- ignoreMimeType, includeMergedRevisions,
- BlameCallback::callback, callback, ctx,
- pool),
+ SVN_JNI_ERR(svn_client_blame5(
+ intPath.c_str(), pegRevision.revision(), revisionStart.revision(),
+ revisionEnd.revision(),
+ svn_diff_file_options_create(subPool.getPool()), ignoreMimeType,
+ includeMergedRevisions, BlameCallback::callback, callback, ctx,
+ subPool.getPool()),
);
}
diff --git a/subversion/bindings/javahl/native/SVNClient.h b/subversion/bindings/javahl/native/SVNClient.h
index 3421b0f..49926f8 100644
--- a/subversion/bindings/javahl/native/SVNClient.h
+++ b/subversion/bindings/javahl/native/SVNClient.h
@@ -46,6 +46,7 @@
class InfoCallback;
class CommitCallback;
class ListCallback;
+class ImportFilterCallback;
class StatusCallback;
class OutputStream;
class PatchCallback;
@@ -107,7 +108,8 @@
const char *localPath, bool dryRun);
void doImport(const char *path, const char *url, CommitMessage *message,
svn_depth_t depth, bool noIgnore, bool ignoreUnknownNodeTypes,
- RevpropTable &revprops, CommitCallback *callback);
+ RevpropTable &revprops, ImportFilterCallback *ifCallback,
+ CommitCallback *commitCallback);
jlong doSwitch(const char *path, const char *url, Revision &revision,
Revision &pegRevision, svn_depth_t depth,
bool depthIsSticky, bool ignoreExternals,
@@ -171,7 +173,8 @@
bool lastChanged);
void upgrade(const char *path);
jbyteArray propertyGet(const char *path, const char *name,
- Revision &revision, Revision &pegRevision);
+ Revision &revision, Revision &pegRevision,
+ StringArray &changelists);
void diff(const char *target1, Revision &revision1,
const char *target2, Revision &revision2,
const char *relativeToDir, OutputStream &outputStream,
diff --git a/subversion/bindings/javahl/native/SVNRepos.cpp b/subversion/bindings/javahl/native/SVNRepos.cpp
index 4a69237..1f4f2db 100644
--- a/subversion/bindings/javahl/native/SVNRepos.cpp
+++ b/subversion/bindings/javahl/native/SVNRepos.cpp
@@ -68,7 +68,7 @@
svn_error_t *
SVNRepos::checkCancel(void *cancelBaton)
{
- SVNRepos *that = (SVNRepos *)cancelBaton;
+ SVNRepos *that = static_cast<SVNRepos *>(cancelBaton);
if (that->m_cancelOperation)
return svn_error_create(SVN_ERR_CANCELLED, NULL,
_("Operation cancelled"));
@@ -248,7 +248,7 @@
}
void SVNRepos::hotcopy(File &path, File &targetPath,
- bool cleanLogs)
+ bool cleanLogs, bool incremental)
{
SVN::Pool requestPool;
@@ -264,9 +264,12 @@
return;
}
- SVN_JNI_ERR(svn_repos_hotcopy(path.getInternalStyle(requestPool),
- targetPath.getInternalStyle(requestPool),
- cleanLogs, requestPool.getPool()), );
+ SVN_JNI_ERR(svn_repos_hotcopy2(path.getInternalStyle(requestPool),
+ targetPath.getInternalStyle(requestPool),
+ cleanLogs, incremental,
+ checkCancel, this /* cancel callback/baton */,
+ requestPool.getPool()),
+ );
}
static void
diff --git a/subversion/bindings/javahl/native/SVNRepos.h b/subversion/bindings/javahl/native/SVNRepos.h
index a34cf88..23ce873 100644
--- a/subversion/bindings/javahl/native/SVNRepos.h
+++ b/subversion/bindings/javahl/native/SVNRepos.h
@@ -58,7 +58,7 @@
void listUnusedDBLogs(File &path,
MessageReceiver &messageReceiver);
void listDBLogs(File &path, MessageReceiver &messageReceiver);
- void hotcopy(File &path, File &targetPath, bool cleanLogs);
+ void hotcopy(File &path, File &targetPath, bool cleanLogs, bool incremental);
void dump(File &path, OutputStream &dataOut, Revision &revsionStart,
Revision &RevisionEnd, bool incremental, bool useDeltas,
ReposNotifyCallback *notifyCallback);
diff --git a/subversion/bindings/javahl/native/StatusCallback.cpp b/subversion/bindings/javahl/native/StatusCallback.cpp
index 49a8043..873bde2 100644
--- a/subversion/bindings/javahl/native/StatusCallback.cpp
+++ b/subversion/bindings/javahl/native/StatusCallback.cpp
@@ -55,7 +55,8 @@
apr_pool_t *pool)
{
if (baton)
- return ((StatusCallback *)baton)->doStatus(local_abspath, status, pool);
+ return static_cast<StatusCallback *>(baton)->doStatus(
+ local_abspath, status, pool);
return SVN_NO_ERROR;
}
diff --git a/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp b/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp
index bbdf13e..aaf2f86 100644
--- a/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp
+++ b/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNClient.cpp
@@ -47,6 +47,7 @@
#include "InfoCallback.h"
#include "StatusCallback.h"
#include "ListCallback.h"
+#include "ImportFilterCallback.h"
#include "ChangelistCallback.h"
#include "StringArray.h"
#include "RevpropTable.h"
@@ -712,7 +713,7 @@
Java_org_apache_subversion_javahl_SVNClient_doImport
(JNIEnv *env, jobject jthis, jstring jpath, jstring jurl, jobject jdepth,
jboolean jnoIgnore, jboolean jignoreUnknownNodeTypes, jobject jrevpropTable,
- jobject jmessage, jobject jcallback)
+ jobject jimportFilterCallback, jobject jmessage, jobject jcommitCallback)
{
JNIEntry(SVNClient, doImport);
SVNClient *cl = SVNClient::getCppObject(jthis);
@@ -737,11 +738,14 @@
if (JNIUtil::isExceptionThrown())
return;
- CommitCallback callback(jcallback);
+ ImportFilterCallback importFilterCallback(jimportFilterCallback);
+ CommitCallback commitCallback(jcommitCallback);
+
cl->doImport(path, url, &message, EnumMapper::toDepth(jdepth),
jnoIgnore ? true : false,
jignoreUnknownNodeTypes ? true : false, revprops,
- jcallback ? &callback : NULL);
+ jimportFilterCallback ? &importFilterCallback : NULL,
+ jcommitCallback ? &commitCallback : NULL);
}
JNIEXPORT jobject JNICALL
@@ -1080,7 +1084,7 @@
JNIEXPORT jbyteArray JNICALL
Java_org_apache_subversion_javahl_SVNClient_propertyGet
(JNIEnv *env, jobject jthis, jstring jpath, jstring jname, jobject jrevision,
- jobject jpegRevision)
+ jobject jpegRevision, jobject jchangelists)
{
JNIEntry(SVNClient, propertyGet);
SVNClient *cl = SVNClient::getCppObject(jthis);
@@ -1105,7 +1109,11 @@
if (JNIUtil::isExceptionThrown())
return NULL;
- return cl->propertyGet(path, name, revision, pegRevision);
+ StringArray changelists(jchangelists);
+ if (JNIUtil::isExceptionThrown())
+ return NULL;
+
+ return cl->propertyGet(path, name, revision, pegRevision, changelists);
}
JNIEXPORT jobject JNICALL
diff --git a/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp b/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp
index 1ab1c59..045a459 100644
--- a/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp
+++ b/subversion/bindings/javahl/native/org_apache_subversion_javahl_SVNRepos.cpp
@@ -170,7 +170,7 @@
JNIEXPORT void JNICALL
Java_org_apache_subversion_javahl_SVNRepos_hotcopy
(JNIEnv *env, jobject jthis, jobject jpath, jobject jtargetPath,
- jboolean jcleanLogs)
+ jboolean jcleanLogs, jboolean jincremental)
{
JNIEntry(SVNRepos, hotcopy);
SVNRepos *cl = SVNRepos::getCppObject(jthis);
@@ -188,7 +188,8 @@
if (JNIUtil::isExceptionThrown())
return;
- cl->hotcopy(path, targetPath, jcleanLogs ? true : false);
+ cl->hotcopy(path, targetPath, jcleanLogs ? true : false,
+ jincremental ? true : false);
}
JNIEXPORT void JNICALL
diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java
index 75089c0..b9de4cb 100644
--- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java
+++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/ConflictDescriptor.java
@@ -250,7 +250,19 @@
/**
* Object is already replaced.
*/
- replaced;
+ replaced,
+
+ /**
+ * Object is moved away.
+ * @since 1.8
+ */
+ moved_away,
+
+ /**
+ * Object is moved here.
+ * @since 1.8
+ */
+ moved_here;
}
public enum Operation
diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java
index 9c9c27f..eaf9b7c 100644
--- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java
+++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNClient.java
@@ -406,6 +406,13 @@
void doImport(String path, String url, Depth depth,
boolean noIgnore, boolean ignoreUnknownNodeTypes,
Map<String, String> revpropTable,
+ ImportFilterCallback importFilterCallback,
+ CommitMessageCallback handler, CommitCallback commitCallback)
+ throws ClientException;
+
+ void doImport(String path, String url, Depth depth,
+ boolean noIgnore, boolean ignoreUnknownNodeTypes,
+ Map<String, String> revpropTable,
CommitMessageCallback handler, CommitCallback callback)
throws ClientException;
@@ -719,6 +726,10 @@
* @throws ClientException
*/
byte[] propertyGet(String path, String name, Revision revision,
+ Revision pegRevision, Collection<String> changelists)
+ throws ClientException;
+
+ byte[] propertyGet(String path, String name, Revision revision,
Revision pegRevision)
throws ClientException;
diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java
index 318fb07..16eae7b 100644
--- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java
+++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/ISVNRepos.java
@@ -113,6 +113,9 @@
* @throws ClientException throw in case of problem
*/
public abstract void hotcopy(File path, File targetPath,
+ boolean cleanLogs, boolean incremental) throws ClientException;
+
+ public abstract void hotcopy(File path, File targetPath,
boolean cleanLogs) throws ClientException;
/**
diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java
index 662b51e..c080448 100644
--- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java
+++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNClient.java
@@ -230,9 +230,22 @@
boolean noIgnore,
boolean ignoreUnknownNodeTypes,
Map<String, String> revpropTable,
- CommitMessageCallback handler, CommitCallback callback)
+ ImportFilterCallback importFilterCallback,
+ CommitMessageCallback handler,
+ CommitCallback commitCallback)
throws ClientException;
+ public void doImport(String path, String url, Depth depth, boolean noIgnore,
+ boolean ignoreUnknownNodeTypes,
+ Map<String, String> revpropTable,
+ CommitMessageCallback handler,
+ CommitCallback callback)
+ throws ClientException
+ {
+ doImport(path, url, depth, noIgnore, ignoreUnknownNodeTypes,
+ revpropTable, null, handler, callback);
+ }
+
public native Set<String> suggestMergeSources(String path,
Revision pegRevision)
throws SubversionException;
@@ -366,8 +379,16 @@
boolean force)
throws ClientException;
+ public byte[] propertyGet(String path, String name,
+ Revision revision, Revision pegRevision)
+ throws ClientException
+ {
+ return propertyGet(path, name, revision, pegRevision, null);
+ }
+
public native byte[] propertyGet(String path, String name,
- Revision revision, Revision pegRevision)
+ Revision revision, Revision pegRevision,
+ Collection<String> changelists)
throws ClientException;
public byte[] fileContent(String path, Revision revision,
diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java
index 98b611f..a3df731 100644
--- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java
+++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNRepos.java
@@ -130,7 +130,14 @@
* @throws ClientException throw in case of problem
*/
public native void hotcopy(File path, File targetPath,
- boolean cleanLogs) throws ClientException;
+ boolean cleanLogs, boolean incremental)
+ throws ClientException;
+
+ public void hotcopy(File path, File targetPath,
+ boolean cleanLogs) throws ClientException
+ {
+ hotcopy(path, targetPath, cleanLogs, false);
+ }
/**
* list all logfiles (BDB) in use or not)
diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java
new file mode 100644
index 0000000..60aabcd
--- /dev/null
+++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/callback/ImportFilterCallback.java
@@ -0,0 +1,40 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+package org.apache.subversion.javahl.callback;
+
+import org.apache.subversion.javahl.ISVNClient;
+import org.apache.subversion.javahl.types.NodeKind;
+
+/**
+ * This interface is used to filter imported nodes in the
+ * {@link ISVNClient#import} call.
+ */
+public interface ImportFilterCallback
+{
+ /**
+ * This method will be called for each node. Returns true to filter the
+ * node (and its descendants) from the import.
+ */
+ public boolean filter(String path, NodeKind kind, boolean special);
+}
diff --git a/subversion/bindings/swig/core.i b/subversion/bindings/swig/core.i
index 9d4ccdd..d32bb4a 100644
--- a/subversion/bindings/swig/core.i
+++ b/subversion/bindings/swig/core.i
@@ -658,14 +658,14 @@
)
#endif
-#ifdef SWIGRUBY
+#ifndef SWIGPERL
%callback_typemap(svn_config_enumerator2_t callback, void *baton,
- ,
+ svn_swig_py_config_enumerator2,
,
svn_swig_rb_config_enumerator)
%callback_typemap(svn_config_section_enumerator2_t callback, void *baton,
- ,
+ svn_swig_py_config_section_enumerator2,
,
svn_swig_rb_config_section_enumerator)
#endif
diff --git a/subversion/bindings/swig/include/svn_containers.swg b/subversion/bindings/swig/include/svn_containers.swg
index 0c1e37a..2825dc8 100644
--- a/subversion/bindings/swig/include/svn_containers.swg
+++ b/subversion/bindings/swig/include/svn_containers.swg
@@ -507,7 +507,11 @@
}
}
#endif
-
+#ifdef SWIGPERL
+%typemap(argout) apr_array_header_t **result_revs {
+ %append_output(svn_swig_pl_revnums_to_list(*$1));
+}
+#endif
#ifdef SWIGRUBY
%typemap(argout) apr_array_header_t **result_revs {
%append_output(svn_swig_rb_apr_array_to_array_svn_rev(*$1));
diff --git a/subversion/bindings/swig/include/svn_types.swg b/subversion/bindings/swig/include/svn_types.swg
index c3a3106..08ad3dd 100644
--- a/subversion/bindings/swig/include/svn_types.swg
+++ b/subversion/bindings/swig/include/svn_types.swg
@@ -793,23 +793,38 @@
Callback: svn_commit_callback2_t
svn_ra get_commit_editor2()
svn_repos_get_commit_editor4()
+ svn_client_mkdir4()
+ svn_client_delete4()
+ svn_client_import4()
+ svn_client_commit5()
+ svn_client_copy6()
+ svn_client_move6()
+ svn_client_propset_remote()
*/
+#ifdef SWIGPERL
+%typemap(in) (svn_commit_callback2_t commit_callback, void *commit_baton) {
+ $1 = svn_swig_pl_thunk_commit_callback2;
+ $2 = (void *)$input;
+ svn_swig_pl_hold_ref_in_pool (_global_pool, $input);
+};
+#endif
+
#ifdef SWIGRUBY
-%typemap(in) (svn_commit_callback2_t callback, void *callback_baton)
+%typemap(in) (svn_commit_callback2_t commit_callback, void *commit_baton)
{
$1 = svn_swig_rb_commit_callback2;
$2 = (void *)svn_swig_rb_make_baton($input, _global_svn_swig_rb_pool);
};
-%typemap(argout) (svn_commit_callback2_t callback, void *callback_baton)
+%typemap(argout) (svn_commit_callback2_t commit_callback, void *commit_baton)
{
svn_swig_rb_set_baton($result, (VALUE)$2);
};
#endif
#ifdef SWIGPYTHON
-%typemap(in) (svn_commit_callback2_t callback, void *callback_baton)
+%typemap(in) (svn_commit_callback2_t commit_callback, void *commit_baton)
{
$1 = svn_swig_py_commit_callback2;
$2 = (void *)$input;
diff --git a/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c b/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c
index 6bc2336..154f7a7 100644
--- a/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c
+++ b/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c
@@ -229,6 +229,12 @@
return sv_2mortal(newSViv(value));
}
+static SV *convert_svn_revnum_t(svn_revnum_t revnum, void *dummy)
+{
+ return sv_2mortal(newSViv((long int)revnum));
+
+}
+
/* c -> perl hash convertors */
static SV *convert_hash(apr_hash_t *hash, element_converter_t converter_func,
void *ctx)
@@ -301,6 +307,12 @@
tinfo);
}
+SV *svn_swig_pl_revnums_to_list(const apr_array_header_t *array)
+{
+ return convert_array(array, (element_converter_t)convert_svn_revnum_t,
+ NULL);
+}
+
/* put the va_arg in stack and invoke caller_func with func.
fmt:
* O: perl object
@@ -907,6 +919,22 @@
return SVN_NO_ERROR;
}
+svn_error_t *svn_swig_pl_thunk_commit_callback2(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ if (!SvOK((SV *)baton))
+ return SVN_NO_ERROR;
+
+ svn_swig_pl_callback_thunk(CALL_SV, baton, NULL,
+ "SS",
+ commit_info, _SWIG_TYPE("svn_commit_info_t *"),
+ pool, POOLINFO);
+
+ return SVN_NO_ERROR;
+}
+
+
/* Wrap RA */
static svn_error_t * thunk_open_tmp_file(apr_file_t **fp,
@@ -1288,6 +1316,54 @@
}
+/* Thunked version of svn_wc_status_func2_t callback type. */
+void svn_swig_pl_status_func2(void *baton,
+ const char *path,
+ svn_wc_status2_t *status)
+{
+ swig_type_info *statusinfo = _SWIG_TYPE("svn_wc_status2 _t *");
+
+ if (!SvOK((SV *)baton)) {
+ return;
+ }
+
+ svn_swig_pl_callback_thunk(CALL_SV, baton, NULL, "sS",
+ path, status, statusinfo);
+
+}
+
+/* Thunked version of svn_wc_status_func3_t callback type. */
+svn_error_t *svn_swig_pl_status_func3(void *baton,
+ const char *path,
+ svn_wc_status2_t *status,
+ apr_pool_t *pool)
+{
+ SV *result;
+ svn_error_t *ret_val = SVN_NO_ERROR;
+
+ swig_type_info *statusinfo = _SWIG_TYPE("svn_wc_status2 _t *");
+
+ if (!SvOK((SV *)baton)) {
+ return ret_val;
+ }
+
+ svn_swig_pl_callback_thunk(CALL_SV, baton, &result, "sSS",
+ path, status, statusinfo,
+ pool, POOLINFO);
+
+ if (sv_derived_from(result, "_p_svn_error_t")) {
+ swig_type_info *errorinfo = _SWIG_TYPE("svn_error_t *");
+ if (SWIG_ConvertPtr(result, (void *)&ret_val, errorinfo, 0) < 0) {
+ SvREFCNT_dec(result);
+ croak("Unable to convert from SWIG Type");
+ }
+ }
+
+ SvREFCNT_dec(result);
+ return ret_val;
+}
+
+
/* Thunked version of svn_client_blame_receiver_t callback type. */
svn_error_t *svn_swig_pl_blame_func(void *baton,
apr_int64_t line_no,
diff --git a/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h b/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h
index 7a90ffb..ae72a82 100644
--- a/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h
+++ b/subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.h
@@ -104,6 +104,8 @@
SV *svn_swig_pl_convert_array(const apr_array_header_t *array,
swig_type_info *tinfo);
+SV *svn_swig_pl_revnums_to_list(const apr_array_header_t *array);
+
/* thunked log receiver function. */
svn_error_t * svn_swig_pl_thunk_log_receiver(void *py_receiver,
apr_hash_t *changed_paths,
@@ -125,6 +127,11 @@
const char *author,
void *baton);
+/* thunked commit editor callback2. */
+svn_error_t *svn_swig_pl_thunk_commit_callback2(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool);
+
/* thunked repos_history callback. */
svn_error_t *svn_swig_pl_thunk_history_func(void *baton,
const char *path,
@@ -229,6 +236,18 @@
void svn_swig_pl_status_func(void *baton,
const char *path,
svn_wc_status_t *status);
+
+/* Thunked version of svn_wc_status_func2_t callback type. */
+void svn_swig_pl_status_func2(void *baton,
+ const char *path,
+ svn_wc_status2_t *status);
+
+/* Thunked version of svn_wc_status_func2_t callback type. */
+svn_error_t *svn_swig_pl_status_func3(void *baton,
+ const char *path,
+ svn_wc_status2_t *status,
+ apr_pool_t *pool);
+
/* Thunked version of svn_client_blame_receiver_t callback type. */
svn_error_t *svn_swig_pl_blame_func(void *baton,
apr_int64_t line_no,
diff --git a/subversion/bindings/swig/perl/native/Client.pm b/subversion/bindings/swig/perl/native/Client.pm
index d41ac0f..52796c9 100644
--- a/subversion/bindings/swig/perl/native/Client.pm
+++ b/subversion/bindings/swig/perl/native/Client.pm
@@ -9,10 +9,10 @@
BEGIN {
@_all_fns =
qw( version diff_summarize_dup create_context checkout3
- checkout2 checkout update3 update2 update switch2 switch
- add4 add3 add2 add mkdir3 mkdir2 mkdir delete3 delete2
+ checkout2 checkout update4 update3 update2 update switch2 switch
+ add4 add3 add2 add mkdir4 mkdir3 mkdir2 mkdir delete3 delete2
delete import3 import2 import commit4 commit3 commit2
- commit status3 status2 status log4 log3 log2 log blame4
+ commit status4 status3 status2 status log4 log3 log2 log blame4
blame3 blame2 blame diff4 diff3 diff2 diff diff_peg4
diff_peg3 diff_peg2 diff_peg diff_summarize2
diff_summarize diff_summarize_peg2 diff_summarize_peg
@@ -254,14 +254,50 @@
=item $ctx-E<gt>add($path, $recursive, $pool);
+Similar to $ctx-E<gt>add2(), but with $force always set to FALSE.
+
+=item $ctx-E<gt>add2($path, $recursive, $force, $ctx, $pool);
+
+Similar to $ctx-E<gt>add3(), but with $no_ignore always set to FALSE.
+
+=item $ctx-E<gt>add3($path, $recursive, $force, $no_ignore, $pool);
+
+Similar to $ctx-E<gt>add4(), but with $add_parents always set to FALSE and
+$depth set according to $recursive; if TRUE, then depth is
+$SVN::Depth::infinity, if FALSE, then $SVN::Depth::empty.
+
+=item $ctx-E<gt>add4($path, $depth, $force, $no_ignore, $add_parents, $pool);
+
Schedule a working copy $path for addition to the repository.
-$path's parent must be under revision control already, but $path is not.
-If $recursive is set, then assuming $path is a directory, all of its
-contents will be scheduled for addition as well.
+If $depth is $SVN::Depth::empty, add just $path and nothing below it. If
+$SVN::Depth::files, add $path and any file children of $path. If
+$SVN::Depth::immediates, add $path, any file children, and any immediate
+subdirectories (but nothing underneath those subdirectories). If
+$SVN::Depth::infinity, add $path and everything under it fully recursively.
+
+$path's parent must be under revision control already (unless $add_parents is
+TRUE), but $path is not.
+
+Unless $force is TRUE and $path is already under version control, returns an
+$SVN::Error::ENTRY_EXISTS object. If $force is set, do not error on
+already-versioned items. When used with $depth set to $SVN::Depth::infinity
+it will enter versioned directories; scheduling unversioned children.
Calls the notify callback for each added item.
+If $no_ignore is FALSE, don't add any file or directory (or recurse into any
+directory) that is unversioned and found by recursion (as opposed to being the
+explicit target $path) and whose name matches the svn:ignore property on its
+parent directory or the global-ignores list in $ctx->config. If $no_ignore is
+TRUE, do include such files and directories. (Note that an svn:ignore property
+can influence this behaviour only when recursing into an already versioned
+directory with $force).
+
+If $add_parents is TRUE, recurse up $path's directory and look for a versioned
+directory. If found, add all intermediate paths between it and $path. If not
+found return $SVN::Error::NO_VERSIONED_PARENT.
+
Important: this is a B<scheduling> operation. No changes will happen
to the repository until a commit occurs. This scheduling can be
removed with $ctx-E<gt>revert().
@@ -299,12 +335,39 @@
=item $ctx-E<gt>checkout($url, $path, $revision, $recursive, $pool);
+Similar to $ctx-E<gt>checkout2(), but with $peg_revision always set to undef (unspecified) and $ignore_externals always set to FALSE.
+
+=item $ctx-E<gt>checkout2($url, $path, $peg_revision, $revision, $recursive, $ignore_externals, $pool);
+
+Similar to $ctx-E<gt>checkout3(), but with $allow_unver_obstructions always set
+to FALSE, and $depth set according to $recurse: if $recurse is TRUE, $depth is
+$SVN::Depth::infinity, if $recurse is FALSE, set $depth to $SVN::Depth::files.
+
+=item $ctx-E<gt>checkout3($url, $path, $preg_revision, $revision, $depth, $ignore_externals, $allow_unver_obstructions, $pool);
+
Checkout a working copy of $url at $revision using $path as the root directory
of the newly checked out working copy.
+The $peg_revision sets the revision at which the path in the $url is treated as representing.
+
$revision must be a number, 'HEAD', or a date. If $revision does not
meet these requirements the $SVN::Error::CLIENT_BAD_REVISION is raised.
+$depth is one of the constants in SVN::Depth and specifies the depth of the
+operation. If set to $SVN::Depth::unknown, then behave as if for
+$SVN::Depth::infinity, except in the case of resuming a previous checkout of
+$path (i.e. updating) in which case use the depth of the existing working copy.
+
+$ignore_exteranls if set to TRUE the operation will ignore external definitions.
+
+$allow_unver_obstructions if set to TRUE the operation will tolerate existing
+unversioned items that obstruct incoming paths. Only obstructions of the same
+type (file or dir) as the added item are tolerated. The text of obstructing
+files is left as-is, effectively treating it as a user modification after the
+checkout. Working properties of obstructing items are set equal to the base
+properties. If set to FALSE, then abort if there are any unversioned
+obstructing items.
+
Returns the value of the revision actually checked out of the repository.
=item $ctx-E<gt>cleanup($dir, $pool);
@@ -565,6 +628,21 @@
=item $ctx-E<gt>mkdir($targets, $pool);
+Similar to $ctx-E<gt>mkdir2() except it returns an svn_client_commit_info_t
+object instead of a svn_commit_info_t object.
+
+=item $ctx-E<gt>mkdir2($targets, $pool);
+
+Similar to $ctx-E<gt>mkdir3(), but with $make_parents always FALSE, and
+$revprop_hash always undef.
+
+=item $ctx-E<gt>mkdir3($targets, $make_parents, $revprop_hash, $pool);
+
+Similar to $ctx-E<gt>mkdir4(), but returns a svn_commit_info_t object rather
+than through a callback function.
+
+=item $ctx-E<gt>mkdir4($targets, $make_parents, $revprop_hash, \&commit_callback, $pool);
+
Create a directory, either in a repository or a working copy.
If $targets contains URLs, immediately attempts to commit the creation of the
@@ -574,10 +652,23 @@
Else, create the directories on disk, and attempt to schedule them for addition.
In this case returns undef.
+If $make_parents is TRUE, create any non-existant parent directories also.
+
+If not undef, $revprop_hash is a reference to a hash table holding additional
+custom revision properites (property names mapped to strings) to be set on the
+new revision in the event that this is a committing operation. This hash
+cannot contain any standard Subversion properties.
+
+Calls the log message callback to query for a commit log message when one is
+needed.
+
Calls the notify callback when the directory has been created (successfully)
in the working copy, with the path of the new directory. Note this is only
called for items added to the working copy.
+If \&commit_callback is not undef, then for each successful commit, call
+\&commit_callback with the svn_commit_info_t object for the commit.
+
=item $ctx-E<gt>move($src_path, $src_revision, $dst_path, $force, $pool);
Move $src_path to $dst_path.
@@ -732,8 +823,20 @@
=item $ctx-E<gt>status($path, $revision, \&status_func, $recursive, $get_all, $update, $no_ignore, $pool);
+Similar to $ctx-E<gt>status2(), but with ignore_externals always set to FALSE, and with the status_func receiving a svn_wc_status2_t instead of a svn_wc_status_t object.
+
+=item $ctx-E<gt>status2($path, $revision, \&status_func, $recursive, $get_all, $update, $no_ignore, $ignore_externals, $pool);
+
+Similar to $ctx-E<gt>status3(), but with the changelists passed as undef, and with recursive instead of depth.
+
+=item $ctx-E<gt>status3($path, $revision, \&status_func, $depth, $get_all, $update, $no_ignore, $ignore_externals, $changelists, $pool);
+
+Similar to $ctx-E<gt>status4(), without the pool parameter to the callback and the return of the callback is ignored.
+
+=item $ctx-E<gt>status4($path, $revision, \&status_func, $depth, $get_all, $update, $no_ignore, $ignore_externals, $changelists, $pool);
+
Given $path to a working copy directory (or single file), call status_func()
-with a set of svn_wc_status_t objects which describe the status of $path and
+with a set of svn_wc_status2_t objects which describe the status of $path and
its children.
If $recursive is true, recurse fully, else do only immediate children.
@@ -746,19 +849,23 @@
return the value of the actual revision against with the working copy was
compared. (The return will be undef if $update is not set).
-The function recurses into externals definitions ('svn:externals') after
-handling the main target, if any exist. The function calls the notify callback
-with $SVN::Wc::Notify::Action::status_external action before handling each
-externals definition, and with $SVN::Wc::Notify::Action::status_completed
-after each.
+Unless ignore_externals is set, the function recurses into externals definitions
+('svn:externals') after handling the main target, if any exist. The function
+calls the notify callback with $SVN::Wc::Notify::Action::status_external action
+before handling each externals definition, and with
+$SVN::Wc::Notify::Action::status_completed after each.
+
+$changelists is a reference to an array of changelist names, used as a restrictive filter on items whose statuses are reported; that is don't report status about any item unless it's a member of those changelists. If changelists is empty (or altogether undef), no changelist filtering occurs.
The status_func subroutine takes the following parameters:
-$path, $status
+$path, $status, $pool
$path is the pathname of the file or directory which status is being
-reported. $status is a svn_wc_status_t object.
+reported. $status is a svn_wc_status2_t object. $pool is an apr_pool_t
+object which is cleaned beteween invocations to the callback.
-The return of the status_func subroutine is ignored.
+The return of the status_func subroutine can be a svn_error_t object created by
+SVN::Error::create in order to propogate an error up.
=item $ctx-E<gt>info($path_or_url, $peg_revision, $revision, \&receiver, $recurse);
@@ -813,15 +920,73 @@
=item $ctx-E<gt>update($path, $revision, $recursive, $pool)
-Update a working copy $path to $revision.
+Similar to $ctx-E<gt>update2() except that it accepts only a single target in
+$path, returns a single revision, and $ignore_externals is always set to FALSE.
+
+=item $ctx-E<gt>update2($paths, $revision, $recursive, $ignore_externals, $pool)
+
+Similar to $ctx-E<gt>update3() but with $allow_unver_obstructions always set to
+FALSE, $depth_is_sticky to FALSE, and $depth set according to $recursive: if
+$recursive is TRUE, set $depth to $SVN::Depth::infinity, if $recursive is
+FALSE, set $depth to $SVN::Depth::files.
+
+=item $ctx-E<gt>update3($paths, $revision, $depth, $depth_is_sticky, $ignore_externals, $allow_unver_obstructions, $pool)
+
+Similar to $ctx-E<gt>update4() but with $make_parents always set to FALSE and
+$adds_as_modification set to TRUE.
+
+=item $ctx-E<gt>update4($paths, $revision, $depth, $depth_is_sticky, $ignore_externals, $allow_unver_obstructions, $adds_as_modification, $make_parents)
+
+Update working trees $paths to $revision.
+
+$paths is a array reference of paths to be updated. Unversioned paths that are
+the direct children of a versioned path will cause an update that attempts to
+add that path; other unversioned paths are skipped.
$revision must be a revision number, 'HEAD', or a date or this method will
raise the $SVN::Error::CLIENT_BAD_REVISION error.
+The paths in $paths can be from multiple working copies from multiple
+repositories, but even if they all come from the same repository there is no
+guarantee that revision represented by 'HEAD' will remain the same as each path
+is updated.
+
+If $ignore_externals is set, don't process externals definitions as part of
+this operation.
+
+If $depth is $SVN::Depth::infinity, update fully recursivelly. Else if it is
+$SVN::Depth::immediates or $SVN::Depth::files, update each target and its file
+entries, but not its subdirectories. Else if $SVN::Depth::empty, update
+exactly each target, nonrecursively (essentially, update the target's
+properties).
+
+If $depth is $SVN::Depth::unknown, take the working depth from $paths and then
+describe as behaved above.
+
+If $depth_is_sticky is set and $depth is not $SVN::Depth::unknown, then in
+addition to update paths, also set their sticky ambient depth value to $depth.
+
+If $allow_unver_obstructions is TRUE then the update tolerates existing
+unversioned items that obstruct added paths. Only obstructions of the same
+type (file or dir) as the added item are tolerated. The text of obstructing
+files is left as-is, effectively treating it as a user modification after the
+update. Working properties of obstructing items are set equal to the base
+properties. If $allow_unver_obstructions is FALSE then the update will abort
+if there are any unversioned obstructing items.
+
+If $adds_as_modification is TRUE, a local addition at the same path as an
+incoming addition of the same node kind results in a normal node with a
+possible local modification, instead of a tree conflict.
+
+If $make_parents is TRUE, create any non-existent parent directories also by
+checking them out at depth=empty.
+
Calls the notify callback for each item handled by the update, and
also for files restored from the text-base.
-Returns the revision to which the working copy was actually updated.
+Returns an array reference to an array of revision numbers with each element
+set to the revision to which $revision was resolved for the corresponding
+element of $paths.
=item $ctx-E<gt>url_from_path($target, $pool); or SVN::Client::url_from_path($target, $pool);
@@ -1265,41 +1430,41 @@
=over 8
-=item $info->URL()
+=item $info-E<gt>URL()
Where the item lives in the repository.
-=item $info->rev()
+=item $info-E<gt>rev()
The revision of the object. If path_or_url is a working-copy
path, then this is its current working revnum. If path_or_url
is a URL, then this is the repos revision that path_or_url lives in.
-=item $info->kind()
+=item $info-E<gt>kind()
The node's kind.
-=item $info->repos_root_URL()
+=item $info-E<gt>repos_root_URL()
The root URL of the repository.
-=item $info->repos_UUID()
+=item $info-E<gt>repos_UUID()
The repository's UUID.
-=item $info->last_changed_rev()
+=item $info-E<gt>last_changed_rev()
The last revision in which this object changed.
-=item $info->last_changed_date()
+=item $info-E<gt>last_changed_date()
The date of the last_changed_rev.
-=item $info->last_changed_author()
+=item $info-E<gt>last_changed_author()
The author of the last_changed_rev.
-=item $info->lock()
+=item $info-E<gt>lock()
An exclusive lock, if present. Could be either local or remote.
@@ -1311,27 +1476,27 @@
=over 8
-=item $info->has_wc_info()
+=item $info-E<gt>has_wc_info()
-=item $info->schedule()
+=item $info-E<gt>schedule()
-=item $info->copyfrom_url()
+=item $info-E<gt>copyfrom_url()
-=item $info->copyfrom_rev()
+=item $info-E<gt>copyfrom_rev()
-=item $info->text_time()
+=item $info-E<gt>text_time()
-=item $info->prop_time()
+=item $info-E<gt>prop_time()
-=item $info->checksum()
+=item $info-E<gt>checksum()
-=item $info->conflict_old()
+=item $info-E<gt>conflict_old()
-=item $info->conflict_new()
+=item $info-E<gt>conflict_new()
-=item $info->conflict_wrk()
+=item $info-E<gt>conflict_wrk()
-=item $info->prejfile()
+=item $info-E<gt>prejfile()
=back
@@ -1378,12 +1543,12 @@
$SVN::Client::COMMIT_ITEM_PROP_MODS
$SVN::Client::COMMIT_ITEM_IS_COPY
-=item $citem>incoming_prop_changes()
+=item $citem-E<gt>incoming_prop_changes()
A reference to an array of svn_prop_t objects representing changes to
WC properties.
-=item $citem>outgoing_prop_changes()
+=item $citem-E<gt>outgoing_prop_changes()
A reference to an array of svn_prop_t objects representing extra
changes to properties in the repository (which are not necessarily
diff --git a/subversion/bindings/swig/perl/native/Core.pm b/subversion/bindings/swig/perl/native/Core.pm
index 63e6c5a..c42ff29 100644
--- a/subversion/bindings/swig/perl/native/Core.pm
+++ b/subversion/bindings/swig/perl/native/Core.pm
@@ -710,6 +710,63 @@
=cut
+package SVN::Depth;
+use SVN::Base qw(Core svn_depth_);
+
+=head2 svn_depth_t - SVN::Depth
+
+An enum of the following constants:
+
+=over 4
+
+=item $SVN::Depth::unknown
+
+Depth undetermined or ignored. In some contexts, this means the client should
+choose an appropriate default depth. The server will generally treat it as
+$SVN::Depth::infinity.
+
+=item $SVN::Depth::exclude
+
+Exclude (i.e., don't descend into) directory D.
+
+Note: In Subversion 1.5, $SVN::Depth::exclude is B<not> supported anyhwere in
+the client-side (Wc/Client/etc) code; it is only supported as an argument to
+set_path functions in the Ra and Repos reporters. (This will enable future
+versions of Subversion to run updates, etc, against 1.5 servers with proper
+$SVN::Depth::exclude behavior, once we get a chance to implement client side
+support for $SVN::Depth::exclude).
+
+=item $SVN::Depth::empty
+
+Just the named directory D, no entries.
+
+Updates will not pull in any files or subdirectories not already present.
+
+=item $SVN::Depth::files
+
+D + its files children, but not subdirs.
+
+Updates will pull in any files not already present, but not subdirectories.
+
+=item $SVN::Depth::immediates
+
+D + immediate children (D and its entries).
+
+Updates will pull in any files or subdirectories not already present; those
+subdirectories' this_dir entries will have depth-empty.
+
+=item $SVN::Depth::infinity
+
+D + all descendants (full recursion from D).
+
+Updates will pull in any files or subdirectories not already present; those
+subdirectories' this_dir entries will have depth-infinity. Equivalent to the
+pre 1.5 default update behavior.
+
+=back
+
+=cut
+
package _p_svn_opt_revision_t;
use SVN::Base qw(Core svn_opt_revision_t_);
@@ -766,6 +823,37 @@
=cut
+package _p_svn_commit_info_t;
+use SVN::Base qw(Core svn_commit_info_t_);
+
+=head2 svn_commit_info_t
+
+=over 4
+
+=item $commit-E<gt>revision()
+
+Just committed revision.
+
+=item $commit-E<gt>date()
+
+Server-side date of the commit.
+
+=item $commit-E<gt>author()
+
+Author of the commit.
+
+=item $commit-E<gt>post_commit_err()
+
+Error message from the post-commit hook, or undef.
+
+=item $commit-E<gt>repos_root()
+
+Repoistory root, may be undef if unknown.
+
+=back
+
+=cut
+
package _p_svn_auth_cred_simple_t;
use SVN::Base qw(Core svn_auth_cred_simple_t_);
diff --git a/subversion/bindings/swig/perl/native/Fs.pm b/subversion/bindings/swig/perl/native/Fs.pm
index d39e81c..58b4b64 100644
--- a/subversion/bindings/swig/perl/native/Fs.pm
+++ b/subversion/bindings/swig/perl/native/Fs.pm
@@ -100,7 +100,7 @@
Generate a unique lock-token using C<$fs>.
TODO - translate this to apply to Perl:
-This can be used in to populate lock->token before calling
+This can be used in to populate lock-E<gt>token before calling
svn_fs_attach_lock().
=item $fs-E<gt>get_access()
diff --git a/subversion/bindings/swig/perl/native/Makefile.PL.in b/subversion/bindings/swig/perl/native/Makefile.PL.in
index d3c776f..1107377 100644
--- a/subversion/bindings/swig/perl/native/Makefile.PL.in
+++ b/subversion/bindings/swig/perl/native/Makefile.PL.in
@@ -49,6 +49,11 @@
my $cflags = '@CFLAGS@';
my $includes = '@SVN_APR_INCLUDES@ @SVN_APRUTIL_INCLUDES@';
+# Avoid this bug in SWIG:
+# https://sourceforge.net/tracker/?func=detail&aid=3571361&group_id=1645&atid=101645
+# SWIG is using C++ style comments in an extern "C" code.
+$cflags =~ s/-ansi\s+//g;
+
# According to the log of r7937, the flags guarded by the conditional break
# the build on FreeBSD if not conditionalized.
my $apr_ldflags = '@SVN_APR_LIBS@'
@@ -122,6 +127,9 @@
FULLPERLRUN=$fullperlrun \$(FULLPERL)
+update_test_counts ::
+\tfor test_file in \$(TEST_FILES); do \$(PERL) -MTest::Count::FileMutator::ByFileType::App -e 'run()' \$\$test_file; done
+
EOPOST
}
diff --git a/subversion/bindings/swig/perl/native/Repos.pm b/subversion/bindings/swig/perl/native/Repos.pm
index 89d1ca4..186f47b 100644
--- a/subversion/bindings/swig/perl/native/Repos.pm
+++ b/subversion/bindings/swig/perl/native/Repos.pm
@@ -87,9 +87,9 @@
close $fh;
-=item $repos->load_fs($dumpfile_fh, $feedback_fh, $uuid_action, $parent_dir, $cancel_func, $cancel_baton);
+=item $repos-E<gt>load_fs($dumpfile_fh, $feedback_fh, $uuid_action, $parent_dir, $cancel_func, $cancel_baton);
-=item $repos->load_fs2($dumpfile_fh, $feedback_fh, $uuid_action, $parent_dir, $use_pre_commit_hook, $use_post_commit_hook, $cancel_func, $cancel_baton);
+=item $repos-E<gt>load_fs2($dumpfile_fh, $feedback_fh, $uuid_action, $parent_dir, $use_pre_commit_hook, $use_post_commit_hook, $cancel_func, $cancel_baton);
Loads a dumpfile specified by the C<$dumpfile_fh> filehandle into the repository.
If the dumpstream contains copy history that is unavailable in the repository,
diff --git a/subversion/bindings/swig/perl/native/Wc.pm b/subversion/bindings/swig/perl/native/Wc.pm
index 0b61555..6ce7028 100644
--- a/subversion/bindings/swig/perl/native/Wc.pm
+++ b/subversion/bindings/swig/perl/native/Wc.pm
@@ -21,7 +21,7 @@
package _p_svn_wc_t;
-=head2 svn_wc_status_t
+=head2 svn_wc_status2_t
=over 4
@@ -67,10 +67,55 @@
An integer representing the status of the item's properties in the repository.
Can be one of the $SVN::Wc::Status::* constants.
+=item $wcstat-E<gt>repos_lock()
+
+A svn_lock_t object representing the entry's lock in the repository, if any.
+
+=item $wcstat-E<gt>url()
+
+The url (actual or expected) of the item.
+
+=item $wcstat-E<gt>ood_last_cmt_rev()
+
+An integer representing the youngest committed revision or $SVN::Core::INVALID_REVNUM is not out of date.
+
+=item $wcstat-E<gt>ood_last_cmt_date()
+
+The date of the most recent commit as microseconds since 00:00:00 January 1, 1970 UTC or 0 if not out of date.
+
+=item $wcstat-E<gt>ood_kind()
+
+An integer representing the kind of the youngest commit. Can be any of the $SVN::Node::* constants. Will be $SVN::Node::none if not out of date.
+
+=item $wcstat-E<gt>tree_conflict()
+
+A svn_wc_conflict_description_t object if the entry is the victim of a tree conflict or undef.
+
+=item $wcstat-E<gt>file_external()
+
+A boolean telling if the item is a file that was added to the working copy as an svn:externals. If file_external is TRUE, then switched is always FALSE.
+
+=item $wcstat-E<gt>pristine_text_status()
+
+An integer representing the status of the item's text as compared to the pristine base of the file. Can be one of the $SVN::Wc::Status::* constants.
+
+=item $wcstat-E<gt>pristine_prop_status()
+
+An integer representing the status of the item's properties as compared to the pristine base of the node. Can be one of the $SVN::Wc::Status::* constants.
+
=back
=cut
+package _p_svn_wc_status2_t;
+use SVN::Base qw(Wc svn_wc_status2_t_);
+
+=head2 svn_wc_status_t
+
+Same as svn_wc_status2_t, but without the repos_lock, url, ood_last_cmt_rev, ood_last_cmt_date, ood_kind, ood_last_cmt_author, tree_conflict, file_external, pristine_text_status, pristine_prop_status fields.
+
+=cut
+
package _p_svn_wc_status_t;
use SVN::Base qw(Wc svn_wc_status_t_);
@@ -430,6 +475,11 @@
=back
+=cut
+
+package SVN::Wc::Status;
+use SVN::Base qw(Wc svn_wc_status_);
+
=head1 COPYRIGHT
Licensed to the Apache Software Foundation (ASF) under one
@@ -451,7 +501,4 @@
=cut
-package SVN::Wc::Status;
-use SVN::Base qw(Wc svn_wc_status_);
-
1;
diff --git a/subversion/bindings/swig/perl/native/t/0use.t b/subversion/bindings/swig/perl/native/t/0use.t
index d45b096..9ed018a 100644
--- a/subversion/bindings/swig/perl/native/t/0use.t
+++ b/subversion/bindings/swig/perl/native/t/0use.t
@@ -22,10 +22,17 @@
use Test::More tests => 7;
use strict;
+# TEST
use_ok 'SVN::Core';
+# TEST
use_ok 'SVN::Repos';
+# TEST
use_ok 'SVN::Fs';
+# TEST
use_ok 'SVN::Delta';
+# TEST
use_ok 'SVN::Ra';
+# TEST
use_ok 'SVN::Wc';
+# TEST
use_ok 'SVN::Client';
diff --git a/subversion/bindings/swig/perl/native/t/1repos.t b/subversion/bindings/swig/perl/native/t/1repos.t
index a9799632..85abd2c 100644
--- a/subversion/bindings/swig/perl/native/t/1repos.t
+++ b/subversion/bindings/swig/perl/native/t/1repos.t
@@ -35,6 +35,7 @@
my $repos;
+# TEST
ok($repos = SVN::Repos::create("$repospath", undef, undef, undef, undef),
"create repository at $repospath");
@@ -60,6 +61,7 @@
$editor->close_edit();
+# TEST
cmp_ok($fs->youngest_rev, '==', 1);
{
$editor = SVN::Delta::Editor->
@@ -73,6 +75,7 @@
$editor->close_edit();
}
+# TEST
cmp_ok($fs->youngest_rev, '==', 2);
my @history;
@@ -80,6 +83,7 @@
SVN::Repos::history($fs, 'tags/foo/filea',
sub {push @history, [@_[0,1]]}, 0, 2, 1);
+# TEST
is_deeply(\@history, [['/tags/foo/filea',2],['/trunk/filea',1]],
'repos_history');
@@ -97,8 +101,10 @@
$editor->close_edit();
}
+# TEST
ok($main::something_destroyed, 'callback properly destroyed');
+# TEST
cmp_ok($fs->youngest_rev, '==', 3);
END {
diff --git a/subversion/bindings/swig/perl/native/t/2fs.t b/subversion/bindings/swig/perl/native/t/2fs.t
index 01e6427..43e40c7 100644
--- a/subversion/bindings/swig/perl/native/t/2fs.t
+++ b/subversion/bindings/swig/perl/native/t/2fs.t
@@ -36,24 +36,32 @@
my $repos;
+# TEST
ok($repos = SVN::Repos::create("$repospath", undef, undef, undef, undef),
"create repository at $repospath");
my $fs = $repos->fs;
+# TEST
cmp_ok($fs->youngest_rev, '==', 0,
"new repository start with rev 0");
+# TEST
is($fs->path, "$repospath/db", '$fs->path()');
+# TEST
is(SVN::Fs::type($fs->path), 'fsfs', 'SVN::Fs::type()');
my $txn = $fs->begin_txn($fs->youngest_rev);
my $txns = $fs->list_transactions;
+# TEST
ok(eq_array($fs->list_transactions, [$txn->name]), 'list transaction');
+# TEST
isa_ok($txn->root, '_p_svn_fs_root_t', '$txn->root()');
+# TEST
is($txn->root->txn_name, $txn->name, '$txn->root->txn_name()');
+# TEST
is($fs->revision_root($fs->youngest_rev)->txn_name, undef);
$txn->root->make_dir('trunk');
@@ -63,32 +71,43 @@
$txn->root->make_file($path);
{
my $stream = $txn->root->apply_text($path, undef);
+ # TEST
isa_ok($stream, 'SVN::Stream', '$txn->root->apply_text');
print $stream $text;
close $stream;
}
$txn->commit;
+# TEST
cmp_ok($fs->youngest_rev, '==', 1, 'revision increased');
my $root = $fs->revision_root($fs->youngest_rev);
+# TEST
cmp_ok($root->check_path($path), '==', $SVN::Node::file, 'check_path');
+# TEST
ok(!$root->is_dir($path), 'is_dir');
+# TEST
ok($root->is_file($path), 'is_file');
{
my $stream = $root->file_contents($path);
local $/;
+ # TEST
is(<$stream>, $text, 'content verified');
+ # TEST
is($root->file_md5_checksum($path), 'dd2314129f81675e95b940ff94ddc935',
'md5 verified');
}
+# TEST
cmp_ok($root->file_length($path), '==', length($text), 'file_length');
# Revision properties
+# TEST
isa_ok($fs->revision_proplist(1), 'HASH', 'revision_proplist: object');
+# TEST
is($fs->revision_prop(1, 'not:exists'), undef, 'revision_prop: nonexistent');
+# TEST
like($fs->revision_prop(1, 'svn:date'), qr/^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$/,
'revision_prop: svn:date');
@@ -108,12 +127,15 @@
or die "error making hook script '$script_filename' executable: $!";
$fs->change_rev_prop(1, 'test-prop', 'foo');
+ # TEST
is($fs->revision_prop(1, 'test-prop'), 'foo', 'change_rev_prop');
$fs->change_rev_prop(1, 'test-prop', undef);
+ # TEST
is($fs->revision_prop(1, 'test-prop'), undef, 'change_rev_prop: deleted');
$fs->change_rev_prop(1, 'binary-prop', $BINARY_DATA);
+ # TEST
is($fs->revision_prop(1, 'binary-prop'), $BINARY_DATA,
'change_rev_prop with binary data');
}
diff --git a/subversion/bindings/swig/perl/native/t/3client.t b/subversion/bindings/swig/perl/native/t/3client.t
index 697a380..5b88370 100644
--- a/subversion/bindings/swig/perl/native/t/3client.t
+++ b/subversion/bindings/swig/perl/native/t/3client.t
@@ -20,7 +20,7 @@
#
#
-use Test::More tests => 121;
+use Test::More tests => 221;
use strict;
# shut up about variables that are only used once.
@@ -28,9 +28,13 @@
# by the bindings but not elsewhere in perl space.
no warnings 'once';
+# TEST
use_ok('SVN::Core');
+# TEST
use_ok('SVN::Repos');
+# TEST
use_ok('SVN::Client');
+# TEST
use_ok('SVN::Wc'); # needed for status
use File::Spec::Functions;
use File::Temp qw(tempdir);
@@ -67,113 +71,236 @@
# This is ugly to create the test repo with SVN::Repos, but
# it seems to be the most reliable way.
+# TEST
ok(SVN::Repos::create("$repospath", undef, undef, undef, undef),
"create repository at $repospath");
my ($ctx) = SVN::Client->new;
+# TEST
isa_ok($ctx,'SVN::Client','Client Object');
my $uuid_from_url = $ctx->uuid_from_url($reposurl);
+# TEST
ok($uuid_from_url,'Valid return from uuid_from_url method form');
# test non method invocation passing a SVN::Client
+# TEST
ok(SVN::Client::uuid_from_url($reposurl,$ctx),
'Valid return from uuid_from_url function form with SVN::Client object');
# test non method invocation passing a _p_svn_client_ctx_t
+# TEST
ok(SVN::Client::uuid_from_url($reposurl,$ctx->{'ctx'}),
'Valid return from uuid_from_url function form with _p_svn_client_ctx object');
my ($ci_dir1) = $ctx->mkdir(["$reposurl/dir1"]);
+# TEST
isa_ok($ci_dir1,'_p_svn_client_commit_info_t');
$current_rev++;
+# TEST
is($ci_dir1->revision,$current_rev,"commit info revision equals $current_rev");
+my ($ci_dir2) = $ctx->mkdir2(["$reposurl/dir2"]);
+# TEST
+isa_ok($ci_dir2,'_p_svn_commit_info_t');
+$current_rev++;
+# TEST
+is($ci_dir2->revision,$current_rev,"commit info revision equals $current_rev");
+
+my ($ci_dir3) = $ctx->mkdir3(["$reposurl/dir3"],0,undef);
+# TEST
+isa_ok($ci_dir3,'_p_svn_commit_info_t');
+$current_rev++;
+# TEST
+is($ci_dir3->revision,$current_rev,"commit info revision equals $current_rev");
+
+# TEST
+is($ctx->mkdir4(["$reposurl/dir4"],0,undef,sub {
+ my ($commit_info) = @_;
+
+ # TEST
+ isa_ok($commit_info,'_p_svn_commit_info_t','commit_info type check');
+
+ # TEST
+ is($commit_info->revision(),$current_rev + 1, 'commit info revision');
+
+ # TEST
+ like($commit_info->date(),
+ qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/,
+ 'commit info date');
+
+ # TEST
+ is($commit_info->post_commit_err(),undef,'commit info post_commit_error');
+
+ # TEST
+ is($commit_info->repos_root(),$reposurl,'commit info repos_root');
+ }),
+ undef,'Returned undef from mkdir4 operation.');
+$current_rev++;
my ($rpgval,$rpgrev) = $ctx->revprop_get('svn:author',$reposurl,$current_rev);
+# TEST
is($rpgval,$username,'svn:author set to expected username from revprop_get');
+# TEST
is($rpgrev,$current_rev,'Returned revnum of current rev from revprop_get');
if ($^O eq 'MSWin32') {
+ # TEST
ok(open(NEW, ">$repospath/hooks/pre-revprop-change.bat"),
'Open pre-revprop-change hook for writing');
+ # TEST
ok(print(NEW 'exit 0'), 'Print to hook');
+ # TEST
ok(close(NEW), 'Close hook');
} else {
+ # TEST
ok(rename("$repospath/hooks/pre-revprop-change.tmpl",
"$repospath/hooks/pre-revprop-change"),
'Rename pre-revprop-change hook');
+ # TEST
ok(chmod(0700,"$repospath/hooks/pre-revprop-change"),
'Change permissions on pre-revprop-change hook');
+ # TEST
is(1, 1, '-')
}
my ($rps_rev) = $ctx->revprop_set('svn:log','mkdir dir1',
$reposurl, $current_rev, 0);
+# TEST
is($rps_rev,$current_rev,
'Returned revnum of current rev from revprop_set');
my ($rph, $rplrev) = $ctx->revprop_list($reposurl,$current_rev);
+# TEST
isa_ok($rph,'HASH','Returned hash reference form revprop_list');
+# TEST
is($rplrev,$current_rev,'Returned current rev from revprop_list');
+# TEST
is($rph->{'svn:author'},$username,
'svn:author is expected user from revprop_list');
+# TEST
is($rph->{'svn:log'},'mkdir dir1',
'svn:log is expected value from revprop_list');
+# TEST
ok($rph->{'svn:date'},'svn:date is set from revprop_list');
+# TEST
is($ctx->checkout($reposurl,$wcpath,'HEAD',1),$current_rev,
'Returned current rev from checkout');
+# TEST
+is($ctx->checkout2($reposurl,$wcpath . '2',undef,'HEAD',1,0),$current_rev,
+ 'Returned current rev from checkout2');
+
+# TEST
+is($ctx->checkout3($reposurl,$wcpath . '3',undef,'HEAD',$SVN::Depth::infinity,
+ 0,0),$current_rev, 'Returned current rev from checkout3');
+
+# TEST
is(SVN::Client::url_from_path($wcpath),$reposurl,
"Returned $reposurl from url_from_path");
+# TEST
ok(open(NEW, ">$wcpath/dir1/new"),'Open new file for writing');
+# TEST
ok(print(NEW 'addtest'), 'Print to new file');
+# TEST
ok(close(NEW),'Close new file');
# no return means success
+# TEST
is($ctx->add("$wcpath/dir1/new",0),undef,
'Returned undef from add schedule operation');
+# TEST
+ok(open(NEW2, ">$wcpath/dir1/new2"),'Open new2 file for writing');
+# TEST
+ok(print(NEW2 'addtest2'), 'Print to new2 file');
+# TEST
+ok(close(NEW2),'Close new2 file');
+
+# no return means success
+# TEST
+is($ctx->add2("$wcpath/dir1/new2",0,0),undef,
+ 'Returned undef from add2 schedule operation');
+
+# TEST
+ok(open(NEW3, ">$wcpath/dir1/new3"),'Open new3 file for writing');
+# TEST
+ok(print(NEW3 'addtest3'), 'Print to new3 file');
+# TEST
+ok(close(NEW3),'Close new3 file');
+
+# no return means success
+# TEST
+is($ctx->add3("$wcpath/dir1/new3",0,0,0),undef,
+ 'Returned undef from add3 schedule operation');
+
+# TEST
+ok(open(NEW4, ">$wcpath/dir1/new4"),'Open new4 file for writing');
+# TEST
+ok(print(NEW4 'addtest4'), 'Print to new4 file');
+# TEST
+ok(close(NEW4),'Close new4 file');
+
+# no return means success
+# TEST
+is($ctx->add4("$wcpath/dir1/new4",$SVN::Depth::empty,0,0,0),undef,
+ 'Returned undef from add4 schedule operation');
+
+
# test the log_msg callback
$ctx->log_msg(
sub
{
my ($log_msg,$tmp_file,$commit_items,$pool) = @_;
+ # TEST
isa_ok($log_msg,'SCALAR','log_msg param to callback is a SCALAR');
+ # TEST
isa_ok($tmp_file,'SCALAR','tmp_file param to callback is a SCALAR');
+ # TEST
isa_ok($commit_items,'ARRAY',
'commit_items param to callback is a SCALAR');
+ # TEST
isa_ok($pool,'_p_apr_pool_t',
'pool param to callback is a _p_apr_pool_t');
my $commit_item = shift @$commit_items;
+ # TEST
isa_ok($commit_item,'_p_svn_client_commit_item3_t',
'commit_item element is a _p_svn_client_commit_item3_t');
+ # TEST
is($commit_item->path(),"$wcpath/dir1/new",
"commit_item has proper path for committed file");
+ # TEST
is($commit_item->kind(),$SVN::Node::file,
"kind() shows the node as a file");
+ # TEST
is($commit_item->url(),"$reposurl/dir1/new",
'URL matches our repos url');
# revision is INVALID because the commit has not happened yet
# and this is not a copy
+ # TEST
is($commit_item->revision(),$SVN::Core::INVALID_REVNUM,
'Revision is INVALID since commit has not happened yet');
+ # TEST
is($commit_item->copyfrom_url(),undef,
'copyfrom_url is undef since file is not a copy');
+ # TEST
is($commit_item->state_flags(),$SVN::Client::COMMIT_ITEM_ADD |
$SVN::Client::COMMIT_ITEM_TEXT_MODS,
'state_flags are ADD and TEXT_MODS');
my $prop_changes = $commit_item->incoming_prop_changes();
+ # TEST
isa_ok($prop_changes, 'ARRAY',
'incoming_prop_changes returns an ARRAY');
+ # TEST
is(scalar(@$prop_changes), 0,
'No elements in the incoming_prop_changes array because ' .
' we did not make any');
$prop_changes = $commit_item->outgoing_prop_changes();
+ # TEST
is($prop_changes, undef,
'No outgoing_prop_changes array because we did not create one');
$$log_msg = 'Add new';
@@ -182,23 +309,30 @@
my ($ci_commit1) = $ctx->commit($wcpath,0);
+# TEST
isa_ok($ci_commit1,'_p_svn_client_commit_info_t',
'Commit returns a _p_svn_client_commit_info');
$current_rev++;
+# TEST
is($ci_commit1->revision,$current_rev,
"commit info revision equals $current_rev");
# get rid of log_msg callback
+# TEST
is($ctx->log_msg(undef),undef,
'Clearing the log_msg callback works');
# test info() on WC
+# TEST
is($ctx->info("$wcpath/dir1/new", undef, 'WORKING',
sub
{
my($infopath,$svn_info_t,$pool) = @_;
+ # TEST
is($infopath,"new",'path passed to receiver is same as WC');
+ # TEST
isa_ok($svn_info_t,'_p_svn_info_t');
+ # TEST
isa_ok($pool,'_p_apr_pool_t',
'pool param is _p_apr_pool_t');
}, 0),
@@ -206,75 +340,416 @@
'info should return undef');
my $svn_error = $ctx->info("$wcpath/dir1/newxyz", undef, 'WORKING', sub {}, 0);
+# TEST
isa_ok($svn_error, '_p_svn_error_t',
'info should return _p_svn_error_t for a nonexistent file');
$svn_error->clear(); #don't leak this
# test getting the log
+# TEST
is($ctx->log("$reposurl/dir1/new",$current_rev,$current_rev,1,0,
sub
{
my ($changed_paths,$revision,
$author,$date,$message,$pool) = @_;
+ # TEST
isa_ok($changed_paths,'HASH',
'changed_paths param is a HASH');
+ # TEST
isa_ok($changed_paths->{'/dir1/new'},
'_p_svn_log_changed_path_t',
'Hash value is a _p_svn_log_changed_path_t');
+ # TEST
is($changed_paths->{'/dir1/new'}->action(),'A',
'action returns A for add');
+ # TEST
is($changed_paths->{'/dir1/new'}->copyfrom_path(),undef,
'copyfrom_path returns undef as it is not a copy');
+ # TEST
is($changed_paths->{'/dir1/new'}->copyfrom_rev(),
$SVN::Core::INVALID_REVNUM,
'copyfrom_rev is set to INVALID as it is not a copy');
+ # TEST
is($revision,$current_rev,
'revision param matches current rev');
+ # TEST
is($author,$username,
'author param matches expected username');
+ # TEST
ok($date,'date param is defined');
+ # TEST
is($message,'Add new',
'message param is the expected value');
+ # TEST
isa_ok($pool,'_p_apr_pool_t',
'pool param is _p_apr_pool_t');
}),
undef,
'log returns undef');
+# TEST
is($ctx->update($wcpath,'HEAD',1),$current_rev,
'Return from update is the current rev');
+my $update2_result = $ctx->update2([$wcpath],'HEAD',1,0);
+# TEST
+isa_ok($update2_result,'ARRAY','update2 returns a list');
+# TEST
+is(scalar(@$update2_result),1,'update2 member count');
+# TEST
+is($update2_result->[0],$current_rev,'return from update2 is the current rev');
+
+my $update3_result = $ctx->update3([$wcpath],'HEAD',$SVN::Depth::infinity,
+ 0,0,0);
+# TEST
+isa_ok($update3_result,'ARRAY','update3 returns a list');
+# TEST
+is(scalar(@$update3_result),1,'update3 member count');
+# TEST
+is($update3_result->[0],$current_rev,'return from update3 is the current rev');
+
+my $update4_result = $ctx->update4([$wcpath],'HEAD',$SVN::Depth::infinity,
+ 0,0,0,1,0);
+# TEST
+isa_ok($update4_result,'ARRAY','update4 returns a list');
+# TEST
+is(scalar(@$update4_result),1,'update4 member count');
+# TEST
+is($update4_result->[0],$current_rev,'return from update4 is the current rev');
+
# no return so we should get undef as the result
# we will get a _p_svn_error_t if there is an error.
+# TEST
is($ctx->propset('perl-test','test-val',"$wcpath/dir1",0),undef,
'propset on a working copy path returns undef');
my ($ph) = $ctx->propget('perl-test',"$wcpath/dir1",undef,0);
+# TEST
isa_ok($ph,'HASH','propget returns a hash');
+# TEST
is($ph->{"$wcpath/dir1"},'test-val','perl-test property has the correct value');
# No revnum for the working copy so we should get INVALID_REVNUM
+# TEST
is($ctx->status($wcpath, undef, sub {
my ($path,$wc_status) = @_;
+ # TEST
is($path,"$wcpath/dir1",
'path param to status callback is' .
- 'the correct path.');
+ ' the correct path.');
+ # TEST
isa_ok($wc_status,'_p_svn_wc_status_t',
'wc_stats param is a' .
' _p_svn_wc_status_t');
+ # TEST
+ is($wc_status->text_status(),
+ $SVN::Wc::Status::normal,
+ 'text_status param to status' .
+ ' callback');
+ # TEST
is($wc_status->prop_status(),
- $SVN::Wc::status_modified,
- 'prop_status is status_modified');
- # TODO test the rest of the members
+ $SVN::Wc::Status::modified,
+ 'prop_status param to status' .
+ ' callback');
+ # TEST
+ is($wc_status->locked(), 0,
+ 'locked param to status callback');
+ # TEST
+ is($wc_status->copied(), 0,
+ 'copied param to status callback');
+ # TEST
+ is($wc_status->switched(), 0,
+ 'switched param to status callback');
+ # TEST
+ is($wc_status->repos_text_status(),
+ $SVN::Wc::Status::none,
+ 'repos_text_status param to status' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_prop_status(),
+ $SVN::Wc::Status::none,
+ 'repos_prop_status param to status' .
+ ' callback');
},
1, 0, 0, 0),
$SVN::Core::INVALID_REVNUM,
'status returns INVALID_REVNUM when run against a working copy');
+# No revnum for the working copy so we should get INVALID_REVNUM
+# TEST
+is($ctx->status2($wcpath, undef, sub {
+ my ($path,$wc_status) = @_;
+ # TEST
+ is($path,"$wcpath/dir1",
+ 'path param to status2 callback');
+ # TEST
+ isa_ok($wc_status,'_p_svn_wc_status2_t',
+ 'wc_stats param to the status2' .
+ ' callback');
+ # TEST
+ is($wc_status->text_status(),
+ $SVN::Wc::Status::normal,
+ 'text_status param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->prop_status(),
+ $SVN::Wc::Status::modified,
+ 'prop_status param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->locked(), 0,
+ 'locked param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->copied(), 0,
+ 'copied param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->switched(), 0,
+ 'switched param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_text_status(),
+ $SVN::Wc::Status::none,
+ 'repos_text_status param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_prop_status(),
+ $SVN::Wc::Status::none,
+ 'repos_prop_status param to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_lock(), undef,
+ 'repos_lock param to status2 callback');
+ # TEST
+ is($wc_status->url(),"$reposurl/dir1",
+ 'url param to status2 callback');
+ # TEST
+ is($wc_status->ood_last_cmt_rev(),
+ $SVN::Core::INVALID_REVNUM,
+ 'ood_last_cmt_rev to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->ood_last_cmt_date(), 0,
+ 'ood_last_cmt_date to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->ood_kind(),
+ $SVN::Node::none,
+ 'ood_kind param to status2 callback');
+ # TEST
+ is($wc_status->ood_last_cmt_author(),
+ undef,
+ 'ood_last_cmt_author to status2' .
+ ' callback');
+ # TEST
+ is($wc_status->tree_conflict(), undef,
+ 'tree_conflict to status2 callback');
+ # TEST
+ is($wc_status->file_external(), 0,
+ 'file_external to status2 callback');
+ # TEST
+ is($wc_status->pristine_text_status(),
+ $SVN::Wc::Status::normal,
+ 'pristine_text_status param to' .
+ ' status2 callback');
+ # TEST
+ is($wc_status->pristine_prop_status(),
+ $SVN::Wc::Status::modified,
+ 'pristine_prop_status param to' .
+ ' status2 callback');
+ },
+ 1, 0, 0, 0, 0),
+ $SVN::Core::INVALID_REVNUM,
+ 'status2 returns INVALID_REVNUM when run against a working copy');
+
+# No revnum for the working copy so we should get INVALID_REVNUM
+# TEST
+is($ctx->status3($wcpath, undef, sub {
+ my ($path,$wc_status) = @_;
+ # TEST
+ is($path,"$wcpath/dir1",
+ 'path param to status3 callback');
+ # TEST
+ isa_ok($wc_status,'_p_svn_wc_status2_t',
+ 'wc_stats param to the status3' .
+ ' callback');
+ # TEST
+ is($wc_status->text_status(),
+ $SVN::Wc::Status::normal,
+ 'text_status param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->prop_status(),
+ $SVN::Wc::Status::modified,
+ 'prop_status param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->locked(), 0,
+ 'locked param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->copied(), 0,
+ 'copied param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->switched(), 0,
+ 'switched param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_text_status(),
+ $SVN::Wc::Status::none,
+ 'repos_text_status param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_prop_status(),
+ $SVN::Wc::Status::none,
+ 'repos_prop_status param to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_lock(), undef,
+ 'repos_lock param to status3 callback');
+ # TEST
+ is($wc_status->url(),"$reposurl/dir1",
+ 'url param to status3 callback');
+ # TEST
+ is($wc_status->ood_last_cmt_rev(),
+ $SVN::Core::INVALID_REVNUM,
+ 'ood_last_cmt_rev to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->ood_last_cmt_date(), 0,
+ 'ood_last_cmt_date to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->ood_kind(),
+ $SVN::Node::none,
+ 'ood_kind param to status3 callback');
+ # TEST
+ is($wc_status->ood_last_cmt_author(),
+ undef,
+ 'ood_last_cmt_author to status3' .
+ ' callback');
+ # TEST
+ is($wc_status->tree_conflict(), undef,
+ 'tree_conflict to status3 callback');
+ # TEST
+ is($wc_status->file_external(), 0,
+ 'file_external to status3 callback');
+ # TEST
+ is($wc_status->pristine_text_status(),
+ $SVN::Wc::Status::normal,
+ 'pristine_text_status param to' .
+ ' status3 callback');
+ # TEST
+ is($wc_status->pristine_prop_status(),
+ $SVN::Wc::Status::modified,
+ 'pristine_prop_status param to' .
+ ' status3 callback');
+ },
+ $SVN::Depth::infinity, 0, 0, 0, 0, undef),
+ $SVN::Core::INVALID_REVNUM,
+ 'status3 returns INVALID_REVNUM when run against a working copy');
+
+# No revnum for the working copy so we should get INVALID_REVNUM
+# TEST
+is($ctx->status4($wcpath, undef, sub {
+ my ($path,$wc_status, $pool) = @_;
+ # TEST
+ is($path,"$wcpath/dir1",
+ 'path param to status4 callback');
+ # TEST
+ isa_ok($wc_status,'_p_svn_wc_status2_t',
+ 'wc_stats param to the status4' .
+ ' callback');
+ # TEST
+ is($wc_status->text_status(),
+ $SVN::Wc::Status::normal,
+ 'text_status param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->prop_status(),
+ $SVN::Wc::Status::modified,
+ 'prop_status param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->locked(), 0,
+ 'locked param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->copied(), 0,
+ 'copied param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->switched(), 0,
+ 'switched param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_text_status(),
+ $SVN::Wc::Status::none,
+ 'repos_text_status param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_prop_status(),
+ $SVN::Wc::Status::none,
+ 'repos_prop_status param to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->repos_lock(), undef,
+ 'repos_lock param to status4 callback');
+ # TEST
+ is($wc_status->url(),"$reposurl/dir1",
+ 'url param to status4 callback');
+ # TEST
+ is($wc_status->ood_last_cmt_rev(),
+ $SVN::Core::INVALID_REVNUM,
+ 'ood_last_cmt_rev to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->ood_last_cmt_date(), 0,
+ 'ood_last_cmt_date to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->ood_kind(),
+ $SVN::Node::none,
+ 'ood_kind param to status4 callback');
+ # TEST
+ is($wc_status->ood_last_cmt_author(),
+ undef,
+ 'ood_last_cmt_author to status4' .
+ ' callback');
+ # TEST
+ is($wc_status->tree_conflict(), undef,
+ 'tree_conflict to status4 callback');
+ # TEST
+ is($wc_status->file_external(), 0,
+ 'file_external to status4 callback');
+ # TEST
+ is($wc_status->pristine_text_status(),
+ $SVN::Wc::Status::normal,
+ 'pristine_text_status param to' .
+ ' status4 callback');
+ # TEST
+ is($wc_status->pristine_prop_status(),
+ $SVN::Wc::Status::modified,
+ 'pristine_prop_status param to' .
+ ' status4 callback');
+ # TEST
+ isa_ok($pool, '_p_apr_pool_t',
+ 'pool param to status4' .
+ ' callback');
+ },
+ $SVN::Depth::infinity, 0, 0, 0, 0, undef),
+ $SVN::Core::INVALID_REVNUM,
+ 'status4 returns INVALID_REVNUM when run against a working copy');
+
+
my ($ci_commit2) = $ctx->commit($wcpath,0);
+# TEST
isa_ok($ci_commit2,'_p_svn_client_commit_info_t',
'commit returns a _p_svn_client_commit_info_t');
$current_rev++;
+# TEST
is($ci_commit2->revision(),$current_rev,
"commit info revision equals $current_rev");
@@ -282,74 +757,100 @@
my($pl) = $ctx->proplist($reposurl,$current_rev,1);
+# TEST
isa_ok($pl,'ARRAY','proplist returns an ARRAY');
+# TEST
isa_ok($pl->[0], '_p_svn_client_proplist_item_t',
'array element is a _p_svn_client_proplist_item_t');
+# TEST
is($pl->[0]->node_name(),"$reposurl/dir1",
'node_name is the expected value');
my $plh = $pl->[0]->prop_hash();
+# TEST
isa_ok($plh,'HASH',
'prop_hash returns a HASH');
+# TEST
is_deeply($plh, {'perl-test' => 'test-val'}, 'test prop list prop_hash values');
# add a dir to test update
-my ($ci_dir2) = $ctx->mkdir(["$reposurl/dir2"]);
-isa_ok($ci_dir2,'_p_svn_client_commit_info_t',
+my ($ci_dir5) = $ctx->mkdir(["$reposurl/dir5"]);
+# TEST
+isa_ok($ci_dir5,'_p_svn_client_commit_info_t',
'mkdir returns a _p_svn_client_commit_info_t');
$current_rev++;
-is($ci_dir2->revision(),$current_rev,
+# TEST
+is($ci_dir5->revision(),$current_rev,
"commit info revision equals $current_rev");
# Use explicit revnum to test that instead of just HEAD.
+# TEST
is($ctx->update($wcpath,$current_rev,$current_rev),$current_rev,
'update returns current rev');
# commit action against a repo returns undef
+# TEST
is($ctx->delete(["$wcpath/dir2"],0),undef,
'delete returns undef');
# no return means success
+# TEST
is($ctx->revert($wcpath,1),undef,
'revert returns undef');
my ($ci_copy) = $ctx->copy("$reposurl/dir1",2,"$reposurl/dir3");
+# TEST
isa_ok($ci_copy,'_p_svn_client_commit_info_t',
'copy returns a _p_svn_client_commitn_info_t when run against repo');
$current_rev++;
+# TEST
is($ci_copy->revision,$current_rev,
"commit info revision equals $current_rev");
+# TEST
ok(mkdir($importpath),'Make import path dir');
+# TEST
ok(open(FOO, ">$importpath/foo"),'Open file for writing in import path dir');
+# TEST
ok(print(FOO 'foobar'),'Print to the file in import path dir');
+# TEST
ok(close(FOO),'Close file in import path dir');
my ($ci_import) = $ctx->import($importpath,$reposurl,0);
+# TEST
isa_ok($ci_import,'_p_svn_client_commit_info_t',
'Import returns _p_svn_client_commint_info_t');
$current_rev++;
+# TEST
is($ci_import->revision,$current_rev,
"commit info revision equals $current_rev");
+# TEST
is($ctx->blame("$reposurl/foo",'HEAD','HEAD', sub {
my ($line_no,$rev,$author,
$date, $line,$pool) = @_;
+ # TEST
is($line_no,0,
'line_no param is zero');
+ # TEST
is($rev,$current_rev,
'rev param is current rev');
+ # TEST
is($author,$username,
'author param is expected' .
'value');
+ # TEST
ok($date,'date is defined');
if ($^O eq 'MSWin32') {
#### Why two \r-s?
+ # TEST
is($line,"foobar\r\r",
'line is expected value');
} else {
+ # TEST
is($line,'foobar',
'line is expected value');
}
+ # TEST
isa_ok($pool,'_p_apr_pool_t',
'pool param is ' .
'_p_apr_pool_t');
@@ -357,42 +858,58 @@
undef,
'blame returns undef');
+# TEST
ok(open(CAT, "+>$testpath/cattest"),'open file for cat output');
+# TEST
is($ctx->cat(\*CAT, "$reposurl/foo", 'HEAD'),undef,
'cat returns undef');
+# TEST
ok(seek(CAT,0,0),
'seek the beginning of the cat file');
+# TEST
is(readline(*CAT),'foobar',
'read the first line of the cat file');
+# TEST
ok(close(CAT),'close cat file');
# the string around the $current_rev exists to expose a past
# bug. In the past we did not accept values that simply
# had not been converted to a number yet.
my ($dirents) = $ctx->ls($reposurl,"$current_rev", 1);
+# TEST
isa_ok($dirents, 'HASH','ls returns a HASH');
+# TEST
isa_ok($dirents->{'dir1'},'_p_svn_dirent_t',
'hash value is a _p_svn_dirent_t');
+# TEST
is($dirents->{'dir1'}->kind(),$SVN::Core::node_dir,
'kind() returns a dir node');
+# TEST
is($dirents->{'dir1'}->size(),0,
'size() returns 0 for a directory');
+# TEST
is($dirents->{'dir1'}->has_props(),1,
'has_props() returns true');
+# TEST
is($dirents->{'dir1'}->created_rev(),$dir1_rev,
'created_rev() returns expected rev');
+# TEST
ok($dirents->{'dir1'}->time(),
'time is defined');
#diag(scalar(localtime($dirents->{'dir1'}->time() / 1000000)));
+# TEST
is($dirents->{'dir1'}->last_author(),$username,
'last_auth() returns expected username');
# test removing a property
+# TEST
is($ctx->propset('perl-test', undef, "$wcpath/dir1", 0),undef,
'propset returns undef');
my ($ph2) = $ctx->propget('perl-test', "$wcpath/dir1", 'WORKING', 0);
+# TEST
isa_ok($ph2,'HASH','propget returns HASH');
+# TEST
is(scalar(keys %$ph2),0,
'No properties after deleting a property');
@@ -455,6 +972,7 @@
my $oldauthbaton = $ctx->auth();
+ # TEST
isa_ok($ctx->auth(SVN::Client::get_simple_prompt_provider(
sub { simple_prompt(@_,'x') },2),
SVN::Client::get_ssl_server_trust_prompt_provider(
@@ -469,9 +987,11 @@
# if this doesn't work we will get an svn_error_t so by
# getting a hash we know it worked.
my ($dirents) = $ctx->ls('https://localhost/svn/test','HEAD',1);
+ # TEST
isa_ok($dirents,'HASH','ls returns a HASH');
# return the auth baton to its original setting
+ # TEST
isa_ok($ctx->auth($oldauthbaton),'_p_svn_auth_baton_t',
'Successfully set auth_baton back to old value');
}
@@ -487,6 +1007,7 @@
foreach my $p (@providers) {
$ok &= defined($p) && $p->isa('_p_svn_auth_provider_object_t');
}
+# TEST
ok($ok, 'svn_auth_get_platform_specific_client_providers returns _p_svn_auth_provider_object_t\'s');
SKIP: {
@@ -508,6 +1029,7 @@
my $callback = \&gnome_keyring_unlock_prompt;
my $result = SVN::Core::auth_set_gnome_keyring_unlock_prompt_func(
$ctx->auth(), $callback);
+ # TEST
is(${$result}, $callback, 'auth_set_gnome_keyring_unlock_prompt_func result equals paramter');
}
diff --git a/subversion/bindings/swig/perl/native/t/4pool.t b/subversion/bindings/swig/perl/native/t/4pool.t
index dabb947..4ad6f03 100644
--- a/subversion/bindings/swig/perl/native/t/4pool.t
+++ b/subversion/bindings/swig/perl/native/t/4pool.t
@@ -55,6 +55,7 @@
my $repos;
+# TEST
ok($repos = SVN::Repos::create("$repospath", undef, undef, undef, undef),
"create repository at $repospath");
@@ -62,19 +63,23 @@
my $pool = SVN::Pool->new_default;
+# TEST
is_pool_default($pool, 'default pool');
{
my $spool = SVN::Pool->new_default_sub;
+ # TEST
is_pool_default($spool, 'lexical default pool default');
}
+# TEST
is_pool_default($pool, 'lexical default pool destroyed');
my $root = $fs->revision_root(0);
my $txn = $fs->begin_txn(0);
+# TEST
$txn->root->make_dir('trunk');
$txn->commit;
@@ -86,6 +91,7 @@
undef, 1, 1, 0, 1);
+# TEST
is_pool_default($pool, 'default pool from c calls destroyed');
END {
diff --git a/subversion/bindings/swig/perl/native/t/5delta-compat.t b/subversion/bindings/swig/perl/native/t/5delta-compat.t
index 3ec473d..f11f125 100644
--- a/subversion/bindings/swig/perl/native/t/5delta-compat.t
+++ b/subversion/bindings/swig/perl/native/t/5delta-compat.t
@@ -33,11 +33,13 @@
my $txstream = SVN::TxDelta::new($source, $target);
+# TEST
isa_ok($txstream, '_p_svn_txdelta_stream_t');
open my $asource, '<', \$srctext;
my $handle = [SVN::TxDelta::apply($asource, $aresult, undef, undef)];
SVN::TxDelta::send_txstream($txstream, @$handle);
+# TEST
is($result, $tgttext, 'delta self test');
diff --git a/subversion/bindings/swig/perl/native/t/5delta.t b/subversion/bindings/swig/perl/native/t/5delta.t
index e955de5..a4f7877 100644
--- a/subversion/bindings/swig/perl/native/t/5delta.t
+++ b/subversion/bindings/swig/perl/native/t/5delta.t
@@ -33,12 +33,15 @@
my $txstream = SVN::TxDelta::new($source, $target);
+# TEST
isa_ok($txstream, '_p_svn_txdelta_stream_t');
open my $asource, '<', \$srctext;
my ($md5, @handle) = SVN::TxDelta::apply($asource, $aresult, undef);
SVN::TxDelta::send_txstream($txstream, @handle);
+# TEST
is($result, $tgttext, 'delta self test');
+# TEST
is("$md5", 'a22b3dadcbddac48d2f1eae3ec5fb86a', 'md5 matched');
diff --git a/subversion/bindings/swig/perl/native/t/6ra.t b/subversion/bindings/swig/perl/native/t/6ra.t
index 25772d0..8c9c09f 100644
--- a/subversion/bindings/swig/perl/native/t/6ra.t
+++ b/subversion/bindings/swig/perl/native/t/6ra.t
@@ -36,6 +36,7 @@
my $repospath = tempdir('svn-perl-test-XXXXXX', TMPDIR => 1, CLEANUP => 1);
my $repos;
+# TEST
ok($repos = SVN::Repos::create("$repospath", undef, undef, undef, undef),
"create repository at $repospath");
@@ -61,25 +62,36 @@
{
my $ra = SVN::Ra->new($uri);
+ # TEST
isa_ok($ra, 'SVN::Ra', 'create with only one argument');
}
my $ra = SVN::Ra->new(url => $uri);
+# TEST
isa_ok($ra, 'SVN::Ra', 'create with hash param');
+# TEST
is($ra->get_uuid, $fs->get_uuid, 'get_uuid');
+# TEST
is($ra->get_latest_revnum, 2, 'get_latest_revnum');
+# TEST
is($ra->get_repos_root, $uri, 'get_repos_root');
# get_dir
{
my ($dirents, $revnum, $props) = $ra->get_dir('trunk',
$SVN::Core::INVALID_REVNUM);
+ # TEST
isa_ok($dirents, 'HASH', 'get_dir: dirents');
+ # TEST
is(scalar(keys %$dirents), 2, 'get_dir: num dirents');
+ # TEST+2
isa_ok($dirents->{$_}, '_p_svn_dirent_t', "get_dir: dirent $_")
for qw( filea fileb );
+ # TEST
is($revnum, $ra->get_latest_revnum, 'get_dir: revnum');
+ # TEST
isa_ok($props, 'HASH', 'get_dir: props');
+ # TEST
is($props->{'dir-prop'}, 'frob', 'get_dir: property dir-prop');
}
@@ -87,15 +99,22 @@
{
my ($revnum, $props) = $ra->get_file('trunk/filea',
$SVN::Core::INVALID_REVNUM, undef);
+ # TEST
is($revnum, $ra->get_latest_revnum, 'get_file: revnum');
+ # TEST
isa_ok($props, 'HASH', 'get_file: props');
+ # TEST
ok(!exists $props->{'test-prop'}, 'get_file: property test-prop deleted');
+ # TEST
is($props->{'binary-prop'}, $BINARY_DATA, 'get_file: property binary-prop');
}
# Revision properties
+# TEST
isa_ok($ra->rev_proplist(1), 'HASH', 'rev_proplist: object');
+# TEST
is($ra->rev_prop(1, 'nonexistent'), undef, 'rev_prop: nonexistent');
+# TEST
like($ra->rev_prop(1, 'svn:date'), qr/^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$/,
'rev_prop: svn:date');
@@ -115,51 +134,71 @@
or die "error making hook script '$script_filename' executable: $!";
$ra->change_rev_prop(1, 'test-prop', 'foo');
+ # TEST
is($ra->rev_prop(1, 'test-prop'), 'foo', 'change_rev_prop');
$ra->change_rev_prop(1, 'test-prop', undef);
+ # TEST
is($ra->rev_prop(1, 'test-prop'), undef, 'change_rev_prop: deleted');
$ra->change_rev_prop(1, 'binary-prop', $BINARY_DATA);
+ # TEST
is($ra->rev_prop(1, 'binary-prop'), $BINARY_DATA,
'change_rev_prop with binary data');
}
# Information about nodes in the filesystem.
+# TEST
is($ra->check_path('trunk', 1), $SVN::Node::dir, 'check_path');
{
my $dirent = $ra->stat('trunk', 1);
+ # TEST
isa_ok($dirent, '_p_svn_dirent_t', 'stat dir: dirent object');
+ # TEST
is($dirent->kind, $SVN::Node::dir, 'stat dir: kind');
+ # TEST
is($dirent->size, 0, 'stat dir: size');
+ # TEST
is($dirent->created_rev, 1, 'stat dir: created_rev');
+ # TEST
ok($dirent->has_props, 'stat dir: has_props');
$dirent = $ra->stat('trunk/fileb', 1);
+ # TEST
is($dirent->kind, $SVN::Node::file, 'stat file: kind');
+ # TEST
ok(!$dirent->has_props, 'stat file: has_props');
}
# do_update
my $ed = MockEditor->new;
my $reporter = $ra->do_update(2, '', 1, $ed);
+# TEST
isa_ok($reporter, 'SVN::Ra::Reporter');
$reporter->set_path('', 0, 1, undef);
$reporter->finish_report;
+# TEST
is($ed->{_base_revnum}, 0, 'do_update: base_revision');
+# TEST
is($ed->{_target_revnum}, 2, 'do_update: target_revnum');
+# TEST
is($ed->{trunk}{props}{'dir-prop'}, 'frob', 'do_update: dir-prop');
+# TEST
ok(!exists $ed->{'trunk/filea'}{props}{'test-prop'},
'do_update: deleted property');
+# TEST
is($ed->{'trunk/filea'}{props}{'binary-prop'}, $BINARY_DATA,
'do_update: binary-prop');
# replay
$ed = MockEditor->new;
$ra->replay(1, 0, 1, $ed);
+# TEST
is($ed->{trunk}{type}, 'dir', "replay: got trunk");
+# TEST
is($ed->{trunk}{props}{'dir-prop'}, 'frob', 'replay: dir-prop');
+# TEST
is($ed->{'trunk/filea'}{props}{'binary-prop'}, $BINARY_DATA,
'replay: binary-prop');
diff --git a/subversion/bindings/swig/perl/native/t/7editor.t b/subversion/bindings/swig/perl/native/t/7editor.t
index 9a050e2..4e4c703 100644
--- a/subversion/bindings/swig/perl/native/t/7editor.t
+++ b/subversion/bindings/swig/perl/native/t/7editor.t
@@ -62,6 +62,7 @@
MyEditor->new(crap => bless {}, 'something'),
undef, 1, 1, 0, 0);
};
+# TEST
ok($main::something_destroyed, 'editor');
package something;
diff --git a/subversion/bindings/swig/perl/native/t/8lock.t b/subversion/bindings/swig/perl/native/t/8lock.t
index 4de01b9..4830d85 100644
--- a/subversion/bindings/swig/perl/native/t/8lock.t
+++ b/subversion/bindings/swig/perl/native/t/8lock.t
@@ -37,12 +37,14 @@
my $repos;
+# TEST
ok($repos = SVN::Repos::create("$repospath", undef, undef, undef, undef),
"create repository at $repospath");
my $fs = $repos->fs;
my $acc = SVN::Fs::create_access('foo');
+# TEST
is($acc->get_username, 'foo');
$fs->set_access($acc);
@@ -58,11 +60,15 @@
$fs->lock('/testfile', $token, 'we hate software', 0, 0, $fs->youngest_rev, 0);
+# TEST
ok(my $lock = $fs->get_lock('/testfile'));
+# TEST
is($lock->token, $token);
+# TEST
is($lock->owner, 'foo');
$acc = SVN::Fs::create_access('fnord');
+# TEST
is($acc->get_username, 'fnord');
$fs->set_access($acc);
@@ -70,11 +76,13 @@
$fs->lock('/testfile', $token, 'we hate software', 0, 0, $fs->youngest_rev, 0);
};
+# TEST
like($@, qr/already locked/);
eval {
$fs->unlock('/testfile', 'software', 0)
};
+# TEST
like($@, qr/no such lock/);
$fs->unlock('/testfile', 'software', 1);
diff --git a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
index fb4383a..c9b4ff6 100644
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
@@ -2159,6 +2159,24 @@
return stream;
}
+PyObject *
+svn_swig_py_convert_txdelta_op_c_array(int num_ops,
+ svn_txdelta_op_t *ops,
+ swig_type_info *op_type_info,
+ PyObject *parent_pool)
+{
+ PyObject *result = PyList_New(num_ops);
+ int i;
+
+ if (!result) return NULL;
+
+ for (i = 0; i < num_ops; ++i)
+ PyList_SET_ITEM(result, i,
+ svn_swig_NewPointerObj(ops + i, op_type_info,
+ parent_pool, NULL));
+
+ return result;
+}
void svn_swig_py_notify_func(void *baton,
const char *path,
@@ -4174,18 +4192,98 @@
return callbacks;
}
-PyObject *
-svn_swig_py_txdelta_window_t_ops_get(svn_txdelta_window_t *window,
- swig_type_info * op_type_info,
- PyObject *window_pool)
+svn_boolean_t
+svn_swig_py_config_enumerator2(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
{
- PyObject *result = PyList_New(window->num_ops);
- int i;
+ PyObject *function = baton;
+ PyObject *result;
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_boolean_t c_result;
- for (i = 0; i < window->num_ops; ++i)
- PyList_SET_ITEM(result, i,
- svn_swig_NewPointerObj(window->ops + i, op_type_info,
- window_pool, NULL));
+ svn_swig_py_acquire_py_lock();
- return result;
+ if ((result = PyObject_CallFunction(function,
+ (char *)"ssO&",
+ name,
+ value,
+ make_ob_pool, pool)) == NULL)
+ {
+ err = callback_exception_error();
+ }
+ else if (!PyBool_Check(result))
+ {
+ err = callback_bad_return_error("Not bool");
+ Py_DECREF(result);
+ }
+
+ /* Any Python exception we might have pending must be cleared,
+ because the SWIG wrapper will not check for it, and return a value with
+ the exception still set. */
+ PyErr_Clear();
+
+ if (err)
+ {
+ /* We can't return the error, but let's at least stop enumeration. */
+ svn_error_clear(err);
+ c_result = FALSE;
+ }
+ else
+ {
+ c_result = result == Py_True;
+ Py_DECREF(result);
+ }
+
+ svn_swig_py_release_py_lock();
+
+ return c_result;
+}
+
+svn_boolean_t
+svn_swig_py_config_section_enumerator2(const char *name,
+ void *baton,
+ apr_pool_t *pool)
+{
+ PyObject *function = baton;
+ PyObject *result;
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_boolean_t c_result;
+
+ svn_swig_py_acquire_py_lock();
+
+ if ((result = PyObject_CallFunction(function,
+ (char *)"sO&",
+ name,
+ make_ob_pool, pool)) == NULL)
+ {
+ err = callback_exception_error();
+ }
+ else if (!PyBool_Check(result))
+ {
+ err = callback_bad_return_error("Not bool");
+ Py_DECREF(result);
+ }
+
+ /* Any Python exception we might have pending must be cleared,
+ because the SWIG wrapper will not check for it, and return a value with
+ the exception still set. */
+ PyErr_Clear();
+
+ if (err)
+ {
+ /* We can't return the error, but let's at least stop enumeration. */
+ svn_error_clear(err);
+ c_result = FALSE;
+ }
+ else
+ {
+ c_result = result == Py_True;
+ Py_DECREF(result);
+ }
+
+ svn_swig_py_release_py_lock();
+
+ return c_result;
}
diff --git a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
index cbd9972..fec6683 100644
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
@@ -299,6 +299,15 @@
svn_stream_t *svn_swig_py_make_stream(PyObject *py_io,
apr_pool_t *pool);
+/* Convert ops, a C array of num_ops elements, to a Python list of SWIG
+ objects with descriptor op_type_info and pool set to parent_pool. */
+SVN_SWIG_SWIGUTIL_EXPORT
+PyObject *
+svn_swig_py_convert_txdelta_op_c_array(int num_ops,
+ svn_txdelta_op_t *ops,
+ swig_type_info * op_type_info,
+ PyObject *parent_pool);
+
/* a notify function that executes a Python function that is passed in
via the baton argument */
SVN_SWIG_SWIGUTIL_EXPORT
@@ -520,16 +529,18 @@
SVN_SWIG_SWIGUTIL_EXPORT
extern const svn_ra_reporter2_t swig_py_ra_reporter2;
-/* Get a list of ops from a window. Used to replace the naive
- svn_txdelta_window_t.ops accessor. op_type_info is supposed to be
- the SWIG descriptor of "svn_txdelta_op_t *". window_pool is supposed
- to be the pool associated with the window proxy and used for wrapping
- the op objects. */
SVN_SWIG_SWIGUTIL_EXPORT
-PyObject *
-svn_swig_py_txdelta_window_t_ops_get(svn_txdelta_window_t *window,
- swig_type_info * op_type_info,
- PyObject *window_pool);
+svn_boolean_t
+svn_swig_py_config_enumerator2(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool);
+
+SVN_SWIG_SWIGUTIL_EXPORT
+svn_boolean_t
+svn_swig_py_config_section_enumerator2(const char *name,
+ void *baton,
+ apr_pool_t *pool);
#ifdef __cplusplus
}
diff --git a/subversion/bindings/swig/python/svn/delta.py b/subversion/bindings/swig/python/svn/delta.py
index 9d93be7..664974b 100644
--- a/subversion/bindings/swig/python/svn/delta.py
+++ b/subversion/bindings/swig/python/svn/delta.py
@@ -30,10 +30,6 @@
__all__ = filter(lambda x: x.lower().startswith('svn_'), locals().keys())
del _unprefix_names
-# Force our accessor since it appears that there isn't a more civilized way
-# to make SWIG use it.
-svn_txdelta_window_t.ops = property(svn_txdelta_window_t_ops_get)
-
class Editor:
def set_target_revision(self, target_revision, pool=None):
diff --git a/subversion/bindings/swig/python/tests/core.py b/subversion/bindings/swig/python/tests/core.py
index 68eacfb..8f4c5c1 100644
--- a/subversion/bindings/swig/python/tests/core.py
+++ b/subversion/bindings/swig/python/tests/core.py
@@ -97,7 +97,7 @@
# will be passed through.
rec.e = svn.core.SubversionException("No fields except message.")
# e.apr_err is None but should be an int
- self.assertRaises(TypeError, svn.client.info2, args)
+ self.assertRaises(TypeError, svn.client.info2, *args)
finally:
# This would happen without the finally block as well, but we expliticly
# order the operations so that the cleanup is not hindered by any open
@@ -105,6 +105,71 @@
del ctx
t.cleanup()
+ def test_config_enumerate2(self):
+ cfg = svn.core.svn_config_create(False)
+ entries = {
+ 'one': 'one-value',
+ 'two': 'two-value',
+ 'three': 'three-value'
+ }
+
+ for (name, value) in entries.iteritems():
+ svn.core.svn_config_set(cfg, "section", name, value)
+
+ received_entries = {}
+ def enumerator(name, value, pool):
+ received_entries[name] = value
+ return len(received_entries) < 2
+
+ svn.core.svn_config_enumerate2(cfg, "section", enumerator)
+
+ self.assertEqual(len(received_entries), 2)
+ for (name, value) in received_entries.iteritems():
+ self.assert_(name in entries)
+ self.assertEqual(value, entries[name])
+
+ def test_config_enumerate2_exception(self):
+ cfg = svn.core.svn_config_create(False)
+ svn.core.svn_config_set(cfg, "section", "one", "one-value")
+ svn.core.svn_config_set(cfg, "section", "two", "two-value")
+
+ def enumerator(name, value, pool):
+ raise Exception
+
+ # the exception will be swallowed, but enumeration must be stopped
+ self.assertEqual(
+ svn.core.svn_config_enumerate2(cfg, "section", enumerator), 1)
+
+ def test_config_enumerate_sections2(self):
+ cfg = svn.core.svn_config_create(False)
+ sections = ['section-one', 'section-two', 'section-three']
+
+ for section in sections:
+ svn.core.svn_config_set(cfg, section, "name", "value")
+
+ received_sections = []
+ def enumerator(section, pool):
+ received_sections.append(section)
+ return len(received_sections) < 2
+
+ svn.core.svn_config_enumerate_sections2(cfg, enumerator)
+
+ self.assertEqual(len(received_sections), 2)
+ for section in received_sections:
+ self.assert_(section in sections)
+
+ def test_config_enumerate_sections2_exception(self):
+ cfg = svn.core.svn_config_create(False)
+ svn.core.svn_config_set(cfg, "section-one", "name", "value")
+ svn.core.svn_config_set(cfg, "section-two", "name", "value")
+
+ def enumerator(section, pool):
+ raise Exception
+
+ # the exception will be swallowed, but enumeration must be stopped
+ self.assertEqual(
+ svn.core.svn_config_enumerate_sections2(cfg, enumerator), 1)
+
def suite():
return unittest.defaultTestLoader.loadTestsFromTestCase(
SubversionCoreTestCase)
diff --git a/subversion/bindings/swig/python/tests/pool.py b/subversion/bindings/swig/python/tests/pool.py
index ff39af5..bd67987 100644
--- a/subversion/bindings/swig/python/tests/pool.py
+++ b/subversion/bindings/swig/python/tests/pool.py
@@ -56,17 +56,7 @@
def test_object_hash_struct_members(self):
"""Check that struct members which are hashes of objects work correctly"""
- # Get an empty config
- (cfg_fd, cfg_name) = tempfile.mkstemp(prefix="conf-")
- os.close(cfg_fd)
-
- try:
- cfg = svn.core.svn_config_read(
- svn.core.svn_dirent_internal_style(cfg_name),
- False)
- finally:
- os.remove(cfg_name)
-
+ cfg = svn.core.svn_config_create(False)
client_ctx = svn.client.svn_client_create_context()
category = svn.core.SVN_CONFIG_CATEGORY_SERVERS
client_ctx.config = { category: cfg }
diff --git a/subversion/bindings/swig/svn_delta.i b/subversion/bindings/swig/svn_delta.i
index 6e9d306..ddcd714 100644
--- a/subversion/bindings/swig/svn_delta.i
+++ b/subversion/bindings/swig/svn_delta.i
@@ -174,61 +174,47 @@
#ifdef SWIGPYTHON
%ignore svn_txdelta_window_t::ops;
-%inline %{
-static PyObject *
-svn_txdelta_window_t_ops_get(PyObject *window_ob)
-{
- void *window;
- PyObject *ops_list, *window_pool;
- int status;
-
- /* Kludge alert!
- Normally, these kinds of conversions would belong in a typemap.
- However, typemaps won't allow us to change the result type to an array,
- so we have to make this custom accessor function.
- A cleaner approach would be to use something like:
-
- %extend svn_txdelta_window_t { void get_ops(apr_array_header_t ** ops); }
-
- But that means unnecessary copying, plus more hacks to get the pool for the
- array and for wrapping the individual op objects. So we just don't bother.
- */
-
- /* Note: the standard svn-python typemap releases the GIL while calling the
- wrapped function, but this function does Python stuff, so we have to
- reacquire it again. */
- svn_swig_py_acquire_py_lock();
- status = svn_swig_ConvertPtr(window_ob, &window,
- SWIG_TypeQuery("svn_txdelta_window_t *"));
-
- if (status != 0)
- {
- PyErr_SetString(PyExc_TypeError,
- "expected an svn_txdelta_window_t* proxy");
- svn_swig_py_release_py_lock();
- return NULL;
- }
-
- window_pool = PyObject_GetAttrString(window_ob, "_parent_pool");
+%extend svn_txdelta_window_t {
- if (window_pool == NULL)
- {
- svn_swig_py_release_py_lock();
- return NULL;
- }
-
- ops_list = svn_swig_py_txdelta_window_t_ops_get(window,
- SWIG_TypeQuery("svn_txdelta_op_t *"), window_pool);
-
- svn_swig_py_release_py_lock();
-
- return ops_list;
+void _ops_get(int *num_ops, svn_txdelta_op_t **ops)
+{
+ *num_ops = self->num_ops;
+ *ops = self->ops;
}
-%}
+
+%pythoncode {
+ ops = property(_ops_get)
+}
+}
+
+%typemap(argout) (int *num_ops, svn_txdelta_op_t **ops) {
+ apr_pool_t *parent_pool;
+ PyObject *parent_py_pool;
+ PyObject *ops_list;
+
+ if (svn_swig_py_get_parent_pool(args, $descriptor(apr_pool_t *),
+ &parent_py_pool, &parent_pool))
+ SWIG_fail;
+
+ ops_list = svn_swig_py_convert_txdelta_op_c_array(*$1, *$2,
+ $descriptor(svn_txdelta_op_t *), parent_py_pool);
+
+ if (!ops_list) SWIG_fail;
+
+ %append_output(ops_list);
+}
#endif
%include svn_delta_h.swg
+#ifdef SWIGPYTHON
+%pythoncode {
+# This function is for backwards compatibility only.
+# Use svn_txdelta_window_t.ops instead.
+svn_txdelta_window_t_ops_get = svn_txdelta_window_t._ops_get
+}
+#endif
+
#ifdef SWIGRUBY
%inline %{
static VALUE
diff --git a/subversion/bindings/swig/svn_wc.i b/subversion/bindings/swig/svn_wc.i
index f5b3c95..6137b99 100644
--- a/subversion/bindings/swig/svn_wc.i
+++ b/subversion/bindings/swig/svn_wc.i
@@ -155,11 +155,16 @@
)
#endif
-#ifndef SWIGPERL
%callback_typemap(svn_wc_status_func2_t status_func, void *status_baton,
svn_swig_py_status_func2,
- ,
+ svn_swig_pl_status_func2,
svn_swig_rb_wc_status_func)
+
+#ifdef SWIGPERL
+%callback_typemap(svn_wc_status_func3_t status_func, void *status_baton,
+ ,
+ svn_swig_pl_status_func3,
+ )
#endif
#ifndef SWIGPERL
diff --git a/subversion/include/private/svn_cache.h b/subversion/include/private/svn_cache.h
index 880f04e..bacc91b 100644
--- a/subversion/include/private/svn_cache.h
+++ b/subversion/include/private/svn_cache.h
@@ -269,16 +269,33 @@
* will generally result in higher hit rates and reduced conflict
* resolution overhead.
*
- * If access to the resulting cache object is guranteed to be serialized,
+ * The cache will be split into @a segment_count segments of equal size.
+ * A higher number reduces lock contention but also limits the maximum
+ * cachable item size. If it is not a power of two, it will be rounded
+ * down to next lower power of two. Also, there is an implementation
+ * specific upper limit and the setting will be capped there automatically.
+ * If the number is 0, a default will be derived from @a total_size.
+ *
+ * If access to the resulting cache object is guaranteed to be serialized,
* @a thread_safe may be set to @c FALSE for maximum performance.
*
+ * There is no limit on the number of threads reading a given cache segment
+ * concurrently. Writes, however, need an exclusive lock on the respective
+ * segment. @a allow_blocking_writes controls contention is handled here.
+ * If set to TRUE, writes will wait until the lock becomes available, i.e.
+ * reads should be short. If set to FALSE, write attempts will be ignored
+ * (no data being written to the cache) if some reader or another writer
+ * currently holds the segment lock.
+ *
* Allocations will be made in @a result_pool, in particular the data buffers.
*/
svn_error_t *
svn_cache__membuffer_cache_create(svn_membuffer_t **cache,
apr_size_t total_size,
apr_size_t directory_size,
+ apr_size_t segment_count,
svn_boolean_t thread_safe,
+ svn_boolean_t allow_blocking_writes,
apr_pool_t *result_pool);
/**
diff --git a/subversion/include/private/svn_client_private.h b/subversion/include/private/svn_client_private.h
index 455f6c3..478714b 100644
--- a/subversion/include/private/svn_client_private.h
+++ b/subversion/include/private/svn_client_private.h
@@ -38,6 +38,21 @@
#endif /* __cplusplus */
+/* Return true if KIND is a revision kind that is dependent on the working
+ * copy. Otherwise, return false. */
+#define SVN_CLIENT__REVKIND_NEEDS_WC(kind) \
+ ((kind) == svn_opt_revision_base || \
+ (kind) == svn_opt_revision_previous || \
+ (kind) == svn_opt_revision_working || \
+ (kind) == svn_opt_revision_committed) \
+
+/* Return true if KIND is a revision kind that the WC can supply without
+ * contacting the repository. Otherwise, return false. */
+#define SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(kind) \
+ ((kind) == svn_opt_revision_base || \
+ (kind) == svn_opt_revision_working || \
+ (kind) == svn_opt_revision_committed)
+
/* A location in a repository. */
typedef struct svn_client__pathrev_t
{
@@ -98,6 +113,39 @@
svn_client__pathrev_fspath(const svn_client__pathrev_t *pathrev,
apr_pool_t *result_pool);
+/* Given PATH_OR_URL, which contains either a working copy path or an
+ absolute URL, a peg revision PEG_REVISION, and a desired revision
+ REVISION, create an RA connection to that object as it exists in
+ that revision, following copy history if necessary. If REVISION is
+ younger than PEG_REVISION, then PATH_OR_URL will be checked to see
+ that it is the same node in both PEG_REVISION and REVISION. If it
+ is not, then @c SVN_ERR_CLIENT_UNRELATED_RESOURCES is returned.
+
+ BASE_DIR_ABSPATH is the working copy path the ra_session corresponds to,
+ and should only be used if PATH_OR_URL is a url
+ ### else NULL? what's it for?
+
+ If PEG_REVISION->kind is 'unspecified', the peg revision is 'head'
+ for a URL or 'working' for a WC path. If REVISION->kind is
+ 'unspecified', the operative revision is the peg revision.
+
+ Store the resulting ra_session in *RA_SESSION_P. Store the final
+ resolved location of the object in *RESOLVED_LOC_P. RESOLVED_LOC_P
+ may be NULL if not wanted.
+
+ Use authentication baton cached in CTX to authenticate against the
+ repository.
+
+ Use POOL for all allocations. */
+svn_error_t *
+svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
+ svn_client__pathrev_t **resolved_loc_p,
+ const char *path_or_url,
+ const char *base_dir_abspath,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
/** Return @c SVN_ERR_ILLEGAL_TARGET if TARGETS contains a mixture of
* URLs and paths; otherwise return SVN_NO_ERROR.
@@ -178,58 +226,20 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
-
-/* Details of a symmetric merge. */
-typedef struct svn_client__symmetric_merge_t
-{
- svn_client__pathrev_t *yca, *base, *mid, *right;
- svn_boolean_t allow_mixed_rev, allow_local_mods, allow_switched_subtrees;
-} svn_client__symmetric_merge_t;
-
-/* Find the information needed to merge all unmerged changes from a source
- * branch into a target branch. The information is the locations of the
- * youngest common ancestor, merge base, and such like.
+/* Set *YCA, *BASE, *RIGHT, *TARGET to the repository locations of the
+ * youngest common ancestor of the branches, the base chosen for 3-way
+ * merge, the right-hand side of the source diff, and the target WC.
*
- * Set *MERGE to the information needed to merge all unmerged changes
- * (up to SOURCE_REVISION) from the source branch SOURCE_PATH_OR_URL @
- * SOURCE_REVISION into the target WC at TARGET_WCPATH.
+ * Any of the output pointers may be NULL if not wanted.
*/
svn_error_t *
-svn_client__find_symmetric_merge(svn_client__symmetric_merge_t **merge,
- const char *source_path_or_url,
- const svn_opt_revision_t *source_revision,
- const char *target_wcpath,
- svn_boolean_t allow_mixed_rev,
- svn_boolean_t allow_local_mods,
- svn_boolean_t allow_switched_subtrees,
- svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
-
-/* Perform a symmetric merge.
- *
- * Merge according to MERGE into the WC at TARGET_WCPATH.
- *
- * The other parameters are as in svn_client_merge4().
- *
- * ### TODO: There's little point in this function being the only way the
- * caller can use the result of svn_client__find_symmetric_merge(). The
- * contents of MERGE should be more public, or there should be other ways
- * the caller can use it, or these two functions should be combined into
- * one. I want to make it more public, and also possibly have more ways
- * to use it in future (for example, do_symmetric_merge_with_step_by_-
- * step_confirmation).
- */
-svn_error_t *
-svn_client__do_symmetric_merge(const svn_client__symmetric_merge_t *merge,
- const char *target_wcpath,
- svn_depth_t depth,
- svn_boolean_t force,
- svn_boolean_t record_only,
- svn_boolean_t dry_run,
- const apr_array_header_t *merge_options,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool);
+svn_client__automatic_merge_get_locations(
+ svn_client__pathrev_t **yca,
+ svn_client__pathrev_t **base,
+ svn_client__pathrev_t **right,
+ svn_client__pathrev_t **target,
+ const svn_client_automatic_merge_t *merge,
+ apr_pool_t *result_pool);
#ifdef __cplusplus
diff --git a/subversion/include/private/svn_cmdline_private.h b/subversion/include/private/svn_cmdline_private.h
index 916c85e..8f41731 100644
--- a/subversion/include/private/svn_cmdline_private.h
+++ b/subversion/include/private/svn_cmdline_private.h
@@ -44,12 +44,14 @@
* @a propname is the property name. @a propval is the property value, which
* will be encoded if it contains unsafe bytes.
*
- * @since New in 1.6.
+ * If @a inherited_prop is TRUE then @a propname is an inherited property,
+ * otherwise @a propname is an explicit property.
*/
void
svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
const char *propname,
svn_string_t *propval,
+ svn_boolean_t inherited_prop,
apr_pool_t *pool);
diff --git a/subversion/include/private/svn_dav_protocol.h b/subversion/include/private/svn_dav_protocol.h
index 4a8762f..94cf069 100644
--- a/subversion/include/private/svn_dav_protocol.h
+++ b/subversion/include/private/svn_dav_protocol.h
@@ -32,6 +32,7 @@
/** Names for the custom HTTP REPORTs understood by mod_dav_svn, sans
namespace. */
#define SVN_DAV__MERGEINFO_REPORT "mergeinfo-report"
+#define SVN_DAV__INHERITED_PROPS_REPORT "inherited-props-report"
/** Names for XML child elements of the custom HTTP REPORTs understood
by mod_dav_svn, sans namespace. */
@@ -44,6 +45,10 @@
#define SVN_DAV__REVISION "revision"
#define SVN_DAV__INCLUDE_DESCENDANTS "include-descendants"
#define SVN_DAV__VERSION_NAME "version-name"
+#define SVN_DAV__IPROP_ITEM "iprop-item"
+#define SVN_DAV__IPROP_PATH "iprop-path"
+#define SVN_DAV__IPROP_PROPNAME "iprop-propname"
+#define SVN_DAV__IPROP_PROPVAL "iprop-propval"
/** Names of XML elements attributes and tags for svn_ra_change_rev_prop2()'s
extension of PROPPATCH. */
diff --git a/subversion/include/private/svn_debug.h b/subversion/include/private/svn_debug.h
index 615f2fa..f85862c 100644
--- a/subversion/include/private/svn_debug.h
+++ b/subversion/include/private/svn_debug.h
@@ -31,7 +31,9 @@
### remain in the code. at that point, we can rejigger this header. */
#ifdef SVN_DEBUG
-#include <stdio.h>
+#define APR_WANT_STDIO
+#include <apr_want.h>
+#include <apr_hash.h>
#ifdef __cplusplus
extern "C" {
diff --git a/subversion/include/private/svn_dep_compat.h b/subversion/include/private/svn_dep_compat.h
index 1754bef..979b549 100644
--- a/subversion/include/private/svn_dep_compat.h
+++ b/subversion/include/private/svn_dep_compat.h
@@ -87,6 +87,21 @@
#endif /* !APR_VERSION_AT_LEAST(1,3,0) */
/**
+ * Work around a platform dependency issue. apr_thread_rwlock_trywrlock()
+ * will make APR_STATUS_IS_EBUSY() return TRUE if the lock could not be
+ * acquired under Unix. Under Windows, this will not work. So, provide
+ * a more portable substitute.
+ *
+ * @since New in 1.8.
+ */
+#ifdef WIN32
+#define SVN_LOCK_IS_BUSY(x) \
+ (APR_STATUS_IS_EBUSY(x) || (x) == APR_FROM_OS_ERROR(WAIT_TIMEOUT))
+#else
+#define SVN_LOCK_IS_BUSY(x) APR_STATUS_IS_EBUSY(x)
+#endif
+
+/**
* Check at compile time if the Serf version is at least a certain
* level.
* @param major The major version component of the version checked
diff --git a/subversion/include/private/svn_eol_private.h b/subversion/include/private/svn_eol_private.h
index c5a4952..d2cce5c 100644
--- a/subversion/include/private/svn_eol_private.h
+++ b/subversion/include/private/svn_eol_private.h
@@ -37,6 +37,20 @@
extern "C" {
#endif /* __cplusplus */
+/* Constants used by various chunky string processing functions.
+ */
+#if APR_SIZEOF_VOIDP == 8
+# define SVN__LOWER_7BITS_SET 0x7f7f7f7f7f7f7f7f
+# define SVN__BIT_7_SET 0x8080808080808080
+# define SVN__R_MASK 0x0a0a0a0a0a0a0a0a
+# define SVN__N_MASK 0x0d0d0d0d0d0d0d0d
+#else
+# define SVN__LOWER_7BITS_SET 0x7f7f7f7f
+# define SVN__BIT_7_SET 0x80808080
+# define SVN__R_MASK 0x0a0a0a0a
+# define SVN__N_MASK 0x0d0d0d0d
+#endif
+
/* Generic EOL character helper routines */
/* Look for the start of an end-of-line sequence (i.e. CR or LF)
diff --git a/subversion/include/private/svn_fs_util.h b/subversion/include/private/svn_fs_util.h
index 4a6a0e2..35a700f 100644
--- a/subversion/include/private/svn_fs_util.h
+++ b/subversion/include/private/svn_fs_util.h
@@ -52,6 +52,12 @@
const char *
svn_fs__canonicalize_abspath(const char *path, apr_pool_t *pool);
+/* Return FALSE, if a svn_fs__canonicalize_abspath will return a
+ different value than PATH (despite creating a copy).
+*/
+svn_boolean_t
+svn_fs__is_canonical_abspath(const char *path);
+
/* If EXPECT_OPEN, verify that FS refers to an open database;
otherwise, verify that FS refers to an unopened database. Return
an appropriate error if the expectation fails to match the
diff --git a/subversion/include/private/svn_log.h b/subversion/include/private/svn_log.h
index 922feec..bdcf32f 100644
--- a/subversion/include/private/svn_log.h
+++ b/subversion/include/private/svn_log.h
@@ -244,6 +244,15 @@
const char *
svn_log__replay(const char *path, svn_revnum_t rev, apr_pool_t *pool);
+/**
+ * Return a log string for a get-inherited-props action.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_log__get_inherited_props(const char *path,
+ svn_revnum_t rev,
+ apr_pool_t *pool);
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/include/private/svn_mergeinfo_private.h b/subversion/include/private/svn_mergeinfo_private.h
index e2812f0..ea696e2 100644
--- a/subversion/include/private/svn_mergeinfo_private.h
+++ b/subversion/include/private/svn_mergeinfo_private.h
@@ -125,20 +125,6 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
-/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path
- converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO
- is declared as 'apr_hash_t *' because its key do not obey the rules of
- 'svn_mergeinfo_t'.
-
- Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use
- SCRATCH_POOL for any temporary allocations. */
-svn_error_t *
-svn_mergeinfo__relpaths_to_urls(apr_hash_t **out_mergeinfo,
- svn_mergeinfo_t mergeinfo,
- const char *repos_root_url,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
-
/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with the relpath
SUFFIX_RELPATH added to the end of each key path.
@@ -170,20 +156,6 @@
const char *val_prefix,
apr_pool_t *pool);
-/* Create a string representation of MERGEINFO in *OUTPUT, allocated in POOL.
- Unlike svn_mergeinfo_to_string(), NULL MERGEINFO is tolerated and results
- in *OUTPUT set to "\n". If SVN_DEBUG is true, then NULL or empty MERGEINFO
- causes *OUTPUT to be set to an appropriate newline terminated string. If
- PREFIX is not NULL then prepend PREFIX to each line in *OUTPUT.
-
- Any relative merge source paths in MERGEINFO are converted to absolute
- paths in *OUTPUT.*/
-svn_error_t *
-svn_mergeinfo__to_formatted_string(svn_string_t **output,
- svn_mergeinfo_t mergeinfo,
- const char *prefix,
- apr_pool_t *pool);
-
/* Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
found in the rangelists within MERGEINFO. Note that *OLDEST_REV is
exclusive and *YOUNGEST_REV is inclusive. If MERGEINFO is NULL or empty
diff --git a/subversion/include/private/svn_named_atomic.h b/subversion/include/private/svn_named_atomic.h
index 0241889..347b33d 100644
--- a/subversion/include/private/svn_named_atomic.h
+++ b/subversion/include/private/svn_named_atomic.h
@@ -83,6 +83,17 @@
const char *name,
apr_pool_t *result_pool);
+/** Removes persistent data structures (files in particular) that got
+ * created for the namespace given by @a name. Use @a pool for temporary
+ * allocations.
+ *
+ * @note You must not call this while the respective namespace is still
+ * in use. Calling this multiple times for the same namespace is safe.
+ */
+svn_error_t *
+svn_atomic_namespace__cleanup(const char *name,
+ apr_pool_t *pool);
+
/** Find the atomic with the specified @a name in namespace @a ns and
* return it in @a *atomic. If no object with that name can be found, the
* behavior depends on @a auto_create. If it is @c FALSE, @a *atomic will
diff --git a/subversion/include/private/svn_pseudo_md5.h b/subversion/include/private/svn_pseudo_md5.h
new file mode 100644
index 0000000..5737fda
--- /dev/null
+++ b/subversion/include/private/svn_pseudo_md5.h
@@ -0,0 +1,83 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_pseudo_md5.h
+ * @brief Subversion hash sum calculation for runtime data (only)
+ */
+
+#ifndef SVN_PSEUDO_MD5_H
+#define SVN_PSEUDO_MD5_H
+
+#include <apr.h> /* for apr_uint32_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/**
+ * Calculates a hash sum for 15 bytes in @a x and returns it in @a digest.
+ * The most significant byte in @a x must be 0 (independent of being on a
+ * little or big endian machine).
+ *
+ * @note Use for runtime data hashing only.
+ *
+ * @note The output is NOT an MD5 digest shares has the same basic
+ * cryptographic properties. Collisions with proper MD5 on the same
+ * or other input data is equally unlikely as any MD5 collision.
+ */
+void svn__pseudo_md5_15(apr_uint32_t digest[4],
+ const apr_uint32_t x[4]);
+
+/**
+ * Calculates a hash sum for 31 bytes in @a x and returns it in @a digest.
+ * The most significant byte in @a x must be 0 (independent of being on a
+ * little or big endian machine).
+ *
+ * @note Use for runtime data hashing only.
+ *
+ * @note The output is NOT an MD5 digest shares has the same basic
+ * cryptographic properties. Collisions with proper MD5 on the same
+ * or other input data is equally unlikely as any MD5 collision.
+ */
+void svn__pseudo_md5_31(apr_uint32_t digest[4],
+ const apr_uint32_t x[8]);
+
+/**
+ * Calculates a hash sum for 63 bytes in @a x and returns it in @a digest.
+ * The most significant byte in @a x must be 0 (independent of being on a
+ * little or big endian machine).
+ *
+ * @note Use for runtime data hashing only.
+ *
+ * @note The output is NOT an MD5 digest shares has the same basic
+ * cryptographic properties. Collisions with proper MD5 on the same
+ * or other input data is equally unlikely as any MD5 collision.
+ */
+void svn__pseudo_md5_63(apr_uint32_t digest[4],
+ const apr_uint32_t x[16]);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_PSEUDO_MD5_H */
diff --git a/subversion/include/private/svn_ra_private.h b/subversion/include/private/svn_ra_private.h
index a186aa5..ae229f7 100644
--- a/subversion/include/private/svn_ra_private.h
+++ b/subversion/include/private/svn_ra_private.h
@@ -119,16 +119,6 @@
const svn_string_t *mylocktoken,
apr_pool_t *scratch_pool);
-
-/** Like svn_ra_get_path_relative_to_root(), except returning a fspath
- * (starting with '/') instead of a relpath.
- */
-svn_error_t *
-svn_ra__get_fspath_relative_to_root(svn_ra_session_t *ra_session,
- const char **fspath,
- const char *url,
- apr_pool_t *pool);
-
/** Register CALLBACKS to be used with the Ev2 shims in RA_SESSION. */
svn_error_t *
svn_ra__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
@@ -218,8 +208,8 @@
CB_BATON is the baton used/shared by the above three callbacks.
- CANCEL_FUNC/BATON is a standard cancellation function, and is used for
- the returned Ev2 editor, and possibly other RA-specific operations.
+ Cancellation is handled through the callbacks provided when SESSION
+ is initially opened.
*EDITOR will be allocated in RESULT_POOL, and all temporary allocations
will be performed in SCRATCH_POOL.
@@ -236,8 +226,6 @@
svn_ra__provide_props_cb_t provide_props_cb,
svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
void *cb_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
@@ -269,6 +257,10 @@
svn_ra__replay_revstart_ev2_callback_t revstart_func,
svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
apr_pool_t *scratch_pool);
/* Similar to svn_ra_replay(), but with an Ev2 editor. */
diff --git a/subversion/include/private/svn_skel.h b/subversion/include/private/svn_skel.h
index 914dd3f..5e60fc9 100644
--- a/subversion/include/private/svn_skel.h
+++ b/subversion/include/private/svn_skel.h
@@ -196,6 +196,14 @@
const svn_skel_t *skel,
apr_pool_t *result_pool);
+/* Parse a `IPROPS' SKEL into a depth-first ordered array of
+ svn_prop_inherited_item_t * structures *IPROPS. Use RESULT_POOL
+ for all allocations. */
+svn_error_t *
+svn_skel__parse_iprops(apr_array_header_t **iprops,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool);
+
/* Parse a `PROPLIST' SKEL looking for PROPNAME. If PROPNAME is found
then return its value in *PROVAL, allocated in RESULT_POOL. */
svn_error_t *
@@ -212,6 +220,14 @@
apr_hash_t *proplist,
apr_pool_t *pool);
+/* Unparse INHERITED_PROPS, a depth-first ordered array of
+ svn_prop_inherited_item_t * structures, into a `IPROPS' skel *SKEL_P.
+ Use RESULT_POOL for all allocations. */
+svn_error_t *
+svn_skel__unparse_iproplist(svn_skel_t **skel_p,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *result_pool);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/include/private/svn_sqlite.h b/subversion/include/private/svn_sqlite.h
index 9882b9b..9df3956 100644
--- a/subversion/include/private/svn_sqlite.h
+++ b/subversion/include/private/svn_sqlite.h
@@ -37,6 +37,17 @@
#endif /* __cplusplus */
+/* Because the SQLite code can be inlined into libsvn_subre/sqlite.c,
+ we define accessors to its compile-time and run-time version
+ numbers here. */
+
+/* Return the value that SQLITE_VERSION had at compile time. */
+const char *svn_sqlite__compiled_version(void);
+
+/* Return the value of sqlite3_libversion() at run time. */
+const char *svn_sqlite__runtime_version(void);
+
+
typedef struct svn_sqlite__db_t svn_sqlite__db_t;
typedef struct svn_sqlite__stmt_t svn_sqlite__stmt_t;
typedef struct svn_sqlite__context_t svn_sqlite__context_t;
@@ -210,6 +221,15 @@
const apr_hash_t *props,
apr_pool_t *scratch_pool);
+/* Bind a set of inherited properties to the given slot. If INHERITED_PROPS
+ is NULL, then no binding will occur. INHERITED_PROPS will be stored as a
+ serialized skel. */
+svn_error_t *
+svn_sqlite__bind_iprops(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *scratch_pool);
+
/* Bind a checksum's value to the given slot. If CHECKSUM is NULL, then no
binding will occur. */
svn_error_t *
@@ -279,6 +299,17 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Return the column as an array of depth-first ordered array of
+ svn_prop_inherited_item_t * structures. If the column is null, then
+ *props is set to NULL. The results will be allocated in RESULT_POOL,
+ and any temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_sqlite__column_iprops(apr_array_header_t **iprops,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* Return the column as a checksum. If the column is null, then NULL will
be stored into *CHECKSUM. The result will be allocated in RESULT_POOL. */
svn_error_t *
diff --git a/subversion/include/private/svn_string_private.h b/subversion/include/private/svn_string_private.h
index 7296cbd..46644b2 100644
--- a/subversion/include/private/svn_string_private.h
+++ b/subversion/include/private/svn_string_private.h
@@ -78,6 +78,18 @@
apr_size_t
svn__i64toa(char * dest, apr_int64_t number);
+/** Returns a decimal string for @a number allocated in @a pool. Put in
+ * the @a seperator at each third place.
+ */
+char *
+svn__ui64toa_sep(apr_uint64_t number, char seperator, apr_pool_t *pool);
+
+/** Returns a decimal string for @a number allocated in @a pool. Put in
+ * the @a seperator at each third place.
+ */
+char *
+svn__i64toa_sep(apr_int64_t number, char seperator, apr_pool_t *pool);
+
/** @} */
/** @} */
diff --git a/subversion/include/private/svn_subr_private.h b/subversion/include/private/svn_subr_private.h
index b01f90b..c78590c 100644
--- a/subversion/include/private/svn_subr_private.h
+++ b/subversion/include/private/svn_subr_private.h
@@ -295,6 +295,36 @@
/** @} */
+/**
+ * @defgroup svn_version Version number dotted triplet parsing
+ * @{
+ */
+
+/* Set @a *version to a version structure parsed from the version
+ * string representation in @a version_string. Return
+ * @c SVN_ERR_MALFORMED_VERSION_STRING if the string fails to parse
+ * cleanly.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_version__parse_version_string(svn_version_t **version,
+ const char *version_string,
+ apr_pool_t *result_pool);
+
+/* Return true iff @a version represents a version number of at least
+ * the level represented by @a major, @a minor, and @a patch.
+ *
+ * @since New in 1.8.
+ */
+svn_boolean_t
+svn_version__at_least(svn_version_t *version,
+ int major,
+ int minor,
+ int patch);
+
+/** @} */
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/include/private/svn_wc_private.h b/subversion/include/private/svn_wc_private.h
index 1e53649..8046540 100644
--- a/subversion/include/private/svn_wc_private.h
+++ b/subversion/include/private/svn_wc_private.h
@@ -55,7 +55,14 @@
apr_pool_t *scratch_pool);
/* Like svn_wc_get_update_editorX and svn_wc_get_status_editorX, but only
- allows updating a file external LOCAL_ABSPATH */
+ allows updating a file external LOCAL_ABSPATH.
+
+ Since this only deals with files, the WCROOT_IPROPS argument in
+ svn_wc_get_update_editorX and svn_wc_get_status_editorX (hashes mapping
+ const char * absolute working copy paths, which are working copy roots, to
+ depth-first ordered arrays of svn_prop_inherited_item_t * structures) is
+ simply IPROPS here, a depth-first ordered arrays of
+ svn_prop_inherited_item_t * structs. */
svn_error_t *
svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
void **edit_baton,
@@ -66,6 +73,7 @@
const char *url,
const char *repos_root_url,
const char *repos_uuid,
+ apr_array_header_t *iprops,
svn_boolean_t use_commit_times,
const char *diff3_cmd,
const apr_array_header_t *preserved_exts,
@@ -549,42 +557,25 @@
apr_pool_t *scratch_pool);
/**
- * Set @a *is_server_excluded to whether @a local_abspath has been
- * excluded by the server, using @a wc_ctx. If @a local_abspath is not
- * in the working copy, return @c SVN_ERR_WC_PATH_NOT_FOUND.
- * Use @a scratch_pool for all temporary allocations.
- */
-svn_error_t *
-svn_wc__node_is_status_server_excluded(svn_boolean_t *is_server_excluded,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool);
-
-/**
- * Set @a *is_not_present to whether the status of @a local_abspath is
- * #svn_wc__db_status_not_present, using @a wc_ctx.
+ * Set @a *not_present to TRUE when @a local_abspath has status
+ * svn_wc__db_status_not_present. Set @a *user_excluded to TRUE when
+ * @a local_abspath has status svn_wc__db_status_excluded. Set
+ * @a *server_excluded to TRUE when @a local_abspath has status
+ * svn_wc__db_status_server_excluded. Otherwise set these values to FALSE.
+ *
+ * If a value is not interesting you can pass #NULL.
+ *
* If @a local_abspath is not in the working copy, return
* @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a scratch_pool for all temporary
* allocations.
*/
svn_error_t *
-svn_wc__node_is_status_not_present(svn_boolean_t *is_not_present,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool);
-
-/**
- * Set @a *is_excluded to whether the status of @a local_abspath is
- * #svn_wc__db_status_excluded, using @a wc_ctx.
- * If @a local_abspath is not in the working copy, return
- * @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a scratch_pool for all temporary
- * allocations.
- */
-svn_error_t *
-svn_wc__node_is_status_excluded(svn_boolean_t *is_excluded,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool);
+svn_wc__node_is_not_present(svn_boolean_t *not_present,
+ svn_boolean_t *user_excluded,
+ svn_boolean_t *server_excluded,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
/**
* Set @a *is_added to whether @a local_abspath is added, using
@@ -885,6 +876,25 @@
void *cancel_baton,
apr_pool_t *scratch_pool);
+/**
+ * Set @a *inherited_props to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the properties
+ * inherited by @a local_abspath from the ACTUAL tree above
+ * @a local_abspath (looking through to the WORKING or BASE tree as
+ * required), up to and including the root of the working copy and
+ * any cached inherited properties inherited by the root.
+ *
+ * Allocate @a *inherited_props in @a result_pool. Use @a scratch_pool
+ * for temporary allocations.
+ */
+svn_error_t *
+svn_wc__get_iprops(apr_array_header_t **inherited_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/** Obtain a mapping of const char * local_abspaths to const svn_string_t*
* property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH
* and its descendants.
@@ -901,6 +911,22 @@
apr_pool_t *scratch_pool);
/**
+ * Set @a *iprops_paths to a hash mapping const char * absolute working
+ * copy paths to the same for each path in the working copy at or below
+ * @a local_abspath, limited by @a depth, that has cached inherited
+ * properties for the base node of the path. Allocate @a *iprop_paths
+ * in @a result_pool. Use @a scratch_pool for temporary allocations.
+ */
+svn_error_t *
+svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/**
* For use by entries.c and entries-dump.c to read old-format working copies.
*/
svn_error_t *
@@ -1438,6 +1464,15 @@
* successful completion of the drive of this editor, will be
* populated with the revision to which the working copy was updated.
*
+ * @a wcroot_iprops is a hash mapping const char * absolute working copy
+ * paths which are working copy roots (at or under the target within the
+ * constraints dictated by @a depth) to depth-first ordered arrays of
+ * svn_prop_inherited_item_t * structures which represent the inherited
+ * properties for the base of those paths at @a target_revision. After a
+ * successful drive of this editor, the base nodes for these paths will
+ * have their inherited properties cache updated with the values from
+ * @a wcroot_iprops.
+ *
* If @a use_commit_times is TRUE, then all edited/added files will
* have their working timestamp set to the last-committed-time. If
* FALSE, the working files will be touched with the 'now' time.
@@ -1479,6 +1514,7 @@
svn_wc_context_t *wc_ctx,
const char *anchor_abspath,
const char *target_basename,
+ apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
svn_depth_t depth,
svn_boolean_t depth_is_sticky,
@@ -1522,6 +1558,7 @@
const char *anchor_abspath,
const char *target_basename,
const char *switch_url,
+ apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
svn_depth_t depth,
svn_boolean_t depth_is_sticky,
@@ -1691,6 +1728,52 @@
void *notify_baton,
apr_pool_t *scratch_pool);
+/**
+ * Move @a src_abspath to @a dst_abspath, by scheduling @a dst_abspath
+ * for addition to the repository, remembering the history. Mark @a src_abspath
+ * as deleted after moving.@a wc_ctx is used for accessing the working copy and
+ * must contain a write lock for the parent directory of @a src_abspath and
+ * @a dst_abspath.
+ *
+ * If @a metadata_only is TRUE then this is a database-only operation and
+ * the working directories and files are not changed.
+ *
+ * @a src_abspath must be a file or directory under version control;
+ * the parent of @a dst_abspath must be a directory under version control
+ * in the same working copy; @a dst_abspath will be the name of the copied
+ * item, and it must not exist already if @a metadata_only is FALSE. Note that
+ * when @a src points to a versioned file, the working file doesn't
+ * necessarily exist in which case its text-base is used instead.
+ *
+ * If @a allow_mixed_revisions is @c FALSE, #SVN_ERR_WC_MIXED_REVISIONS
+ * will be raised if the move source is a mixed-revision subtree.
+ * If @a allow_mixed_revisions is TRUE, a mixed-revision move source is
+ * allowed. This parameter should be set to FALSE except where backwards
+ * compatibility to svn_wc_move() is required.
+ *
+ * If @a cancel_func is non-NULL, call it with @a cancel_baton at
+ * various points during the operation. If it returns an error
+ * (typically #SVN_ERR_CANCELLED), return that error immediately.
+ *
+ * If @a notify_func is non-NULL, call it with @a notify_baton and the path
+ * of the root node (only) of the destination.
+ *
+ * Use @a scratch_pool for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_wc__move2(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t allow_mixed_revisions,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/include/svn_client.h b/subversion/include/svn_client.h
index a0d5757..f0fed81 100644
--- a/subversion/include/svn_client.h
+++ b/subversion/include/svn_client.h
@@ -355,10 +355,30 @@
} svn_client_proplist_item_t;
/**
- * The callback invoked by svn_client_proplist3(). Each invocation
- * provides the regular properties of @a path which is either a WC path or
- * a URL. @a prop_hash maps property names (char *) to property
- values (svn_string_t *). Use @a pool for all temporary allocation.
+ * The callback invoked by svn_client_proplist4(). Each invocation
+ * provides the regular and/or inherited properties of @a path, which is
+ * either a working copy path or a URL. If @a prop_hash is not @c NULL, then
+ * it maps explicit <tt>const char *</tt> property names to
+ * <tt>svn_string_t *</tt> explicit property values. If @a inherited_props
+ * is not @c NULL, then it is a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the
+ * properties inherited by @a path. Use @a scratch_pool for all temporary
+ * allocations.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *(*svn_proplist_receiver2_t)(
+ void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to #svn_proplist_receiver2_t, but doesn't return inherited
+ * properties.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
*
* @since New in 1.5.
*/
@@ -970,7 +990,10 @@
/** Initialize a client context.
* Set @a *ctx to a client context object, allocated in @a pool, that
- * represents a particular instance of an svn client.
+ * represents a particular instance of an svn client. @a cfg_hash is used
+ * to initialise the config member of the returned context object and should
+ * remain valid for the lifetime of the object. @a cfg_hash may be @c NULL,
+ * in which case it is ignored.
*
* In order to avoid backwards compatibility problems, clients must
* use this function to initialize and allocate the
@@ -979,8 +1002,21 @@
*
* The current implementation never returns error, but callers should
* still check for error, for compatibility with future versions.
+ *
+ * @since New in 1.8.
*/
svn_error_t *
+svn_client_create_context2(svn_client_ctx_t **ctx,
+ apr_hash_t *cfg_hash,
+ apr_pool_t *pool);
+
+
+/** Similar to svn_client_create_context but passes a NULL @a cfg_hash.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
svn_client_create_context(svn_client_ctx_t **ctx,
apr_pool_t *pool);
@@ -1046,7 +1082,7 @@
svn_boolean_t keep_last_origpath_on_truepath_collision,
apr_pool_t *pool);
-/*
+/**
* Similar to svn_client_args_to_target_array2() but with
* @a keep_last_origpath_on_truepath_collision always set to FALSE.
*
@@ -1465,16 +1501,15 @@
* @a path and everything under it fully recursively.
*
* @a path's parent must be under revision control already (unless
- * @a add_parents is TRUE), but @a path is not. If @a recursive is
- * set, then assuming @a path is a directory, all of its contents will
- * be scheduled for addition as well.
+ * @a add_parents is TRUE), but @a path is not.
*
* If @a force is not set and @a path is already under version
* control, return the error #SVN_ERR_ENTRY_EXISTS. If @a force is
* set, do not error on already-versioned items. When used on a
- * directory in conjunction with the @a recursive flag, this has the
- * effect of scheduling for addition unversioned files and directories
- * scattered deep within a versioned tree.
+ * directory in conjunction with a @a depth value greater than
+ * #svn_depth_empty, this has the effect of scheduling for addition
+ * any unversioned files and directories scattered within even a
+ * versioned tree (up to @a depth).
*
* If @a ctx->notify_func2 is non-NULL, then for each added item, call
* @a ctx->notify_func2 with @a ctx->notify_baton2 and the path of the
@@ -2353,11 +2388,6 @@
*
* @a scratch_pool will be cleared between invocations to the callback.
*
- * ### we might be revamping the status infrastructure, and this callback
- * ### could totally disappear by the end of 1.7 development. however, we
- * ### need to mark the STATUS parameter as "const" so that it is easier
- * ### to reason about who/what can modify those structures.
- *
* @since New in 1.7.
*/
typedef svn_error_t *(*svn_client_status_func_t)(
@@ -3346,6 +3376,90 @@
* @{
*/
+/* Details of an automatic merge. */
+typedef struct svn_client_automatic_merge_t svn_client_automatic_merge_t;
+
+/* Find the information needed to merge all unmerged changes from a source
+ * branch into a target branch. The information is the locations of the
+ * youngest common ancestor, merge base, and such like.
+ *
+ * Set *MERGE to the information needed to merge all unmerged changes
+ * (up to SOURCE_REVISION) from the source branch SOURCE_PATH_OR_URL @
+ * SOURCE_REVISION into the target WC at TARGET_WCPATH.
+ */
+svn_error_t *
+svn_client_find_automatic_merge(svn_client_automatic_merge_t **merge,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_wcpath,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Find out what kind of automatic merge would be needed, when the target
+ * is only known as a repository location rather than a WC.
+ *
+ * Like svn_client_find_automatic_merge() except that SOURCE_PATH_OR_URL @
+ * SOURCE_REVISION should refer to a repository location and not a WC.
+ *
+ * ### The result, *MERGE_P, may not be suitable for passing to
+ * svn_client_do_automatic_merge(). The target WC state would not be
+ * checked (as in the ALLOW_* flags). We should resolve this problem:
+ * perhaps add the allow_* params here, or provide another way of setting
+ * them; and perhaps ensure __do_...() will accept the result iff given a
+ * WC that matches the stored target location.
+ */
+svn_error_t *
+svn_client_find_automatic_merge_no_wc(
+ svn_client_automatic_merge_t **merge_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Perform an automatic merge.
+ *
+ * Merge according to MERGE into the WC at TARGET_WCPATH.
+ *
+ * The other parameters are as in svn_client_merge4().
+ *
+ * ### TODO: There's little point in this function being the only way the
+ * caller can use the result of svn_client_find_automatic_merge(). The
+ * contents of MERGE should be more public, or there should be other ways
+ * the caller can use it, or these two functions should be combined into
+ * one. I want to make it more public, and also possibly have more ways
+ * to use it in future (for example, do_automatic_merge_with_step_by_-
+ * step_confirmation).
+ */
+svn_error_t *
+svn_client_do_automatic_merge(const svn_client_automatic_merge_t *merge,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
+
+/* Return TRUE iff the automatic merge represented by MERGE is going to be
+ * a reintegrate-like merge: that is, merging in the opposite direction
+ * from the last full merge.
+ *
+ * This function exists because the merge is NOT really automatic and the
+ * client can be more friendly if it knows something about the differences.
+ */
+svn_boolean_t
+svn_client_automatic_merge_is_reintegrate_like(
+ const svn_client_automatic_merge_t *merge);
+
+
/** Merge changes from @a source1/@a revision1 to @a source2/@a revision2 into
* the working-copy path @a target_wcpath.
*
@@ -3713,6 +3827,15 @@
* @a target_path_or_url (as of @a target_peg_revision). If @a
* finding_merged is FALSE then find the revisions eligible for merging.
*
+ * @a source_start_revision and @a source_end_revision bound the
+ * operative range of revisions of the merge source which are
+ * described to the caller. If @a source_end_revision is of kind
+ * @c svn_opt_revision_unspecified, it is interpreted as the same
+ * revision as @a source_start_revision. If both are of kind
+ * @c svn_opt_revision_unspecified, no bounding occurs and the entire
+ * history of the merge source (up to @a source_peg_revision, per the
+ * typical default peg/operative revision behaviors) is considered.
+ *
* If @a depth is #svn_depth_empty consider only the explicit or
* inherited mergeinfo on @a target_path_or_url when calculating merged
* revisions from @a source_path_or_url. If @a depth is #svn_depth_infinity
@@ -4278,6 +4401,12 @@
* If @a make_parents is TRUE, create any non-existent parent directories
* also. Otherwise, the parent of @a dst_path must already exist.
*
+ * If @a allow_mixed_revisions is @c FALSE, #SVN_ERR_WC_MIXED_REVISIONS
+ * will be raised if the move source is a mixed-revision subtree.
+ * If @a allow_mixed_revisions is TRUE, a mixed-revision move source is
+ * allowed. This parameter should be set to FALSE except where backwards
+ * compatibility to svn_client_move6() is required.
+ *
* If non-NULL, @a revprop_table is a hash table holding additional,
* custom revision properties (<tt>const char *</tt> names mapped to
* <tt>svn_string_t *</tt> values) to be set on the new revision in
@@ -4298,7 +4427,26 @@
* @a commit_callback with @a commit_baton and a #svn_commit_info_t for
* the commit.
*
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_client_move7(const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ svn_boolean_t allow_mixed_revisions,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_client_move7(), but with allow_mixed_revisions always
+ * set to @c TRUE.
+ *
* @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
*/
svn_error_t *
svn_client_move6(const apr_array_header_t *src_paths,
@@ -4649,9 +4797,20 @@
/**
* Set @a *props to a hash table whose keys are absolute paths or URLs
- * of items on which property @a propname is set, and whose values are
- * `#svn_string_t *' representing the property value for @a propname
- * at that path.
+ * of items on which property @a propname is explicitly set, and whose
+ * values are <tt>svn_string_t *</tt> representing the property value for
+ * @a propname at that path.
+ *
+ * If @a inherited_props is not @c NULL, then set @a *inherited_props to a
+ * depth-first ordered array of #svn_prop_inherited_item_t * structures
+ * representing the properties inherited by @a target. If @a target is a
+ * working copy path, then properties inherited by @a target as far as the
+ * root of the working copy are obtained from the working copy's actual
+ * property values. Properties inherited from above the working copy root
+ * come from the inherited properties cache. If @a target is a URL, then
+ * the inherited properties come from the repository. If @a inherited_props
+ * is not @c NULL and no inheritable properties are found, then set
+ * @a *inherited_props to an empty array.
*
* Allocate @a *props, its keys, and its values in @a pool, use
* @a scratch_pool for temporary allocations.
@@ -4691,9 +4850,31 @@
* This function returns SVN_ERR_UNVERSIONED_RESOURCE when it is called on
* unversioned nodes.
*
- * @since New in 1.7.
+ * @since New in 1.8.
*/
svn_error_t *
+svn_client_propget5(apr_hash_t **props,
+ apr_array_header_t **inherited_props,
+ const char *propname,
+ const char *target, /* abspath or URL */
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_propget5 but doesn't support the retrieval of the
+ * properties inherited by @a target.
+ *
+ * @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.8 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
svn_client_propget4(apr_hash_t **props,
const char *propname,
const char *target, /* abspath or URL */
@@ -4786,22 +4967,22 @@
apr_pool_t *pool);
/**
- * Invoke @a receiver with @a receiver_baton to return the regular properties
- * of @a target, a URL or working copy path. @a receiver will be called
- * for each path encountered.
+ * Invoke @a receiver with @a receiver_baton to return the regular explicit, and
+ * possibly the inherited, properties of @a target, a URL or working copy path.
+ * @a receiver will be called for each path encountered.
*
* @a target is a WC path or a URL.
*
- * If @a revision->kind is #svn_opt_revision_unspecified, then get
- * properties from the working copy, if @a target is a working copy
- * path, or from the repository head if @a target is a URL. Else get
- * the properties as of @a revision. The actual node revision
- * selected is determined by the path as it exists in @a peg_revision.
- * If @a peg_revision->kind is #svn_opt_revision_unspecified, then it
- * defaults to #svn_opt_revision_head for URLs or
- * #svn_opt_revision_working for WC targets. Use the authentication
- * baton cached in @a ctx for authentication if contacting the
- * repository.
+ * If @a revision->kind is #svn_opt_revision_unspecified, then get the
+ * explicit (and possibly the inherited) properties from the working copy,
+ * if @a target is a working copy path, or from the repository head if
+ * @a target is a URL. Else get the properties as of @a revision.
+ * The actual node revision selected is determined by the path as it exists
+ * in @a peg_revision. If @a peg_revision->kind is
+ * #svn_opt_revision_unspecified, then it defaults to #svn_opt_revision_head
+ * for URLs or #svn_opt_revision_working for WC targets. Use the
+ * authentication baton cached in @a ctx for authentication if contacting
+ * the repository.
*
* If @a depth is #svn_depth_empty, list only the properties of
* @a target itself. If @a depth is #svn_depth_files, and
@@ -4819,11 +5000,44 @@
* of one of those changelists. If @a changelists is empty (or
* altogether @c NULL), no changelist filtering occurs.
*
+ * If @a get_target_inherited_props is true, then also return any inherited
+ * properties when @a receiver is called for @a target. If @a target is a
+ * working copy path, then properties inherited by @a target as far as the
+ * root of the working copy are obtained from the working copy's actual
+ * property values. Properties inherited from above the working copy
+ * root come from the inherited properties cache. If @a target is a URL,
+ * then the inherited properties come from the repository.
+ * If @a get_target_inherited_props is false, then no inherited properties
+ * are returned to @a receiver.
+ *
* If @a target is not found, return the error #SVN_ERR_ENTRY_NOT_FOUND.
*
- * @since New in 1.5.
+ * @since New in 1.8.
*/
svn_error_t *
+svn_client_proplist4(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
+ * Similar to svn_client_proplist4(), except that the @a receiver type is
+ * a #svn_proplist_receiver_t and there is no support for finding the
+ * inherited properties for @a target and there is no separate scratch pool.
+ *
+ * @since New in 1.5.
+ *
+ * @deprecated Provided for backward compatibility with the 1.8 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
svn_client_proplist3(const char *target,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *revision,
diff --git a/subversion/include/svn_config.h b/subversion/include/svn_config.h
index ad696d0..47189af 100644
--- a/subversion/include/svn_config.h
+++ b/subversion/include/svn_config.h
@@ -92,6 +92,8 @@
#define SVN_CONFIG_OPTION_PASSWORD_STORES "password-stores"
#define SVN_CONFIG_OPTION_KWALLET_WALLET "kwallet-wallet"
#define SVN_CONFIG_OPTION_KWALLET_SVN_APPLICATION_NAME_WITH_PID "kwallet-svn-application-name-with-pid"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT "ssl-client-cert-file-prompt"
/* The majority of options of the "auth" section
* has been moved to SVN_CONFIG_CATEGORY_SERVERS. */
#define SVN_CONFIG_SECTION_HELPERS "helpers"
@@ -106,6 +108,7 @@
#define SVN_CONFIG_OPTION_GLOBAL_IGNORES "global-ignores"
#define SVN_CONFIG_OPTION_LOG_ENCODING "log-encoding"
#define SVN_CONFIG_OPTION_USE_COMMIT_TIMES "use-commit-times"
+/** @deprecated Not used by Subversion since 2003/r847039 (well before 1.0) */
#define SVN_CONFIG_OPTION_TEMPLATE_ROOT "template-root"
#define SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS "enable-auto-props"
#define SVN_CONFIG_OPTION_NO_UNLOCK "no-unlock"
@@ -115,6 +118,10 @@
#define SVN_CONFIG_OPTION_MEMORY_CACHE_SIZE "memory-cache-size"
#define SVN_CONFIG_SECTION_TUNNELS "tunnels"
#define SVN_CONFIG_SECTION_AUTO_PROPS "auto-props"
+/** @since New in 1.8. */
+#define SVN_CONFIG_SECTION_WORKING_COPY "working-copy"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE "exclusive-locking"
/** @} */
/** @name Repository conf directory configuration files strings
@@ -131,12 +138,12 @@
#define SVN_CONFIG_OPTION_AUTHZ_DB "authz-db"
/** @since New in 1.7. */
#define SVN_CONFIG_OPTION_FORCE_USERNAME_CASE "force-username-case"
+/** @since New in 1.8. */
+#define SVN_CONFIG_OPTION_HOOKS_ENV "hooks-env"
#define SVN_CONFIG_SECTION_SASL "sasl"
#define SVN_CONFIG_OPTION_USE_SASL "use-sasl"
#define SVN_CONFIG_OPTION_MIN_SSF "min-encryption"
#define SVN_CONFIG_OPTION_MAX_SSF "max-encryption"
-/** @since New in 1.8. */
-#define SVN_CONFIG_SECTION_HOOKS_ENV "hooks-env"
/* For repository password database */
#define SVN_CONFIG_SECTION_USERS "users"
@@ -626,6 +633,26 @@
const char *fname,
apr_pool_t *pool);
+/** Create a deep copy of the config object @a src and return
+ * it in @a cfgp, allocating the memory in @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_dup(svn_config_t **cfgp,
+ svn_config_t *src,
+ apr_pool_t *pool);
+
+/** Create a deep copy of the config hash @a src_hash and return
+ * it in @a cfg_hash, allocating the memory in @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_config_copy_config(apr_hash_t **cfg_hash,
+ apr_hash_t *src_hash,
+ apr_pool_t *pool);
+
/** @} */
#ifdef __cplusplus
diff --git a/subversion/include/svn_dav.h b/subversion/include/svn_dav.h
index 6c57b69..eae3102 100644
--- a/subversion/include/svn_dav.h
+++ b/subversion/include/svn_dav.h
@@ -53,6 +53,15 @@
/** This header is *TEMPORARILY* used to transmit the delta base to the
* server. It contains a version resource URL for what is on the client.
+ *
+ * @note The HTTP delta draft recommends an If-None-Match header
+ * holding an entity tag corresponding to the base copy that the
+ * client has. In Subversion, it is much more natural to use a version
+ * URL to specify that base. We'd like, then, to use the If: header
+ * to specify the URL. Unfortunately, mod_dav sees all "State-token"
+ * items as lock tokens. So we'll use this custom header until mod_dav
+ * and other backend APIs are taught to be less rigid, at which time
+ * we can switch to using an If: header to report our base version.
*/
#define SVN_DAV_DELTA_BASE_HEADER "X-SVN-VR-Base"
@@ -177,6 +186,13 @@
* @since New in 1.7. */
#define SVN_DAV_VTXN_NAME_HEADER "SVN-VTxn-Name"
+/** This header is used in the OPTIONS response to identify named
+ * skel-based POST request types which the server is prepared to
+ * handle. (HTTP protocol v2 only)
+ * @since New in 1.8. */
+#define SVN_DAV_SUPPORTED_POSTS_HEADER "SVN-Supported-Posts"
+
+
/**
* @name Fulltext MD5 headers
*
@@ -283,6 +299,19 @@
#define SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY\
SVN_DAV_PROP_NS_DAV "svn/partial-replay"
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to get
+ * inherited properties. */
+#define SVN_DAV_NS_DAV_SVN_INHERITED_PROPS \
+ SVN_DAV_PROP_NS_DAV "svn/inherited-props"
+
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) knows how to
+ * properly handle ephemeral (that is, deleted-just-before-commit) FS
+ * transaction properties. */
+#define SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS\
+ SVN_DAV_PROP_NS_DAV "svn/ephemeral-txnprops"
+
/** @} */
/** @} */
diff --git a/subversion/include/svn_delta.h b/subversion/include/svn_delta.h
index 3f711fd..07a4fdc 100644
--- a/subversion/include/svn_delta.h
+++ b/subversion/include/svn_delta.h
@@ -452,6 +452,20 @@
apr_pool_t *pool);
+/** Send the @a contents of length @a len as a txdelta against an empty
+ * source directly to window-handler @a handler/@a handler_baton.
+ *
+ * All temporary allocation is performed in @a pool.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_txdelta_send_contents(const unsigned char *contents,
+ apr_size_t len,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
/** Prepare to apply a text delta. @a source is a readable generic stream
* yielding the source data, @a target is a writable generic stream to
* write target data to, and allocation takes place in a sub-pool of
@@ -483,6 +497,7 @@
+
/*** Producing and consuming svndiff-format text deltas. ***/
/** Prepare to produce an svndiff-format diff from text delta windows.
@@ -1309,8 +1324,9 @@
* Each path in @a paths is a const char *. The editor drive will be
* performed in the same order as @a paths. The paths should be sorted
* using something like svn_sort_compare_paths to ensure that a depth-first
- * pattern is observed for directory/file baton creation. Some callers may
- * need further customization of the order (ie. libsvn_delta/compat.c).
+ * pattern is observed for directory/file baton creation. If @a sort_paths
+ * is set, the function will sort the paths for you. Some callers may need
+ * further customization of the order (ie. libsvn_delta/compat.c).
*
* Use @a scratch_pool for all necessary allocations.
*
@@ -1320,6 +1336,7 @@
svn_delta_path_driver2(const svn_delta_editor_t *editor,
void *edit_baton,
const apr_array_header_t *paths,
+ svn_boolean_t sort_paths,
svn_delta_path_driver_cb_func_t callback_func,
void *callback_baton,
apr_pool_t *scratch_pool);
diff --git a/subversion/include/svn_diff.h b/subversion/include/svn_diff.h
index 4e3cd73..082e872 100644
--- a/subversion/include/svn_diff.h
+++ b/subversion/include/svn_diff.h
@@ -1063,12 +1063,12 @@
svn_boolean_t reverse;
} svn_patch_t;
-/* An opaque type representing an open patch file.
+/** An opaque type representing an open patch file.
*
* @since New in 1.7. */
typedef struct svn_patch_file_t svn_patch_file_t;
-/* Open @a patch_file at @a local_abspath.
+/** Open @a patch_file at @a local_abspath.
* Allocate @a patch_file in @a result_pool.
*
* @since New in 1.7. */
diff --git a/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h
index 58e9a25..b3cafa1 100644
--- a/subversion/include/svn_error_codes.h
+++ b/subversion/include/svn_error_codes.h
@@ -540,6 +540,11 @@
SVN_ERR_WC_CATEGORY_START + 39,
"Couldn't open a working copy file because access was denied")
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_WC_MIXED_REVISIONS,
+ SVN_ERR_WC_CATEGORY_START + 40,
+ "Mixed-revision working copy was found but not expected")
+
/* fs errors */
SVN_ERRDEF(SVN_ERR_FS_GENERAL,
@@ -777,6 +782,11 @@
SVN_ERR_FS_CATEGORY_START + 51,
"A packed revprop could not be read")
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_FS_REPPROP_CACHE_INIT_FAILURE,
+ SVN_ERR_FS_CATEGORY_START + 52,
+ "Could not initialize the revprop caching infrastructure.")
+
/* repos errors */
SVN_ERRDEF(SVN_ERR_REPOS_LOCKED,
@@ -1410,6 +1420,11 @@
SVN_ERR_MISC_CATEGORY_START + 36,
"too many memcached servers configured")
+ /** @since New in 1.8. */
+ SVN_ERRDEF(SVN_ERR_MALFORMED_VERSION_STRING,
+ SVN_ERR_MISC_CATEGORY_START + 37,
+ "failed to parse version number string")
+
/* command-line client errors */
SVN_ERRDEF(SVN_ERR_CL_ARG_PARSING_ERROR,
diff --git a/subversion/include/svn_fs.h b/subversion/include/svn_fs.h
index 908160f..82f4b93 100644
--- a/subversion/include/svn_fs.h
+++ b/subversion/include/svn_fs.h
@@ -402,6 +402,24 @@
apr_pool_t *pool);
+/**
+ * Take an exclusive lock on @a fs to prevent commits and then invoke
+ * @a freeze_body passing @a baton.
+ *
+ * @note The BDB backend doesn't implement this feature so most
+ * callers should not call this function directly but should use the
+ * higher level #svn_repos_freeze instead.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_freeze(svn_fs_t *fs,
+ svn_error_t *(*freeze_body)(void *baton, apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
+
+
+
/** Subversion filesystems based on Berkeley DB.
*
* The following functions are specific to Berkeley DB filesystems.
@@ -1957,6 +1975,44 @@
const char *path,
apr_pool_t *pool);
+/**
+ * Callback function type that gets presented with a immutable non-NULL
+ * @a contents of @a len bytes. Further parameters may be passed through
+ * in @a baton.
+ *
+ * Allocations must be made in @a pool.
+ *
+ * @since New in 1.8.
+ */
+typedef svn_error_t *
+(*svn_fs_process_contents_func_t)(const unsigned char *contents,
+ apr_size_t len,
+ void *baton,
+ apr_pool_t *pool);
+
+/** Attempts to efficiently provide the contents of the file @a path in
+ * @a root. If that succeeds, @a *success will be set to #TRUE and the
+ * contents will be passed to the the @a processor along with the given
+ * @a baton. Allocations take place in @a pool.
+ *
+ * This function is intended to support zero copy data processing. It may
+ * not be implemented for all data backends or not applicable for certain
+ * content. In that case, @a *success will always be #FALSE. Also, this
+ * is a best-effort function which means there is no guarantee that e.g.
+ * @a processor gets called at for any content.
+ *
+ * @note @a processor is expected to be relatively short function with
+ * at most O(content size) runtime.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_fs_try_process_file_contents(svn_boolean_t *success,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
/** Create a new file named @a path in @a root. The file's initial contents
* are the empty string, and it has no properties. @a root must be the
diff --git a/subversion/include/svn_io.h b/subversion/include/svn_io.h
index bd38c8d..fdb02d5 100644
--- a/subversion/include/svn_io.h
+++ b/subversion/include/svn_io.h
@@ -610,13 +610,13 @@
/** Set @a *different_p12 to non-zero if @a file1 and @a file2 have different
* sizes, else set to zero. Do the similar for @a *different_p23 with
* @a file2 and @a file3, and @a *different_p13 for @a file1 and @a file3.
- * All three of @a file1, @a file2 and @a file3 are utf8-encoded.
+ * The filenames @a file1, @a file2 and @a file3 are utf8-encoded.
*
* Setting @a *different_p12 to zero does not mean the files definitely
* have the same size, it merely means that the sizes are not
* definitely different. That is, if the size of one or both files
- * cannot be determined, then the sizes are not known to be different,
- * so @a *different_p12 is set to 0.
+ * cannot be determined (due to stat() returning an error), then the sizes
+ * are not known to be different, so @a *different_p12 is set to 0.
*/
svn_error_t *
svn_io_filesizes_three_different_p(svn_boolean_t *different_p12,
@@ -1155,9 +1155,9 @@
svn_error_t *
svn_stream_close(svn_stream_t *stream);
-/** Reset a generic stream back to its origin. E.g. On a file this would be
+/** Reset a generic stream back to its origin. (E.g. On a file this would be
* implemented as a seek to position 0). This function returns a
- * #SVN_ERR_STREAM_RESET_NOT_SUPPORTED error when the stream doesn't
+ * #SVN_ERR_STREAM_SEEK_NOT_SUPPORTED error when the stream doesn't
* implement resetting.
*
* @since New in 1.7.
@@ -2200,9 +2200,9 @@
int version,
apr_pool_t *pool);
-/* Read a line of text from a file, up to a specified length.
+/** Read a line of text from a file, up to a specified length.
*
- * Allocate @a *stringbuf in @a result_pool, and read into it one line
+ * Allocate @a *stringbuf in @a result_pool, and read into it one line
* from @a file. Reading stops either after a line-terminator was found
* or after @a max_len bytes have been read.
*
diff --git a/subversion/include/svn_mergeinfo.h b/subversion/include/svn_mergeinfo.h
index 6b330f1..54cf5c1 100644
--- a/subversion/include/svn_mergeinfo.h
+++ b/subversion/include/svn_mergeinfo.h
@@ -332,6 +332,7 @@
* if called in a loop.
*
* @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
*/
SVN_DEPRECATED
svn_error_t *
diff --git a/subversion/include/svn_props.h b/subversion/include/svn_props.h
index 2fd666a..5b0782a 100644
--- a/subversion/include/svn_props.h
+++ b/subversion/include/svn_props.h
@@ -85,6 +85,23 @@
apr_pool_t *pool);
+/** A structure to represent inherited properties.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_prop_inherited_item_t
+{
+ /** The absolute working copy path, relative filesystem path, or URL from
+ * which the properties in @a prop_hash are inherited. */
+ const char *path_or_url;
+
+ /** A hash of (const char *) inherited property names, and (svn_string_t *)
+ * property values. */
+ apr_hash_t *prop_hash;
+
+} svn_prop_inherited_item_t;
+
+
/**
* Given a hash (keys <tt>const char *</tt> and values <tt>const
* svn_string_t</tt>) of properties, returns an array of svn_prop_t
@@ -367,6 +384,14 @@
*/
#define SVN_PROP_MERGEINFO SVN_PROP_PREFIX "mergeinfo"
+/** Prefix for all Subersion inhertiable properties. */
+#define SVN_PROP_INHERITABLE_PREFIX SVN_PROP_PREFIX "inheritable-"
+
+/** Property used to record inheritable configuration auto-props. */
+#define SVN_PROP_INHERITABLE_AUTO_PROPS SVN_PROP_INHERITABLE_PREFIX "auto-props"
+
+/** Property used to record inheritable configuration ignores. */
+#define SVN_PROP_INHERITABLE_IGNORES SVN_PROP_INHERITABLE_PREFIX "ignores"
/** Meta-data properties.
*
@@ -540,6 +565,44 @@
/** @} */
+/**
+ * These are reserved properties attached to a "transaction" object in
+ * the repository filesystem in advance of the pre-commit hook script
+ * running on the server, but then automatically removed from the
+ * transaction before its promotion to a new revision.
+ *
+ * @defgroup svn_props_ephemeral_txnprops Ephemeral transaction properties
+ * @{
+ */
+
+/** The prefix used for all (ephemeral) transaction properties. */
+#define SVN_PROP_TXN_PREFIX SVN_PROP_PREFIX "txn-"
+
+/** Identifies the client version compability level. For clients
+ * compiled against Subversion libraries, this is @c SVN_VER_NUMBER.
+ * Third-party implementations are advised to use similar formatting
+ * for values of this property.
+ */
+#define SVN_PROP_TXN_CLIENT_COMPAT_VERSION \
+ SVN_PROP_TXN_PREFIX "client-compat-version"
+
+/** Identifies the client's user agent string, if any. */
+#define SVN_PROP_TXN_USER_AGENT \
+ SVN_PROP_TXN_PREFIX "user-agent"
+
+/** The prefix reserved for copies of (ephemeral) transaction
+ * properties designed to outlive the transaction. Administrators may
+ * choose to, in their pre-commit hook scripts, copy the values of one
+ * or more properties named @c SVN_PROP_TXN_PREFIX + "something"
+ * to new properties named @c SVN_PROP_REVISION_PREFIX + "something",
+ * allowing that information to survive the commit-time removal of
+ * ephemeral transaction properties.
+ */
+#define SVN_PROP_REVISION_PREFIX SVN_PROP_PREFIX "revision-"
+
+
+/** @} */
+
/** @} */
diff --git a/subversion/include/svn_ra.h b/subversion/include/svn_ra.h
index b193c6f..1ad3324 100644
--- a/subversion/include/svn_ra.h
+++ b/subversion/include/svn_ra.h
@@ -874,12 +874,12 @@
* or @c SVN_PROP_REVISION_AUTHOR.
*
* Before @c close_edit returns, but after the commit has succeeded,
- * it will invoke @a callback (if non-NULL) with the new revision number,
- * the commit date (as a <tt>const char *</tt>), commit author (as a
- * <tt>const char *</tt>), and @a callback_baton as arguments. If
- * @a callback returns an error, that error will be returned from @c
- * close_edit, otherwise @c close_edit will return successfully
- * (unless it encountered an error before invoking @a callback).
+ * it will invoke @a commit_callback (if non-NULL) with filled-in
+ * #svn_commit_info_t *, @a commit_baton, and @a pool or some subpool
+ * thereof as arguments. If @a commit_callback returns an error, that error
+ * will be returned from @c * close_edit, otherwise @c close_edit will return
+ * successfully (unless it encountered an error before invoking
+ * @a commit_callback).
*
* The callback will not be called if the commit was a no-op
* (i.e. nothing was committed);
@@ -905,8 +905,8 @@
const svn_delta_editor_t **editor,
void **edit_baton,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
apr_pool_t *pool);
@@ -926,8 +926,8 @@
const svn_delta_editor_t **editor,
void **edit_baton,
const char *log_msg,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
apr_pool_t *pool);
@@ -1922,6 +1922,28 @@
apr_pool_t *pool);
/**
+ * Set @a *inherited_props to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures representing the properties
+ * inherited by @a path at @a revision (or the 'head' revision if
+ * @a revision is @c SVN_INVALID_REVNUM). Interpret @a path relative to
+ * the URL in @a session. Use @a pool for all allocations. If no
+ * inheritable properties are found, then set @a *inherited_props to
+ * an empty array.
+ *
+ * Allocated @a *inherited_props in @a result_pool, use @a scratch_pool
+ * for temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_ra_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **inherited_props,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/**
* @defgroup Capabilities Dynamically query the server's capabilities.
*
* @{
@@ -1993,6 +2015,21 @@
*/
#define SVN_RA_CAPABILITY_ATOMIC_REVPROPS "atomic-revprops"
+/**
+ * The capability to get inherited properties.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_RA_CAPABILITY_INHERITED_PROPS "inherited-props"
+
+/**
+ * The capability of a server to automatically remove transaction
+ * properties prefixed with SVN_PROP_EPHEMERAL_PREFIX.
+ *
+ * @since New in 1.8.
+ */
+#define SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS "ephemeral-txnprops"
+
/* *** PLEASE READ THIS IF YOU ADD A NEW CAPABILITY ***
*
* RA layers generally fetch all capabilities when asked about any
diff --git a/subversion/include/svn_ra_svn.h b/subversion/include/svn_ra_svn.h
index 6ba3e52..f98963f 100644
--- a/subversion/include/svn_ra_svn.h
+++ b/subversion/include/svn_ra_svn.h
@@ -62,6 +62,10 @@
#define SVN_RA_SVN_CAP_PARTIAL_REPLAY "partial-replay"
/* maps to SVN_RA_CAPABILITY_ATOMIC_REVPROPS */
#define SVN_RA_SVN_CAP_ATOMIC_REVPROPS "atomic-revprops"
+/* maps to SVN_RA_CAPABILITY_INHERITED_PROPERTIES: */
+#define SVN_RA_SVN_CAP_INHERITED_PROPS "inherited-props"
+/* maps to SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS */
+#define SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS "ephemeral-txnprops"
/** ra_svn passes @c svn_dirent_t fields over the wire as a list of
* words, these are the values used to represent each field.
@@ -158,6 +162,66 @@
typedef svn_error_t *(*svn_ra_svn_edit_callback)(void *baton);
/**
+ * List of all commands supported by the SVN:// protocol.
+ *
+ * @since New in 1.8
+ */
+typedef enum svn_ra_svn_cmd_t
+{
+ svn_ra_svn_cmd_target_rev,
+ svn_ra_svn_cmd_open_root,
+ svn_ra_svn_cmd_delete_entry,
+ svn_ra_svn_cmd_add_dir,
+ svn_ra_svn_cmd_open_dir,
+ svn_ra_svn_cmd_change_dir_prop,
+ svn_ra_svn_cmd_close_dir,
+ svn_ra_svn_cmd_absent_dir,
+ svn_ra_svn_cmd_add_file,
+ svn_ra_svn_cmd_open_file,
+ svn_ra_svn_cmd_change_file_prop,
+ svn_ra_svn_cmd_close_file,
+ svn_ra_svn_cmd_absent_file,
+ svn_ra_svn_cmd_textdelta_chunk,
+ svn_ra_svn_cmd_textdelta_end,
+ svn_ra_svn_cmd_apply_textdelta,
+ svn_ra_svn_cmd_close_edit,
+ svn_ra_svn_cmd_abort_edit,
+
+ svn_ra_svn_cmd_set_path,
+ svn_ra_svn_cmd_delete_path,
+ svn_ra_svn_cmd_link_path,
+ svn_ra_svn_cmd_finish_report,
+ svn_ra_svn_cmd_abort_report,
+
+ svn_ra_svn_cmd_reparent,
+ svn_ra_svn_cmd_get_latest_rev,
+ svn_ra_svn_cmd_get_dated_rev,
+ svn_ra_svn_cmd_change_rev_prop2,
+ svn_ra_svn_cmd_change_rev_prop,
+ svn_ra_svn_cmd_rev_proplist,
+ svn_ra_svn_cmd_rev_prop,
+ svn_ra_svn_cmd_get_file,
+ svn_ra_svn_cmd_update,
+ svn_ra_svn_cmd_switch,
+ svn_ra_svn_cmd_status,
+ svn_ra_svn_cmd_diff,
+ svn_ra_svn_cmd_check_path,
+ svn_ra_svn_cmd_stat,
+ svn_ra_svn_cmd_get_file_revs,
+ svn_ra_svn_cmd_lock,
+ svn_ra_svn_cmd_unlock,
+ svn_ra_svn_cmd_get_lock,
+ svn_ra_svn_cmd_get_locks,
+ svn_ra_svn_cmd_replay,
+ svn_ra_svn_cmd_replay_range,
+ svn_ra_svn_cmd_get_deleted_rev,
+ svn_ra_svn_cmd_get_iprops,
+ svn_ra_svn_cmd_finish_replay,
+
+ svn_ra_svn_cmd__last
+} svn_ra_svn_cmd_t;
+
+/**
* Set the shim callbacks to be used by @a conn to @a shim_callbacks.
*
* @note This is a private API, external consumers should not use it.
@@ -170,10 +234,32 @@
* input/output files.
*
* Either @a sock or @a in_file/@a out_file must be set, not both.
- * Specify the desired network data compression level (zlib) from
- * 0 (no compression) to 9 (best but slowest).
+ * @a compression_level specifies the desired network data compression
+ * level (zlib) from 0 (no compression) to 9 (best but slowest).
+ *
+ * To reduce the overhead of checking for cancellation requests from the
+ * data receiver, set @a error_check_interval to some non-zero value.
+ * It defines the number of bytes that must have been sent since the last
+ * check before the next check will be made.
+ *
+ * Allocate the result in @a pool.
+ *
+ * @since New in 1.8
+ */
+svn_ra_svn_conn_t *svn_ra_svn_create_conn3(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ int compression_level,
+ apr_size_t zero_copy_limit,
+ apr_size_t error_check_interval,
+ apr_pool_t *pool);
+
+/** Similar to svn_ra_svn_create_conn3() but disables the zero copy code
+ * path and sets the error checking interval to 0.
*
* @since New in 1.7.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
*/
svn_ra_svn_conn_t *
svn_ra_svn_create_conn2(apr_socket_t *sock,
@@ -182,7 +268,7 @@
int compression_level,
apr_pool_t *pool);
-/** Similar to svn_ra_svn_create_conn2() but uses default
+/** Similar to svn_ra_svn_create_conn2() but uses the default
* compression level (#SVN_DELTA_COMPRESSION_LEVEL_DEFAULT) for network
* transmissions.
*
@@ -219,6 +305,13 @@
int
svn_ra_svn_compression_level(svn_ra_svn_conn_t *conn);
+/** Return the zero-copy data block limit to use for network transmissions
+ *
+ * @since New in 1.8.
+ */
+apr_size_t
+svn_ra_svn_zero_copy_limit(svn_ra_svn_conn_t *conn);
+
/** Returns the remote address of the connection as a string, if known,
* or NULL if inapplicable. */
const char *
@@ -448,13 +541,28 @@
/** Write a command over the network, using the same format string notation
* as svn_ra_svn_write_tuple().
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * Use svn_ra_svn_write_templated_cmd instead.
*/
+SVN_DEPRECATED
svn_error_t *
svn_ra_svn_write_cmd(svn_ra_svn_conn_t *conn,
apr_pool_t *pool,
const char *cmdname,
const char *fmt, ...);
+/** Write a command of type @a cmd over the network connection @a conn.
+ * The parameters to be provided are command-specific. @a pool will be
+ * used for allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_ra_svn_write_templated_cmd(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_cmd_t cmd, ...);
+
/** Write a successful command response over the network, using the
* same format string notation as svn_ra_svn_write_tuple(). Do not use
* partial tuples with this function; if you need to use partial
diff --git a/subversion/include/svn_repos.h b/subversion/include/svn_repos.h
index 32d362f..5c584cb 100644
--- a/subversion/include/svn_repos.h
+++ b/subversion/include/svn_repos.h
@@ -653,6 +653,20 @@
svn_repos_recover(const char *path,
apr_pool_t *pool);
+/**
+ * Take an exclusive lock on @a path to prevent commits and then
+ * invoke @a freeze_body passing @a baton. The repository may be
+ * readable by Subversion while frozen, or it may be unreadable,
+ * depending on which FS backend the repository uses.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_freeze(const char *path,
+ svn_error_t *(*freeze_body)(void *baton, apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool);
+
/** This function is a wrapper around svn_fs_berkeley_logfiles(),
* returning log file paths relative to the root of the repository.
*
@@ -764,16 +778,25 @@
svn_repos_post_unlock_hook(svn_repos_t *repos,
apr_pool_t *pool);
-/** Set the environment that @a repos's hooks will inherit to @a hooks_env,
- * a hash table where keys and values represent names and values of environment
- * variables. @a hooks_env must live at least as long as @a repos.
+/** Set the environment that @a repos's hooks will inherit.
+ * The environment is specified in a file at @a hooks_env_path.
+ * If @a hooks_env_path is @c NULL, the file is searched at its
+ * default location in the repository. If @a hooks_env_path is
+ * not absolute, it specifies a path relative to the parent of
+ * the file's default location in the repository.
*
- * If this function is not called, hooks will run in an empty environment.
+ * The @a result_pool should be the same pool that @a repos was allocated in.
+ * The @a scratch_pool is used for temporary allocations.
+ *
+ * If this function is not called, or if the file does not list any
+ * environment variables, hooks will run in an empty environment.
*
* @since New in 1.8. */
-void
+svn_error_t *
svn_repos_hooks_setenv(svn_repos_t *repos,
- apr_hash_t *hooks_env);
+ const char *hooks_env_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/** @} */
@@ -819,6 +842,13 @@
* avoid sending data through @a editor/@a edit_baton which is not
* authorized for transmission.
*
+ * @a zero_copy_limit controls up to which size in bytes data blocks may
+ * be sent using the zero-copy code path. On that path, a number of
+ * in-memory copy operations have been eliminated to maximize throughput.
+ * However, until the whole block has been pushed to the network stack,
+ * other clients may get blocked. Thus, be careful when using larger
+ * values here. 0 disables the optimization.
+ *
* All allocation for the context and collected state will occur in
* @a pool.
*
@@ -851,7 +881,33 @@
* than or equal to the depth of the working copy, then the editor
* operations will affect only paths at or above @a depth.
*
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_begin_report3(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_size_t zero_copy_limit,
+ apr_pool_t *pool);
+
+/**
+ * The same as svn_repos_begin_report3(), but setting the @a zero_copy_limit
+ * to 0.
+ *
* @since New in 1.5.
+ *
+ * @deprecated Provided for backward compatibility with the 1.7 API.
*/
svn_error_t *
svn_repos_begin_report2(void **report_baton,
@@ -903,7 +959,7 @@
/**
- * Given a @a report_baton constructed by svn_repos_begin_report2(),
+ * Given a @a report_baton constructed by svn_repos_begin_report3(),
* record the presence of @a path, at @a revision with depth @a depth,
* in the current tree.
*
@@ -974,7 +1030,7 @@
apr_pool_t *pool);
/**
- * Given a @a report_baton constructed by svn_repos_begin_report2(),
+ * Given a @a report_baton constructed by svn_repos_begin_report3(),
* record the presence of @a path in the current tree, containing the contents
* of @a link_path at @a revision with depth @a depth.
*
@@ -1040,7 +1096,7 @@
svn_boolean_t start_empty,
apr_pool_t *pool);
-/** Given a @a report_baton constructed by svn_repos_begin_report2(),
+/** Given a @a report_baton constructed by svn_repos_begin_report3(),
* record the non-existence of @a path in the current tree.
*
* @a path may not be underneath a path on which svn_repos_set_path3()
@@ -1056,7 +1112,7 @@
const char *path,
apr_pool_t *pool);
-/** Given a @a report_baton constructed by svn_repos_begin_report2(),
+/** Given a @a report_baton constructed by svn_repos_begin_report3(),
* finish the report and drive the editor as specified when the report
* baton was constructed.
*
@@ -1073,7 +1129,7 @@
apr_pool_t *pool);
-/** Given a @a report_baton constructed by svn_repos_begin_report2(),
+/** Given a @a report_baton constructed by svn_repos_begin_report3(),
* abort the report. This function can be called anytime before
* svn_repos_finish_report() is called.
*
@@ -1147,7 +1203,7 @@
* the total size of the delta.
*
* ### svn_repos_dir_delta2 is mostly superseded by the reporter
- * ### functionality (svn_repos_begin_report2 and friends).
+ * ### functionality (svn_repos_begin_report3 and friends).
* ### svn_repos_dir_delta2 does allow the roots to be transaction
* ### roots rather than just revision roots, and it has the
* ### entry_props flag. Almost all of Subversion's own code uses the
@@ -1284,14 +1340,14 @@
*
* Calling @a (*editor)->close_edit completes the commit.
*
- * If @a callback is non-NULL, then before @c close_edit returns (but
+ * If @a commit_callback is non-NULL, then before @c close_edit returns (but
* after the commit has succeeded) @c close_edit will invoke
- * @a callback with a filled-in #svn_commit_info_t *, @a callback_baton,
- * and @a pool or some subpool thereof as arguments. If @a callback
+ * @a commit_callback with a filled-in #svn_commit_info_t *, @a commit_baton,
+ * and @a pool or some subpool thereof as arguments. If @a commit_callback
* returns an error, that error will be returned from @c close_edit,
* otherwise if there was a post-commit hook failure, then that error
* will be returned with code SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED.
- * (Note that prior to Subversion 1.6, @a callback cannot be NULL; if
+ * (Note that prior to Subversion 1.6, @a commit_callback cannot be NULL; if
* you don't need a callback, pass a dummy function.)
*
* Calling @a (*editor)->abort_edit aborts the commit, and will also
@@ -1312,8 +1368,8 @@
const char *repos_url,
const char *base_path,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_repos_authz_callback_t authz_callback,
void *authz_baton,
apr_pool_t *pool);
@@ -1338,8 +1394,8 @@
const char *base_path,
const char *user,
const char *log_msg,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_repos_authz_callback_t authz_callback,
void *authz_baton,
apr_pool_t *pool);
@@ -2293,7 +2349,7 @@
* @{
*
* As it turns out, the svn_repos_replay2(), svn_repos_dir_delta2() and
- * svn_repos_begin_report2() interfaces can be extremely useful for
+ * svn_repos_begin_report3() interfaces can be extremely useful for
* examining the repository, or more exactly, changes to the repository.
* These drivers allows for differences between two trees to be
* described using an editor.
@@ -2346,7 +2402,7 @@
* repos's filesystem.
*
* The editor can also be driven by svn_repos_dir_delta2() or
- * svn_repos_begin_report2(), but unless you have special needs,
+ * svn_repos_begin_report3(), but unless you have special needs,
* svn_repos_replay2() is preferred.
*
* Invoke svn_repos_node_from_baton() on @a edit_baton to obtain the root
@@ -3219,6 +3275,31 @@
void *authz_read_baton,
apr_pool_t *pool);
+/**
+ * Set @a *inherited_values to a depth-first ordered array of
+ * #svn_prop_inherited_item_t * structures (the path_or_url members of
+ * which are relative filesystem paths) representing the properties
+ * inherited by @a path in @a root. If no properties are inherited,
+ * then set @a *inherited_values to an empty array.
+ *
+ * If optional @a authz_read_func is non-NULL, then use this function
+ * (along with optional @a authz_read_baton) to check the readability
+ * of each parent path from which properties are inherited. Silently omit
+ * properties for unreadable parent paths.
+ *
+ * Allocate @a *inherited_props in @a result_pool. Use @a scratch_pool for
+ * temporary allocations.
+ *
+ * @since New in 1.8.
+ */
+svn_error_t *
+svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/** Capabilities **/
diff --git a/subversion/include/svn_string.h b/subversion/include/svn_string.h
index 69c6dc4..9eb04e9 100644
--- a/subversion/include/svn_string.h
+++ b/subversion/include/svn_string.h
@@ -307,6 +307,45 @@
svn_stringbuf_appendcstr(svn_stringbuf_t *targetstr,
const char *cstr);
+/** Read @a count bytes from @a bytes and insert them into @a str at
+ * position @a pos and following. The resulting string will be
+ * @c count+str->len bytes long. If @c pos is larger or equal to the
+ * number of bytes currently used in @a str, simply append @a bytes.
+ *
+ * Reallocs if necessary. @a str is affected, nothing else is.
+ *
+ * @note The inserted string may be a sub-range if @a str.
+ */
+void
+svn_stringbuf_insert(svn_stringbuf_t *str,
+ apr_size_t pos,
+ const char *bytes,
+ apr_size_t count);
+
+/** Removes @a count bytes from @a str, starting at position @a pos.
+ * If that range exceeds the current string data, @a str gets truncated
+ * at @a pos. If the latter is larger or equal to @c str->pos, this will
+ * be a no-op. Otherwise, the resulting string will be @c str->len-count
+ * bytes long.
+ */
+void
+svn_stringbuf_remove(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t count);
+
+/** Faster but functionally equivalent to the following sequence:
+ * @code
+ * svn_stringbuf_remove(str, pos, old_count);
+ * svn_stringbuf_insert(str, pos, bytes, new_count);
+ * @endcode
+ */
+void
+svn_stringbuf_replace(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t old_count,
+ const char *bytes,
+ apr_size_t new_count);
+
/** Return a duplicate of @a original_string. */
svn_stringbuf_t *
svn_stringbuf_dup(const svn_stringbuf_t *original_string, apr_pool_t *pool);
diff --git a/subversion/include/svn_types.h b/subversion/include/svn_types.h
index 4820469..2c48b25 100644
--- a/subversion/include/svn_types.h
+++ b/subversion/include/svn_types.h
@@ -509,7 +509,7 @@
svn_depth_t
svn_depth_from_word(const char *word);
-/* Return #svn_depth_infinity if boolean @a recurse is TRUE, else
+/** Return #svn_depth_infinity if boolean @a recurse is TRUE, else
* return #svn_depth_files.
*
* @note New code should never need to use this, it is called only
@@ -520,7 +520,7 @@
#define SVN_DEPTH_INFINITY_OR_FILES(recurse) \
((recurse) ? svn_depth_infinity : svn_depth_files)
-/* Return #svn_depth_infinity if boolean @a recurse is TRUE, else
+/** Return #svn_depth_infinity if boolean @a recurse is TRUE, else
* return #svn_depth_immediates.
*
* @note New code should never need to use this, it is called only
@@ -531,7 +531,7 @@
#define SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse) \
((recurse) ? svn_depth_infinity : svn_depth_immediates)
-/* Return #svn_depth_infinity if boolean @a recurse is TRUE, else
+/** Return #svn_depth_infinity if boolean @a recurse is TRUE, else
* return #svn_depth_empty.
*
* @note New code should never need to use this, it is called only
@@ -542,7 +542,7 @@
#define SVN_DEPTH_INFINITY_OR_EMPTY(recurse) \
((recurse) ? svn_depth_infinity : svn_depth_empty)
-/* Return a recursion boolean based on @a depth.
+/** Return a recursion boolean based on @a depth.
*
* Although much code has been converted to use depth, some code still
* takes a recurse boolean. In most cases, it makes sense to treat
@@ -1261,7 +1261,7 @@
*/
typedef unsigned long svn_linenum_t;
-/* The maximum value of an svn_linenum_t.
+/** The maximum value of an svn_linenum_t.
*
* @since New in 1.7.
*/
diff --git a/subversion/include/svn_version.h b/subversion/include/svn_version.h
index 9131c10..7b01baf 100644
--- a/subversion/include/svn_version.h
+++ b/subversion/include/svn_version.h
@@ -34,6 +34,7 @@
#ifndef APR_STRINGIFY
#include <apr_general.h>
#endif
+#include <apr_tables.h>
#include "svn_types.h"
@@ -256,6 +257,153 @@
svn_subr_version(void);
+/**
+ * Extended version infomation, including info about the running system.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_version_extended_t svn_version_extended_t;
+
+/**
+ * Return version information for the running program. If @a verbose
+ * is true, collect extra information that may be expensive to
+ * retrieve (for example, the OS release name, list of shared
+ * libraries, etc.). Use @a pool for all allocations.
+ *
+ * @since New in 1.8.
+ */
+const svn_version_extended_t *
+svn_version_extended(svn_boolean_t verbose,
+ apr_pool_t *pool);
+
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The date when the libsvn_subr library was compiled, in the
+ * format defined by the C standard macro @c __DATE__.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_build_date(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The time when the libsvn_subr library was compiled, in the
+ * format defined by the C standard macro @c __TIME__.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_build_time(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The canonical host triplet (arch-vendor-osname) of the
+ * system where libsvn_subr was compiled.
+ *
+ * @note On Unix-like systems (includng Mac OS X), this string is the
+ * same as the output of the config.guess script.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_build_host(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The localized copyright notice.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_copyright(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The canonical host triplet (arch-vendor-osname) of the
+ * system where the current process is running.
+ *
+ * @note This string may not be the same as the output of config.guess
+ * on the same system.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_runtime_host(const svn_version_extended_t *ext_info);
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return The "commercial" release name of the running operating
+ * system, if available. Not to be confused with, e.g., the output of
+ * "uname -v" or "uname -r". The returned value may be @c NULL.
+ *
+ * @since New in 1.8.
+ */
+const char *
+svn_version_ext_runtime_osname(const svn_version_extended_t *ext_info);
+
+/**
+ * Dependent library information.
+ * Describes the name and versions of known dependencies
+ * used by libsvn_subr.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_version_ext_linked_lib_t
+{
+ const char *name; /**< Library name */
+ const char *compiled_version; /**< Compile-time version string */
+ const char *runtime_version; /**< Run-time version string (optional) */
+} svn_version_ext_linked_lib_t;
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return Array of svn_version_ext_linked_lib_t describing dependent
+ * libraries. The returned value may be @c NULL.
+ *
+ * @since New in 1.8.
+ */
+const apr_array_header_t *
+svn_version_ext_linked_libs(const svn_version_extended_t *ext_info);
+
+
+/**
+ * Loaded shared library information.
+ * Describes the name and, where available, version of the shared libraries
+ * loaded by the running program.
+ *
+ * @since New in 1.8.
+ */
+typedef struct svn_version_ext_loaded_lib_t
+{
+ const char *name; /**< Library name */
+ const char *version; /**< Library version (optional) */
+} svn_version_ext_loaded_lib_t;
+
+
+/**
+ * Accessor for svn_version_extended_t.
+ *
+ * @return Array of svn_version_ext_loaded_lib_t describing loaded
+ * shared libraries. The returned value may be @c NULL.
+ *
+ * @note On Mac OS X, the loaded frameworks, private frameworks and
+ * system libraries will not be listed.
+ *
+ * @since New in 1.8.
+ */
+const apr_array_header_t *
+svn_version_ext_loaded_libs(const svn_version_extended_t *ext_info);
+
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/include/svn_wc.h b/subversion/include/svn_wc.h
index 9e6dad2..715dfe5 100644
--- a/subversion/include/svn_wc.h
+++ b/subversion/include/svn_wc.h
@@ -828,7 +828,7 @@
svn_wc_external_item2_create(svn_wc_external_item2_t **item,
apr_pool_t *pool);
-/* Same as svn_wc_external_item2_create() except the pointer to the new
+/** Same as svn_wc_external_item2_create() except the pointer to the new
* empty item is 'const' which is stupid since the next thing you need to do
* is fill in its fields.
*
@@ -1142,7 +1142,7 @@
/** The mergeinfo on path was updated. @since New in 1.7. */
svn_wc_notify_merge_record_info,
- /** An working copy directory was upgraded to the latest format
+ /** A working copy directory was upgraded to the latest format.
* @since New in 1.7. */
svn_wc_notify_upgraded_path,
@@ -1593,10 +1593,6 @@
svn_wc_conflict_reason_replaced,
/** Object is moved away. @since New in 1.8. */
svn_wc_conflict_reason_moved_away,
- /** Object is moved away and was edited post-move. @since New in 1.8. */
- /* ### Do we really need this. The edit state is on a different node,
- which might just change while the tree conflict exists? */
- svn_wc_conflict_reason_moved_away_and_edited,
/** Object is moved here. @since New in 1.8. */
svn_wc_conflict_reason_moved_here
@@ -1954,7 +1950,7 @@
*
* Set the @c local_abspath field of the created struct to @a local_abspath
* (which must be an absolute path), the @c kind field
- * to #svn_wc_conflict_kind_prop, the @c node_kind to @a node_kind, and
+ * to #svn_wc_conflict_kind_property, the @c node_kind to @a node_kind, and
* the @c property_name to @a property_name.
*
* @note: It is the caller's responsibility to set the other required fields
@@ -3729,7 +3725,7 @@
* @since New in 1.8. */
const char *moved_to_abspath;
- /* TRUE iff the item is a file brought in by an svn:externals definition.
+ /** TRUE iff the item is a file brought in by an svn:externals definition.
* @since New in 1.8. */
svn_boolean_t file_external;
@@ -3855,7 +3851,7 @@
/**
- * Same as #svn_wc_status2_t, but without the #svn_lock_t 'repos_lock' field.
+ * Same as #svn_wc_status2_t, but without the #svn_lock_t 'repos_lock', const char 'url', #svn_revnum_t 'ood_last_cmt_rev', apr_time_t 'ood_last_cmt_date', #svn_node_kind_t 'ood_kind', const char 'ood_last_cmt_author', #svn_wc_conflict_description_t 'tree_conflict', #svn_boolean_t 'file_external', #svn_wc_status_kind 'pristine_text_status', and #svn_wc_status_kind 'pristine_prop_status' fields.
*
* @deprecated Provided for backward compatibility with the 1.1 API.
*/
@@ -3993,11 +3989,6 @@
*
* @a scratch_pool will be cleared between invocations to the callback.
*
- * ### we might be revamping the status infrastructure, and this callback
- * ### could totally disappear by the end of 1.7 development. however, we
- * ### need to mark the STATUS parameter as "const" so that it is easier
- * ### to reason about who/what can modify those structures.
- *
* @since New in 1.7.
*/
typedef svn_error_t *(*svn_wc_status_func4_t)(void *baton,
@@ -4414,6 +4405,8 @@
* Use @a scratch_pool for temporary allocations.
*
* @since New in 1.7.
+ * @deprecated Provided for backward compatibility with the 1.7 API.
+ * @see svn_client_move7()
*/
svn_error_t *
svn_wc_move(svn_wc_context_t *wc_ctx,
@@ -7062,7 +7055,7 @@
* (typically #SVN_ERR_CANCELLED), return that error immediately.
*
* For each directory converted, @a notify_func will be called with
- * in @a notify_baton action #svn_wc_notify_upgrade_path and as path
+ * in @a notify_baton action #svn_wc_notify_upgraded_path and as path
* the path of the upgraded directory. @a notify_func may be @c NULL
* if this notification is not needed.
*
@@ -7848,7 +7841,8 @@
/**
- * The callback type used by svn_client_get_changelists().
+ * The callback type used by svn_wc_get_changelists() and
+ * svn_client_get_changelists().
*
* On each invocation, @a path is a newly discovered member of the
* changelist, and @a baton is a private function closure.
@@ -7861,7 +7855,10 @@
apr_pool_t *pool);
-/* @since New in 1.7.
+/**
+ * ### TODO: Doc string, please.
+ *
+ * @since New in 1.7.
*/
svn_error_t *
svn_wc_get_changelists(svn_wc_context_t *wc_ctx,
diff --git a/subversion/include/svn_xml.h b/subversion/include/svn_xml.h
index 66fac22..90969be 100644
--- a/subversion/include/svn_xml.h
+++ b/subversion/include/svn_xml.h
@@ -297,7 +297,10 @@
const char *encoding,
apr_pool_t *pool);
-/* Like svn_xml_make_header2, but does not emit encoding information. */
+/** Like svn_xml_make_header2(), but does not emit encoding information.
+ *
+ * @deprecated Provided for backward compatibility with the 1.6 API.
+ */
SVN_DEPRECATED
void
svn_xml_make_header(svn_stringbuf_t **str,
diff --git a/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c b/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c
index 8c49ab8..80cc042 100644
--- a/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c
+++ b/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c
@@ -28,6 +28,7 @@
/*** Includes. ***/
#include <apr_pools.h>
+#include <apr_strings.h>
#include "svn_auth.h"
#include "svn_config.h"
#include "svn_error.h"
@@ -38,7 +39,6 @@
#include "svn_private_config.h"
#include <glib.h>
-#include <dbus/dbus.h>
#include <gnome-keyring.h>
@@ -138,9 +138,9 @@
return;
}
-/* Returns the default keyring name. */
+/* Returns the default keyring name, allocated in RESULT_POOL. */
static char*
-get_default_keyring_name(apr_pool_t *pool)
+get_default_keyring_name(apr_pool_t *result_pool)
{
char *def = NULL;
struct gnome_keyring_baton key_info;
@@ -159,7 +159,7 @@
return NULL;
}
- def = strdup(key_info.keyring_name);
+ def = apr_pstrdup(result_pool, key_info.keyring_name);
callback_destroy_data_keyring(&key_info);
return def;
@@ -291,7 +291,6 @@
svn_boolean_t non_interactive,
apr_pool_t *pool)
{
- char *default_keyring = NULL;
GnomeKeyringResult result;
GList *items;
@@ -299,8 +298,6 @@
SVN_ERR(ensure_gnome_keyring_is_unlocked(non_interactive, parameters, pool));
- default_keyring = get_default_keyring_name(pool);
-
if (! apr_hash_get(parameters,
"gnome-keyring-opening-failed",
APR_HASH_KEY_STRING))
@@ -339,8 +336,6 @@
"");
}
- free(default_keyring);
-
return SVN_NO_ERROR;
}
@@ -356,7 +351,6 @@
svn_boolean_t non_interactive,
apr_pool_t *pool)
{
- char *default_keyring = NULL;
GnomeKeyringResult result;
guint32 item_id;
@@ -364,8 +358,6 @@
SVN_ERR(ensure_gnome_keyring_is_unlocked(non_interactive, parameters, pool));
- default_keyring = get_default_keyring_name(pool);
-
if (! apr_hash_get(parameters,
"gnome-keyring-opening-failed",
APR_HASH_KEY_STRING))
@@ -388,8 +380,6 @@
"");
}
- free(default_keyring);
-
*done = (result == GNOME_KEYRING_RESULT_OK);
return SVN_NO_ERROR;
}
@@ -428,12 +418,14 @@
pool);
}
+#if GLIB_CHECK_VERSION(2,6,0)
static void
log_noop(const gchar *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer user_data)
{
/* do nothing */
}
+#endif
static void
init_gnome_keyring(void)
@@ -449,7 +441,9 @@
suppress stderr spam for not only libgnome-keyring, but for
anything else the app is linked to that uses glib logging and
doesn't specify a log_domain. */
+#if GLIB_CHECK_VERSION(2,6,0)
g_log_set_default_handler(log_noop, NULL);
+#endif
}
static const svn_auth_provider_t gnome_keyring_simple_provider = {
diff --git a/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c
index 67b0acc..e60345cd 100644
--- a/subversion/libsvn_client/add.c
+++ b/subversion/libsvn_client/add.c
@@ -56,27 +56,6 @@
/*** Code. ***/
-/* This structure is used as baton for enumerating the config entries
- in the auto-props section.
-*/
-typedef struct auto_props_baton_t
-{
- /* the file name for which properties are searched */
- const char *filename;
-
- /* when this flag is set the hash contains svn:executable */
- svn_boolean_t have_executable;
-
- /* when mimetype is not NULL is set the hash contains svn:mime-type */
- const char *mimetype;
-
- /* the hash table for storing the property name/value pairs */
- apr_hash_t *properties;
-
- /* a pool used for allocating memory */
- apr_pool_t *pool;
-} auto_props_baton_t;
-
/* Remove leading and trailing white space from a C string, in place. */
static void
trim_string(char **pstr)
@@ -155,114 +134,96 @@
*props = temp_props;
}
-/* For one auto-props config entry (NAME, VALUE), if the filename pattern
- NAME matches BATON->filename case insensitively then add the properties
- listed in VALUE into BATON->properties.
- BATON must point to an auto_props_baton_t.
-*/
-static svn_boolean_t
-auto_props_enumerator(const char *name,
- const char *value,
- void *baton,
- apr_pool_t *pool)
-{
- int i;
- auto_props_baton_t *autoprops = baton;
- apr_array_header_t *props;
+/* PROPVALS is a hash mapping char * property names to const char * property
+ values. PROPERTIES can be empty but not NULL.
- /* nothing to do here without a value */
- if (*value == 0)
- return TRUE;
+ If FILENAME doesn't match the filename pattern PATTERN case insensitively,
+ the do nothing. Otherwise for each 'name':'value' pair in PROPVALS, add
+ a new entry mappying 'name' to a svn_string_t * wrapping the 'value' in
+ PROPERTIES. The svn_string_t is allocated in the pool used to allocate
+ PROPERTIES, but the char *'s from PROPVALS are re-used in PROPERTIES.
+ If PROPVALS contains a 'svn:mime-type' mapping, then set *MIMETYPE to
+ the mapped value. Likewise if PROPVALS contains a mapping for
+ svn:executable, then set *HAVE_EXECUTABLE to TRUE.
+
+ Use SCRATCH_POOL for temporary allocations.
+*/
+static void
+get_auto_props_for_pattern(apr_hash_t *properties,
+ const char **mimetype,
+ svn_boolean_t *have_executable,
+ const char *filename,
+ const char *pattern,
+ apr_hash_t *propvals,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
/* check if filename matches and return if it doesn't */
- if (apr_fnmatch(name, autoprops->filename, APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH)
- return TRUE;
+ if (apr_fnmatch(pattern, filename,
+ APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH)
+ return;
- split_props(&props, value, autoprops->pool);
-
- for (i = 0; i < props->nelts; i++)
+ for (hi = apr_hash_first(scratch_pool, propvals);
+ hi != NULL;
+ hi = apr_hash_next(hi))
{
- size_t len;
- const char *this_value;
- char *property = APR_ARRAY_IDX(props, i, char *);
- char *equal_sign = strchr(property, '=');
+ const char *propname = svn__apr_hash_index_key(hi);
+ const char *propval = svn__apr_hash_index_val(hi);
+ svn_string_t *propval_str =
+ svn_string_create_empty(apr_hash_pool_get(properties));
- if (equal_sign)
- {
- *equal_sign = '\0';
- equal_sign++;
- trim_string(&equal_sign);
- unquote_string(&equal_sign);
- this_value = equal_sign;
- }
- else
- {
- this_value = "";
- }
- trim_string(&property);
- len = strlen(property);
+ propval_str->data = propval;
+ propval_str->len = strlen(propval);
- if (len > 0)
- {
- svn_string_t *propval = apr_palloc(autoprops->pool,
- sizeof(*propval));
- propval->data = this_value;
- propval->len = strlen(this_value);
-
- apr_hash_set(autoprops->properties, property, len, propval);
- if (strcmp(property, SVN_PROP_MIME_TYPE) == 0)
- autoprops->mimetype = this_value;
- else if (strcmp(property, SVN_PROP_EXECUTABLE) == 0)
- autoprops->have_executable = TRUE;
- }
+ apr_hash_set(properties, propname, APR_HASH_KEY_STRING,
+ propval_str);
+ if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
+ *mimetype = propval;
+ else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0)
+ *have_executable = TRUE;
}
- return TRUE;
}
svn_error_t *
-svn_client__get_auto_props(apr_hash_t **properties,
- const char **mimetype,
- const char *path,
- svn_magic__cookie_t *magic_cookie,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
+svn_client__get_paths_auto_props(apr_hash_t **properties,
+ const char **mimetype,
+ const char *path,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- svn_config_t *cfg;
- svn_boolean_t use_autoprops;
- auto_props_baton_t autoprops;
+ apr_hash_index_t *hi;
+ svn_boolean_t have_executable = FALSE;
- /* initialisation */
- autoprops.properties = apr_hash_make(pool);
- autoprops.filename = svn_dirent_basename(path, pool);
- autoprops.pool = pool;
- autoprops.mimetype = NULL;
- autoprops.have_executable = FALSE;
- *properties = autoprops.properties;
+ *properties = apr_hash_make(result_pool);
+ *mimetype = NULL;
- cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING) : NULL;
+ for (hi = apr_hash_first(scratch_pool, autoprops);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *pattern = svn__apr_hash_index_key(hi);
+ apr_hash_t *propvals = svn__apr_hash_index_val(hi);
- /* check that auto props is enabled */
- SVN_ERR(svn_config_get_bool(cfg, &use_autoprops,
- SVN_CONFIG_SECTION_MISCELLANY,
- SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE));
-
- /* search for auto props */
- if (use_autoprops)
- svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS,
- auto_props_enumerator, &autoprops, pool);
+ get_auto_props_for_pattern(*properties, mimetype, &have_executable,
+ svn_dirent_basename(path, scratch_pool),
+ pattern, propvals, scratch_pool);
+ }
/* if mimetype has not been set check the file */
- if (! autoprops.mimetype)
+ if (! *mimetype)
{
- SVN_ERR(svn_io_detect_mimetype2(&autoprops.mimetype, path,
- ctx->mimetypes_map, pool));
+ SVN_ERR(svn_io_detect_mimetype2(mimetype, path, ctx->mimetypes_map,
+ result_pool));
/* If we got no mime-type, or if it is "application/octet-stream",
* try to get the mime-type from libmagic. */
if (magic_cookie &&
- (!autoprops.mimetype ||
- strcmp(autoprops.mimetype, "application/octet-stream") == 0))
+ (!*mimetype ||
+ strcmp(*mimetype, "application/octet-stream") == 0))
{
const char *magic_mimetype;
@@ -275,29 +236,29 @@
* returns "text/plain" for them. */
SVN_ERR(svn_magic__detect_binary_mimetype(&magic_mimetype,
path, magic_cookie,
- pool, pool));
+ result_pool,
+ scratch_pool));
if (magic_mimetype)
- autoprops.mimetype = magic_mimetype;
+ *mimetype = magic_mimetype;
}
- if (autoprops.mimetype)
- apr_hash_set(autoprops.properties, SVN_PROP_MIME_TYPE,
+ if (*mimetype)
+ apr_hash_set(*properties, SVN_PROP_MIME_TYPE,
strlen(SVN_PROP_MIME_TYPE),
- svn_string_create(autoprops.mimetype, pool));
+ svn_string_create(*mimetype, result_pool));
}
/* if executable has not been set check the file */
- if (! autoprops.have_executable)
+ if (! have_executable)
{
svn_boolean_t executable = FALSE;
- SVN_ERR(svn_io_is_file_executable(&executable, path, pool));
+ SVN_ERR(svn_io_is_file_executable(&executable, path, scratch_pool));
if (executable)
- apr_hash_set(autoprops.properties, SVN_PROP_EXECUTABLE,
+ apr_hash_set(*properties, SVN_PROP_EXECUTABLE,
strlen(SVN_PROP_EXECUTABLE),
- svn_string_create_empty(pool));
+ svn_string_create_empty(result_pool));
}
- *mimetype = autoprops.mimetype;
return SVN_NO_ERROR;
}
@@ -305,6 +266,7 @@
static svn_error_t *
add_file(const char *local_abspath,
svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -317,21 +279,39 @@
/* Check to see if this is a special file. */
SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, pool));
- if (is_special)
- mimetype = NULL;
- else
- /* Get automatic properties */
- /* This may fail on write-only files:
- we open them to estimate file type.
- That's why we postpone the add until after this step. */
- SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, local_abspath,
- magic_cookie, ctx, pool));
-
/* Add the file */
SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, local_abspath,
NULL, NULL, pool));
if (is_special)
+ {
+ mimetype = NULL;
+ }
+ else
+ {
+ apr_hash_t *file_autoprops;
+
+ /* Get automatic properties */
+ /* Grab the inherited svn:inheritable-auto-props and config file
+ auto-props for this file if we haven't already got them
+ when iterating over the file's unversioned parents. */
+ if (autoprops == NULL)
+ SVN_ERR(svn_client__get_all_auto_props(
+ &file_autoprops, svn_dirent_dirname(local_abspath,pool),
+ ctx, pool, pool));
+ else
+ file_autoprops = autoprops;
+
+ /* This may fail on write-only files:
+ we open them to estimate file type.
+ That's why we postpone the add until after this step. */
+ SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
+ local_abspath, magic_cookie,
+ file_autoprops, ctx, pool,
+ pool));
+ }
+
+ if (is_special)
/* This must be a special file. */
SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_SPECIAL,
svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool),
@@ -387,8 +367,8 @@
}
/* Schedule directory DIR_ABSPATH, and some of the tree under it, for
- * addition. DEPTH is the depth at this
- * point in the descent (it may be changed for recursive calls).
+ * addition. DEPTH is the depth at this point in the descent (it may
+ * be changed for recursive calls).
*
* If DIR_ABSPATH (or any item below DIR_ABSPATH) is already scheduled for
* addition, add will fail and return an error unless FORCE is TRUE.
@@ -399,8 +379,27 @@
* Use MAGIC_COOKIE (which may be NULL) to detect the mime-type of files
* if necessary.
*
+ * If not NULL, *CONFIG_AUTOPROPS is a hash representing the config file and
+ * svn:inheritable-auto-props autoprops which apply to DIR_ABSPATH. It maps
+ * const char * file patterns to another hash which maps const char *
+ * property names to const char *property values. If *CONFIG_AUTOPROPS is
+ * NULL and DIR_ABSPATH is unversioned, then this function will populate
+ * *CONFIG_AUTOPROPS (allocated in RESULT_POOL) using DIR_ABSPATH's nearest
+ * versioned parent to determine the svn:inheritable-auto-props which DIR_ABSPATH
+ * will inherit once added.
+ *
+ * If IGNORES is not NULL, then it is an array of const char * ignore patterns
+ * that apply to any children of DIR_ABSPATH. If REFRESH_IGNORES is TRUE, then
+ * the passed in value of IGNORES (if any) is itself ignored and this function
+ * will gather all ignore patterns applicable to DIR_ABSPATH itself. Any
+ * recursive calls to this function get the refreshed ignore patterns. If
+ * IGNORES is NULL and REFRESH_IGNORES is FALSE, then all children of DIR_ABSPATH
+ * are unconditionally added.
+ *
* If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to allow
- * the user to cancel the operation
+ * the user to cancel the operation.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
*/
static svn_error_t *
add_dir_recursive(const char *dir_abspath,
@@ -408,14 +407,19 @@
svn_boolean_t force,
svn_boolean_t no_ignore,
svn_magic__cookie_t *magic_cookie,
+ apr_hash_t **config_autoprops,
+ svn_boolean_t refresh_ignores,
+ apr_array_header_t *ignores,
svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
apr_pool_t *iterpool;
- apr_array_header_t *ignores;
apr_hash_t *dirents;
apr_hash_index_t *hi;
+ svn_boolean_t entry_exists = FALSE;
+ svn_boolean_t found_unversioned_root = FALSE;
/* Check cancellation; note that this catches recursive calls too. */
if (ctx->cancel_func)
@@ -423,19 +427,42 @@
iterpool = svn_pool_create(scratch_pool);
+ if (refresh_ignores)
+ SVN_ERR(svn_client__get_all_ignores(&ignores, dir_abspath,
+ no_ignore, ctx, scratch_pool,
+ scratch_pool));
+
/* Add this directory to revision control. */
err = svn_wc_add_from_disk(ctx->wc_ctx, dir_abspath,
ctx->notify_func2, ctx->notify_baton2,
iterpool);
- if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
- svn_error_clear(err);
- else if (err)
- return svn_error_trace(err);
-
- if (!no_ignore)
+ if (err)
{
- SVN_ERR(svn_wc_get_ignores2(&ignores, ctx->wc_ctx, dir_abspath,
- ctx->config, scratch_pool, iterpool));
+ if (err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ {
+ svn_error_clear(err);
+ entry_exists = TRUE;
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* For the root of any unversioned subtree, get some or all of the
+ following:
+
+ 1) Explicit and inherited svn:inheritable-auto-props properties on
+ DIR_ABSPATH
+ 2) Explicit and inherited svn:inheritabled-ignores properties on
+ DIR_ABSPATH
+ 3) auto-props from the CTX->CONFIG hash */
+ if (!entry_exists && *config_autoprops == NULL)
+ {
+ SVN_ERR(svn_client__get_all_auto_props(config_autoprops, dir_abspath,
+ ctx, result_pool,
+ scratch_pool));
+ found_unversioned_root = TRUE;
}
SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, scratch_pool,
@@ -460,7 +487,8 @@
if (svn_wc_is_adm_dir(name, iterpool))
continue;
- if ((!no_ignore) && svn_wc_match_ignore_list(name, ignores, iterpool))
+ if (ignores
+ && svn_wc_match_ignore_list(name, ignores, iterpool))
continue;
/* Construct the full path of the entry. */
@@ -473,14 +501,23 @@
if (depth == svn_depth_immediates)
depth_below_here = svn_depth_empty;
+ /* When DIR_ABSPATH is the root of an unversioned subtree then
+ it and all of its children have the same set of ignores. So
+ save any recursive calls the extra work of finding the same
+ set of ignores. */
+ if (refresh_ignores && !entry_exists)
+ refresh_ignores = FALSE;
+
SVN_ERR(add_dir_recursive(abspath, depth_below_here,
force, no_ignore, magic_cookie,
- ctx, iterpool));
+ config_autoprops, refresh_ignores,
+ ignores, ctx, iterpool, iterpool));
}
else if ((dirent->kind == svn_node_file || dirent->special)
&& depth >= svn_depth_files)
{
- err = add_file(abspath, magic_cookie, ctx, iterpool);
+ err = add_file(abspath, magic_cookie, *config_autoprops, ctx,
+ iterpool);
if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
svn_error_clear(err);
else
@@ -491,121 +528,104 @@
/* Destroy the per-iteration pool. */
svn_pool_destroy(iterpool);
+ /* Reset CONFIG_AUTOPROPS if we just finished processing the root
+ of an unversioned subtree. */
+ if (found_unversioned_root)
+ *config_autoprops = NULL;
+
return SVN_NO_ERROR;
}
-
-/* The main logic of the public svn_client_add4.
- *
- * EXISTING_PARENT_ABSPATH is the absolute path to the first existing
- * parent directory of local_abspath. If not NULL, all missing parents
- * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */
-static svn_error_t *
-add(const char *local_abspath,
- svn_depth_t depth,
- svn_boolean_t force,
- svn_boolean_t no_ignore,
- const char *existing_parent_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
+/* This structure is used as baton for collecting the config entries
+ in the auto-props section and any inherited svn:inheritable-auto-props
+ properties.
+*/
+typedef struct collect_auto_props_baton_t
{
- svn_node_kind_t kind;
- svn_error_t *err;
- svn_magic__cookie_t *magic_cookie;
+ /* the hash table for storing the property name/value pairs */
+ apr_hash_t *autoprops;
- svn_magic__init(&magic_cookie, scratch_pool);
+ /* a pool used for allocating memory */
+ apr_pool_t *result_pool;
+} collect_auto_props_baton_t;
- if (existing_parent_abspath)
+/* Implements svn_config_enumerator2_t callback.
+
+ For one auto-props config entry (NAME, VALUE), stash a copy of
+ NAME and VALUE, allocated in BATON->POOL, in BATON->AUTOPROP.
+ BATON must point to an collect_auto_props_baton_t.
+*/
+static svn_boolean_t
+all_auto_props_collector(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ collect_auto_props_baton_t *autoprops_baton = baton;
+ apr_array_header_t *autoprops;
+ int i;
+
+ /* nothing to do here without a value */
+ if (*value == 0)
+ return TRUE;
+
+ split_props(&autoprops, value, pool);
+
+ for (i = 0; i < autoprops->nelts; i ++)
{
- const char *parent_abspath;
- const char *child_relpath;
- apr_array_header_t *components;
- int i;
- apr_pool_t *iterpool;
+ size_t len;
+ const char *this_value;
+ char *property = APR_ARRAY_IDX(autoprops, i, char *);
+ char *equal_sign = strchr(property, '=');
- parent_abspath = existing_parent_abspath;
- child_relpath = svn_dirent_is_child(existing_parent_abspath,
- local_abspath, NULL);
- components = svn_path_decompose(child_relpath, scratch_pool);
- iterpool = svn_pool_create(scratch_pool);
- for (i = 0; i < components->nelts - 1; i++)
+ if (equal_sign)
{
- const char *component;
- svn_node_kind_t disk_kind;
-
- svn_pool_clear(iterpool);
-
- if (ctx->cancel_func)
- SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
-
- component = APR_ARRAY_IDX(components, i, const char *);
- parent_abspath = svn_dirent_join(parent_abspath, component,
- scratch_pool);
- SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool));
- if (disk_kind != svn_node_none && disk_kind != svn_node_dir)
- return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL,
- _("'%s' prevents creating parent of '%s'"),
- parent_abspath, local_abspath);
-
- SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool));
- SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, parent_abspath,
- ctx->notify_func2, ctx->notify_baton2,
- scratch_pool));
+ *equal_sign = '\0';
+ equal_sign++;
+ trim_string(&equal_sign);
+ unquote_string(&equal_sign);
+ this_value = equal_sign;
}
- svn_pool_destroy(iterpool);
- }
+ else
+ {
+ this_value = "";
+ }
+ trim_string(&property);
+ len = strlen(property);
- SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
- if (kind == svn_node_dir)
- {
- /* We use add_dir_recursive for all directory targets
- and pass depth along no matter what it is, so that the
- target's depth will be set correctly. */
- err = add_dir_recursive(local_abspath, depth, force, no_ignore,
- magic_cookie, ctx, scratch_pool);
- }
- else if (kind == svn_node_file)
- err = add_file(local_abspath, magic_cookie, ctx, scratch_pool);
- else if (kind == svn_node_none)
- {
- svn_boolean_t tree_conflicted;
+ if (len > 0)
+ {
+ apr_hash_t *pattern_hash = apr_hash_get(autoprops_baton->autoprops,
+ name, APR_HASH_KEY_STRING);
+ svn_string_t *propval;
- /* Provide a meaningful error message if the node does not exist
- * on disk but is a tree conflict victim. */
- err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
- ctx->wc_ctx, local_abspath,
- scratch_pool);
- if (err)
- svn_error_clear(err);
- else if (tree_conflicted)
- return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
- _("'%s' is an existing item in conflict; "
- "please mark the conflict as resolved "
- "before adding a new item here"),
- svn_dirent_local_style(local_abspath,
- scratch_pool));
+ /* Force reserved boolean property values to '*'. */
+ if (svn_prop_is_boolean(property))
+ {
+ /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
+ propval = svn_string_create("*", autoprops_baton->result_pool);
+ }
+ else
+ {
+ propval = svn_string_create(this_value,
+ autoprops_baton->result_pool);
+ }
- return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
- _("'%s' not found"),
- svn_dirent_local_style(local_abspath,
- scratch_pool));
+ if (!pattern_hash)
+ {
+ pattern_hash = apr_hash_make(autoprops_baton->result_pool);
+ apr_hash_set(autoprops_baton->autoprops,
+ apr_pstrdup(autoprops_baton->result_pool, name),
+ APR_HASH_KEY_STRING, pattern_hash);
+ }
+ apr_hash_set(pattern_hash,
+ apr_pstrdup(autoprops_baton->result_pool, property),
+ APR_HASH_KEY_STRING, propval->data);
+ }
}
- else
- return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("Unsupported node kind for path '%s'"),
- svn_dirent_local_style(local_abspath,
- scratch_pool));
-
- /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */
- if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
- {
- svn_error_clear(err);
- err = SVN_NO_ERROR;
- }
- return svn_error_trace(err);
+ return TRUE;
}
-
/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned
* directory. If found, return its path in *EXISTING_PARENT_ABSPATH.
* Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */
@@ -657,6 +677,406 @@
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_client__get_all_auto_props(apr_hash_t **autoprops,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *inherited_config_auto_props;
+ apr_hash_t *props;
+ svn_opt_revision_t rev;
+ svn_string_t *config_auto_prop;
+ svn_boolean_t use_autoprops;
+ collect_auto_props_baton_t autoprops_baton;
+ svn_error_t *err = NULL;
+ svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
+ svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config,
+ SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING) : NULL;
+ *autoprops = apr_hash_make(result_pool);
+ autoprops_baton.result_pool = result_pool;
+ autoprops_baton.autoprops = *autoprops;
+
+ /* Are "traditional" auto-props enabled? If so grab them from the
+ config. This is our starting set auto-props, which may be overriden
+ by svn:inheritable-auto-props. */
+ SVN_ERR(svn_config_get_bool(cfg, &use_autoprops,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE));
+ if (use_autoprops)
+ svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS,
+ all_auto_props_collector, &autoprops_baton,
+ scratch_pool);
+
+ /* Convert the config file setting (if any) into a hash mapping file
+ patterns to as hash of prop-->val mappings. */
+ if (svn_path_is_url(path_or_url))
+ rev.kind = svn_opt_revision_head;
+ else
+ rev.kind = svn_opt_revision_working;
+
+ /* If PATH_OR_URL is a WC path, then it might be unversioned, in which case
+ we find it's nearest versioned parent. */
+ while (err == NULL)
+ {
+ err = svn_client_propget5(&props, &inherited_config_auto_props,
+ SVN_PROP_INHERITABLE_AUTO_PROPS, path_or_url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (target_is_url || err->apr_err != SVN_ERR_UNVERSIONED_RESOURCE)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(find_existing_parent(&path_or_url, ctx, path_or_url,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ /* Stash any explicit PROPS for PARENT_PATH into the inherited props array,
+ since these are actually inherited props for LOCAL_ABSPATH. */
+ config_auto_prop = apr_hash_get(props, path_or_url, APR_HASH_KEY_STRING);
+
+ if (config_auto_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = path_or_url;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ apr_hash_set(new_iprop->prop_hash,
+ SVN_PROP_INHERITABLE_AUTO_PROPS,
+ APR_HASH_KEY_STRING,
+ config_auto_prop);
+ APR_ARRAY_PUSH(inherited_config_auto_props,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ for (i = 0; i < inherited_config_auto_props->nelts; i++)
+ {
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_config_auto_props, i, svn_prop_inherited_item_t *);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+ const char *ch = propval->data;
+ svn_stringbuf_t *config_auto_prop_pattern;
+ svn_stringbuf_t *config_auto_prop_val;
+
+ svn_pool_clear(iterpool);
+
+ config_auto_prop_pattern = svn_stringbuf_create_empty(iterpool);
+ config_auto_prop_val = svn_stringbuf_create_empty(iterpool);
+
+ /* Parse svn:inheritable-auto-props value. */
+ while (*ch != '\0')
+ {
+ svn_stringbuf_setempty(config_auto_prop_pattern);
+ svn_stringbuf_setempty(config_auto_prop_val);
+
+ /* Parse the file pattern. */
+ while (*ch != '\0' && *ch != '=' && *ch != '\n')
+ {
+ svn_stringbuf_appendbyte(config_auto_prop_pattern, *ch);
+ ch++;
+ }
+
+ svn_stringbuf_strip_whitespace(config_auto_prop_pattern);
+
+ /* Parse the auto-prop group. */
+ while (*ch != '\0' && *ch != '\n')
+ {
+ svn_stringbuf_appendbyte(config_auto_prop_val, *ch);
+ ch++;
+ }
+
+ /* Strip leading '=' and whitespace from auto-prop group. */
+ if (config_auto_prop_val->data[0] == '=')
+ svn_stringbuf_remove(config_auto_prop_val, 0, 1);
+ svn_stringbuf_strip_whitespace(config_auto_prop_val);
+
+ all_auto_props_collector(config_auto_prop_pattern->data,
+ config_auto_prop_val->data,
+ &autoprops_baton,
+ scratch_pool);
+
+ /* Skip to next line if any. */
+ while (*ch != '\0' && *ch != '\n')
+ ch++;
+ if (*ch == '\n')
+ ch++;
+ }
+ svn_pool_destroy(iterpool);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_opt_revision_t rev;
+ apr_hash_t *explicit_ignores;
+ apr_array_header_t *inherited_ignores;
+ svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
+ svn_string_t *explicit_prop;
+ int i;
+
+ if (target_is_url)
+ rev.kind = svn_opt_revision_head;
+ else
+ rev.kind = svn_opt_revision_working;
+
+ SVN_ERR(svn_client_propget5(&explicit_ignores, &inherited_ignores,
+ SVN_PROP_INHERITABLE_IGNORES, path_or_url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool));
+
+ explicit_prop = apr_hash_get(explicit_ignores, path_or_url,
+ APR_HASH_KEY_STRING);
+
+ if (explicit_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = path_or_url;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ apr_hash_set(new_iprop->prop_hash,
+ SVN_PROP_INHERITABLE_IGNORES,
+ APR_HASH_KEY_STRING,
+ explicit_prop);
+ APR_ARRAY_PUSH(inherited_ignores,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ *ignores = apr_array_make(result_pool, 16, sizeof(const char *));
+
+ for (i = 0; i < inherited_ignores->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_ignores, i, svn_prop_inherited_item_t *);
+ svn_string_t *ignore_val = apr_hash_get(elt->prop_hash,
+ SVN_PROP_INHERITABLE_IGNORES,
+ APR_HASH_KEY_STRING);
+ if (ignore_val)
+ svn_cstring_split_append(*ignores, ignore_val->data, "\n\r\t\v ",
+ FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_client__get_all_ignores(apr_array_header_t **ignores,
+ const char *local_abspath,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *explicit_ignores;
+ apr_array_header_t *inherited_ignores;
+ svn_error_t *err = NULL;
+ svn_string_t *explicit_prop;
+ int i;
+ svn_opt_revision_t rev;
+
+ rev.kind = svn_opt_revision_working;
+
+ /* LOCAL_ABSPATH might be unversioned, in which case we find its
+ nearest versioned parent. */
+ while (err == NULL)
+ {
+ err = svn_client_propget5(&explicit_ignores, &inherited_ignores,
+ SVN_PROP_INHERITABLE_IGNORES, local_abspath,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_UNVERSIONED_RESOURCE)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(find_existing_parent(&local_abspath, ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ explicit_prop = apr_hash_get(explicit_ignores, local_abspath,
+ APR_HASH_KEY_STRING);
+
+ if (explicit_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = local_abspath;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ apr_hash_set(new_iprop->prop_hash,
+ SVN_PROP_INHERITABLE_IGNORES,
+ APR_HASH_KEY_STRING,
+ explicit_prop);
+ APR_ARRAY_PUSH(inherited_ignores,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ /* Now that we are sure we have an existing parent, get the config ignore
+ and the local ignore patterns... */
+ if (!no_ignore)
+ SVN_ERR(svn_wc_get_ignores2(ignores, ctx->wc_ctx, local_abspath,
+ ctx->config, result_pool, scratch_pool));
+ else
+ *ignores = apr_array_make(result_pool, 16, sizeof(const char *));
+
+ /* ...and add the inherited ignores to it. */
+ for (i = 0; i < inherited_ignores->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_ignores, i, svn_prop_inherited_item_t *);
+ svn_string_t *ignore_val = apr_hash_get(elt->prop_hash,
+ SVN_PROP_INHERITABLE_IGNORES,
+ APR_HASH_KEY_STRING);
+ if (ignore_val)
+ svn_cstring_split_append(*ignores, ignore_val->data, "\n\r\t\v ",
+ FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The main logic of the public svn_client_add4.
+ *
+ * EXISTING_PARENT_ABSPATH is the absolute path to the first existing
+ * parent directory of local_abspath. If not NULL, all missing parents
+ * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */
+static svn_error_t *
+add(const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ const char *existing_parent_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ svn_magic__cookie_t *magic_cookie;
+ apr_hash_t *config_autoprops = NULL;
+ apr_array_header_t *ignores = NULL;
+
+ svn_magic__init(&magic_cookie, scratch_pool);
+
+ if (existing_parent_abspath)
+ {
+ const char *parent_abspath;
+ const char *child_relpath;
+ apr_array_header_t *components;
+ int i;
+ apr_pool_t *iterpool;
+
+ parent_abspath = existing_parent_abspath;
+ child_relpath = svn_dirent_is_child(existing_parent_abspath,
+ local_abspath, NULL);
+ components = svn_path_decompose(child_relpath, scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < components->nelts - 1; i++)
+ {
+ const char *component;
+ svn_node_kind_t disk_kind;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ parent_abspath = svn_dirent_join(parent_abspath, component,
+ scratch_pool);
+ SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool));
+ if (disk_kind != svn_node_none && disk_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL,
+ _("'%s' prevents creating parent of '%s'"),
+ parent_abspath, local_abspath);
+
+ SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, parent_abspath,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ if (kind == svn_node_dir)
+ {
+ /* We use add_dir_recursive for all directory targets
+ and pass depth along no matter what it is, so that the
+ target's depth will be set correctly. */
+ err = add_dir_recursive(local_abspath, depth, force, no_ignore,
+ magic_cookie, &config_autoprops, TRUE, ignores,
+ ctx, scratch_pool, scratch_pool);
+ }
+ else if (kind == svn_node_file)
+ err = add_file(local_abspath, magic_cookie, config_autoprops, ctx,
+ scratch_pool);
+ else if (kind == svn_node_none)
+ {
+ svn_boolean_t tree_conflicted;
+
+ /* Provide a meaningful error message if the node does not exist
+ * on disk but is a tree conflict victim. */
+ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool);
+ if (err)
+ svn_error_clear(err);
+ else if (tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("'%s' is an existing item in conflict; "
+ "please mark the conflict as resolved "
+ "before adding a new item here"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported node kind for path '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */
+ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+}
+
svn_error_t *
@@ -671,6 +1091,8 @@
const char *parent_abspath;
const char *local_abspath;
const char *existing_parent_abspath;
+ svn_boolean_t is_wc_root;
+ svn_error_t *err;
if (svn_path_is_url(path))
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
@@ -678,20 +1100,55 @@
SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
- /* ### this is a hack.
- ### before we switched to absolute paths, if a user tried to do
- ### 'svn add .', PATH would be "" and PARENT_PATH would also be "",
- ### thus emulating the behavior below. Now that we are using
- ### absolute paths, svn_dirent_dirname() doesn't behave the same way
- ### w.r.t. '.', so we need to include the following hack. This
- ### behavior is tested in schedule_tests-11. */
- if (path[0] == 0)
- parent_abspath = local_abspath;
+ /* See if we're being asked to add a wc-root. That's typically not
+ okay, unless we're in "force" mode. svn_wc__strictly_is_wc_root()
+ will return TRUE even if LOCAL_ABSPATH is a *symlink* to a working
+ copy root, which is a scenario we want to treat differently. */
+ err = svn_wc__strictly_is_wc_root(&is_wc_root, ctx->wc_ctx,
+ local_abspath, pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ return svn_error_trace(err);
+ }
+
+ svn_error_clear(err);
+ err = NULL; /* SVN_NO_ERROR */
+ is_wc_root = FALSE;
+ }
+ if (is_wc_root)
+ {
+#ifdef HAVE_SYMLINK
+ svn_node_kind_t disk_kind;
+ svn_boolean_t is_special;
+
+ SVN_ERR(svn_io_check_special_path(local_abspath, &disk_kind, &is_special,
+ pool));
+
+ /* A symlink can be an unversioned target and a wcroot. Lets try to add
+ the symlink, which can't be a wcroot. */
+ if (is_special)
+ is_wc_root = FALSE;
+ else
+#endif
+ {
+ if (! force)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+ }
+
+ if (is_wc_root)
+ parent_abspath = local_abspath; /* We will only add children */
else
parent_abspath = svn_dirent_dirname(local_abspath, pool);
existing_parent_abspath = NULL;
- if (add_parents)
+ if (add_parents && !is_wc_root)
{
apr_pool_t *subpool;
const char *existing_parent_abspath2;
@@ -707,8 +1164,8 @@
SVN_WC__CALL_WITH_WRITE_LOCK(
add(local_abspath, depth, force, no_ignore, existing_parent_abspath,
ctx, pool),
- ctx->wc_ctx,
- existing_parent_abspath ? existing_parent_abspath : parent_abspath,
+ ctx->wc_ctx, (existing_parent_abspath ? existing_parent_abspath
+ : parent_abspath),
FALSE /* lock_anchor */, pool);
return SVN_NO_ERROR;
}
@@ -908,9 +1365,8 @@
pool));
/* Call the path-based editor driver. */
- err = svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM,
- targets, path_driver_cb_func,
- (void *)editor, pool);
+ err = svn_delta_path_driver2(editor, edit_baton, targets, TRUE,
+ path_driver_cb_func, (void *)editor, pool);
if (err)
{
/* At least try to abort the edit (and fs txn) before throwing err. */
diff --git a/subversion/libsvn_client/cat.c b/subversion/libsvn_client/cat.c
index 64c88d1..db1edc3 100644
--- a/subversion/libsvn_client/cat.c
+++ b/subversion/libsvn_client/cat.c
@@ -62,7 +62,6 @@
svn_string_t *eol_style, *keywords, *special;
const char *eol = NULL;
svn_boolean_t local_mod = FALSE;
- apr_time_t tm;
svn_stream_t *input;
svn_node_kind_t kind;
@@ -118,27 +117,15 @@
if (eol_style)
svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
- if (local_mod && (! special))
- {
- /* Use the modified time from the working copy if
- the file */
- SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
- }
- else
- {
- SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx,
- local_abspath, scratch_pool,
- scratch_pool));
- }
-
if (keywords)
{
svn_revnum_t changed_rev;
const char *rev_str;
const char *author;
const char *url;
+ apr_time_t tm;
- SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx,
+ SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, &tm, &author, wc_ctx,
local_abspath, scratch_pool,
scratch_pool));
SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, local_abspath, scratch_pool,
@@ -152,6 +139,13 @@
current user's username */
rev_str = apr_psprintf(scratch_pool, "%ldM", changed_rev);
author = _("(local)");
+
+ if (! special)
+ {
+ /* Use the modified time from the working copy for files */
+ SVN_ERR(svn_io_file_affected_time(&tm, local_abspath,
+ scratch_pool));
+ }
}
else
{
diff --git a/subversion/libsvn_client/cleanup.c b/subversion/libsvn_client/cleanup.c
index 13767c3..542ec6f 100644
--- a/subversion/libsvn_client/cleanup.c
+++ b/subversion/libsvn_client/cleanup.c
@@ -144,8 +144,9 @@
upgrade to avoid that errors in the externals causes the wc upgrade to
fail. Thanks to caching the performance penalty of walking the wc a
second time shouldn't be too severe */
- SVN_ERR(svn_client_propget4(&externals, SVN_PROP_EXTERNALS, local_abspath,
- &rev, &rev, NULL, svn_depth_infinity, NULL, ctx,
+ SVN_ERR(svn_client_propget5(&externals, NULL, SVN_PROP_EXTERNALS,
+ local_abspath, &rev, &rev, NULL,
+ svn_depth_infinity, NULL, ctx,
scratch_pool, scratch_pool));
iterpool = svn_pool_create(scratch_pool);
diff --git a/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h
index 4b64839..95164d9 100644
--- a/subversion/libsvn_client/client.h
+++ b/subversion/libsvn_client/client.h
@@ -217,40 +217,6 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
-/* Given PATH_OR_URL, which contains either a working copy path or an
- absolute URL, a peg revision PEG_REVISION, and a desired revision
- REVISION, create an RA connection to that object as it exists in
- that revision, following copy history if necessary. If REVISION is
- younger than PEG_REVISION, then PATH_OR_URL will be checked to see
- that it is the same node in both PEG_REVISION and REVISION. If it
- is not, then @c SVN_ERR_CLIENT_UNRELATED_RESOURCES is returned.
-
- BASE_DIR_ABSPATH is the working copy path the ra_session corresponds to,
- and should only be used if PATH_OR_URL is a url
- ### else NULL? what's it for?
-
- If PEG_REVISION->kind is 'unspecified', the peg revision is 'head'
- for a URL or 'working' for a WC path. If REVISION->kind is
- 'unspecified', the operative revision is the peg revision.
-
- Store the resulting ra_session in *RA_SESSION_P. Store the final
- resolved location of the object in *RESOLVED_LOC_P. RESOLVED_LOC_P
- may be NULL if not wanted.
-
- Use authentication baton cached in CTX to authenticate against the
- repository.
-
- Use POOL for all allocations. */
-svn_error_t *
-svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
- svn_client__pathrev_t **resolved_loc_p,
- const char *path_or_url,
- const char *base_dir_abspath,
- const svn_opt_revision_t *peg_revision,
- const svn_opt_revision_t *revision,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool);
-
/* Ensure that RA_SESSION's session URL matches SESSION_URL,
reparenting that session if necessary.
Store the previous session URL in *OLD_SESSION_URL (so that if the
@@ -360,21 +326,88 @@
/*** Add/delete ***/
-/* Read automatic properties matching PATH from CTX->config.
+/* Read automatic properties matching PATH from AUTOPROPS. AUTOPROPS
+ is is a hash as per svn_client__get_all_auto_props.
+
Set *PROPERTIES to a hash containing propname/value pairs
- (const char * keys mapping to svn_string_t * values), or if
- auto-props are disabled, set *PROPERTIES to NULL.
+ (const char * keys mapping to svn_string_t * values). *PROPERTIES
+ may be an empty hash, but will not be NULL.
+
Set *MIMETYPE to the mimetype, if any, or to NULL.
+
If MAGIC_COOKIE is not NULL and no mime-type can be determined
via CTX->config try to detect the mime-type with libmagic.
- Allocate the hash table, keys, values, and mimetype in POOL. */
-svn_error_t *svn_client__get_auto_props(apr_hash_t **properties,
- const char **mimetype,
- const char *path,
- svn_magic__cookie_t *magic_cookie,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool);
+ Allocate the *PROPERTIES and its contents as well as *MIMETYPE, in
+ RESULT_POOL. Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *svn_client__get_paths_auto_props(
+ apr_hash_t **properties,
+ const char **mimetype,
+ const char *path,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Gather all auto-props from CTX->config (or none if auto-props are
+ disabled) and all svn:inheritable-auto-props explicitly set on or inherited
+ by PATH_OR_URL.
+
+ If PATH_OR_URL is an unversioned WC path then gather the
+ svn:inheritable-auto-props inherited by PATH_OR_URL's nearest versioned
+ parent.
+
+ If PATH_OR_URL is a URL ask for the properties @HEAD, if it is a WC
+ path as sfor the working properties.
+
+ Store both types of auto-props in *AUTOPROPS, a hash mapping const
+ char * file patterns to another hash which maps const char * property
+ names to const char *property values.
+
+ If a given property name exists for the same pattern in both the config
+ file and in an a svn:inheritable-auto-props property, the latter overrides the
+ former. If a given property name exists for the same pattern in two
+ different inherited svn:inheritable-auto-props, then the closer path-wise
+ property overrides the more distant. svn:inheritable-auto-props explicitly set
+ on PATH_OR_URL have the highest precedence and override inherited props
+ and config file settings.
+
+ Allocate *AUTOPROPS in RESULT_POOL. Use SCRATCH_POOL for temporary
+ allocations. */
+svn_error_t *svn_client__get_all_auto_props(apr_hash_t **autoprops,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Get a combined list of ignore patterns from CTX->CONFIG, local ignore
+ patterns on LOCAL_ABSPATH (per the svn:ignore property), and from any
+ svn:inheritable-ignores properties set on, or inherited by, LOCAL_ABSPATH.
+ If LOCAL_ABSPATH is unversioned but is located within a valid working copy,
+ then find its nearest versioned parent path, if any, and return the ignore
+ patterns for that parent. Return an SVN_ERR_WC_NOT_WORKING_COPY error if
+ LOCAL_ABSPATH is neither a versioned working copy path nor an unversioned
+ path within a working copy. Store the collected patterns as const char *
+ elements in the array *IGNORES. Allocate *IGNORES and its contents in
+ RESULT_POOL. Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *svn_client__get_all_ignores(apr_array_header_t **ignores,
+ const char *local_abspath,
+ svn_boolean_t no_ignore,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Get a list of ignore patterns defined by the svn:inheritable-ignores
+ properties set on, or inherited by, PATH_OR_URL. Store the collected
+ patterns as const char * elements in the array *IGNORES. Allocate
+ *IGNORES and its contents in RESULT_POOL. Use SCRATCH_POOL for
+ temporary allocations. */
+svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/* The main logic for client deletion from a working copy. Deletes PATH
from CTX->WC_CTX. If PATH (or any item below a directory PATH) is
@@ -562,6 +595,34 @@
/* ---------------------------------------------------------------- */
+/*** Inheritable Properties ***/
+
+/* Fetch the inherited properties for the base of LOCAL_ABSPATH as well
+ as any WC roots under LOCAL_ABSPATH (as limited by DEPTH) using
+ RA_SESSION. Store the results in *WCROOT_IPROPS, a hash mapping
+ const char * absolute working copy paths to depth-first ordered arrays
+ of svn_prop_inherited_item_t * structures.
+
+ If LOCAL_ABSPATH has no base then do nothing.
+
+ RA_SESSION should be an open RA session pointing at the URL of PATH,
+ or NULL, in which case this function will open its own temporary session.
+
+ Allocate *WCROOT_IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary
+ allocations.
+*/
+svn_error_t *
+svn_client__get_inheritable_props(apr_hash_t **wcroot_iprops,
+ const char *local_abspath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ---------------------------------------------------------------- */
+
/*** Editor for repository diff ***/
/* Create an editor for a pure repository comparison, i.e. comparing one
@@ -1033,21 +1094,6 @@
apr_hash_t *relpath_map,
apr_pool_t *result_pool);
-/* Return true if KIND is a revision kind that is dependent on the working
- * copy. Otherwise, return false. */
-#define SVN_CLIENT__REVKIND_NEEDS_WC(kind) \
- ((kind) == svn_opt_revision_base || \
- (kind) == svn_opt_revision_previous || \
- (kind) == svn_opt_revision_working || \
- (kind) == svn_opt_revision_committed) \
-
-/* Return true if KIND is a revision kind that the WC can supply without
- * contacting the repository. Otherwise, return false. */
-#define SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(kind) \
- ((kind) == svn_opt_revision_base || \
- (kind) == svn_opt_revision_working || \
- (kind) == svn_opt_revision_committed)
-
/* Return REVISION unless its kind is 'unspecified' in which case return
* a pointer to a statically allocated revision structure of kind 'head'
* if PATH_OR_URL is a URL or 'base' if it is a WC path. */
diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c
index e9004bd..924b46b 100644
--- a/subversion/libsvn_client/commit.c
+++ b/subversion/libsvn_client/commit.c
@@ -75,6 +75,13 @@
/* A magic cookie for mime-type detection. */
svn_magic__cookie_t *magic_cookie;
+
+ /* Collection of all possible configuration file dictated auto-props and
+ svn:inheritable-auto-props. A hash mapping const char * file patterns to a
+ second hash which maps const char * property names to const char *
+ property values. Properties which don't have a value, e.g. svn:executable,
+ simply map the property name to an empty string. */
+ apr_hash_t *autoprops;
} import_ctx_t;
@@ -219,9 +226,11 @@
if (! dirent->special)
{
/* add automatic properties */
- SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, local_abspath,
- import_ctx->magic_cookie,
- ctx, pool));
+ SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
+ local_abspath,
+ import_ctx->magic_cookie,
+ import_ctx->autoprops,
+ ctx, pool, pool));
}
else
properties = apr_hash_make(pool);
@@ -278,7 +287,8 @@
/* Return in CHILDREN a mapping of basenames to dirents for the importable
* children of DIR_ABSPATH. EXCLUDES is a hash of absolute paths to filter
- * out. IGNORES, if non-NULL, is a list of basenames to filter out.
+ * out. IGNORES and MANDATORY_IGNORES, if non-NULL, are lists of basename
+ * patterns to filter out.
* FILTER_CALLBACK and FILTER_BATON will be called for each absolute path,
* allowing users to further filter the list of returned entries.
*
@@ -288,6 +298,7 @@
const char *dir_abspath,
apr_hash_t *excludes,
apr_array_header_t *ignores,
+ apr_array_header_t *mandatory_ignores,
svn_client_import_filter_func_t filter_callback,
void *filter_baton,
svn_client_ctx_t *ctx,
@@ -348,6 +359,12 @@
continue;
}
+ if (svn_wc_match_ignore_list(base_name, mandatory_ignores, iterpool))
+ {
+ apr_hash_set(dirents, base_name, APR_HASH_KEY_STRING, NULL);
+ continue;
+ }
+
if (filter_callback)
{
svn_boolean_t filter = FALSE;
@@ -375,6 +392,7 @@
const char *edit_path,
svn_depth_t depth,
apr_hash_t *excludes,
+ apr_array_header_t *mandatory_ignores,
svn_boolean_t no_ignore,
svn_boolean_t ignore_unknown_node_types,
svn_client_import_filter_func_t filter_callback,
@@ -394,6 +412,7 @@
void *dir_baton,
svn_depth_t depth,
apr_hash_t *excludes,
+ apr_array_header_t *mandatory_ignores,
svn_boolean_t no_ignore,
svn_boolean_t ignore_unknown_node_types,
svn_client_import_filter_func_t filter_callback,
@@ -436,10 +455,9 @@
SVN_ERR(import_dir(editor, dir_baton, this_abspath,
this_edit_path, depth_below_here, excludes,
- no_ignore, ignore_unknown_node_types,
- filter_callback, filter_baton,
- import_ctx, ctx,
- iterpool));
+ mandatory_ignores, no_ignore,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, import_ctx, ctx, iterpool));
}
else if (dirent->kind == svn_node_file && depth >= svn_depth_files)
{
@@ -490,6 +508,9 @@
* EXCLUDES is a hash whose keys are absolute paths to exclude from
* the import (values are unused).
*
+ * MANDATORY_IGNORES is an array of const char * ignore patterns. Any child
+ * of LOCAL_ABSPATH which matches one or more of the patterns is not imported.
+ *
* If NO_IGNORE is FALSE, don't import files or directories that match
* ignore patterns.
*
@@ -507,6 +528,7 @@
const char *edit_path,
svn_depth_t depth,
apr_hash_t *excludes,
+ apr_array_header_t *mandatory_ignores,
svn_boolean_t no_ignore,
svn_boolean_t ignore_unknown_node_types,
svn_client_import_filter_func_t filter_callback,
@@ -525,8 +547,8 @@
SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes, ignores,
- filter_callback, filter_baton, ctx,
- pool, pool));
+ mandatory_ignores, filter_callback,
+ filter_baton, ctx, pool, pool));
/* Import this directory, but not yet its children. */
{
@@ -556,8 +578,8 @@
/* Now import the children recursively. */
SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
- this_dir_baton, depth, excludes, no_ignore,
- ignore_unknown_node_types,
+ this_dir_baton, depth, excludes, mandatory_ignores,
+ no_ignore, ignore_unknown_node_types,
filter_callback, filter_baton,
import_ctx, ctx, pool));
@@ -590,6 +612,19 @@
* EXCLUDES is a hash whose keys are absolute paths to exclude from
* the import (values are unused).
*
+ * AUTOPROPS is hash of all config file autoprops and
+ * svn:inheritable-auto-props inherited by the import target, see the
+ * IMPORT_CTX member of the same name.
+ *
+ * LOCAL_IGNORES is an array of const char * ignore patterns which
+ * correspond to the svn:ignore property (if any) set on the root of the
+ * repository target and thus dictates which immediate children of that
+ * target should be ignored and not imported.
+ *
+ * MANDATORY_IGNORES is an array of const char * ignore patterns which
+ * correspond to the svn:inheritable-ignores properties (if any) set on
+ * the root of the repository target or inherited by it.
+ *
* If NO_IGNORE is FALSE, don't import files or directories that match
* ignore patterns.
*
@@ -610,6 +645,9 @@
void *edit_baton,
svn_depth_t depth,
apr_hash_t *excludes,
+ apr_hash_t *autoprops,
+ apr_array_header_t *local_ignores,
+ apr_array_header_t *mandatory_ignores,
svn_boolean_t no_ignore,
svn_boolean_t ignore_unknown_node_types,
svn_client_import_filter_func_t filter_callback,
@@ -624,6 +662,7 @@
import_ctx_t *import_ctx = apr_pcalloc(pool, sizeof(*import_ctx));
const svn_io_dirent2_t *dirent;
+ import_ctx->autoprops = autoprops;
svn_magic__init(&import_ctx->magic_cookie, pool);
/* Get a root dir baton. We pass an invalid revnum to open_root
@@ -687,8 +726,9 @@
if (!no_ignore)
{
SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
- ignores_match = svn_wc_match_ignore_list(local_abspath,
- ignores, pool);
+ ignores_match =
+ (svn_wc_match_ignore_list(local_abspath, ignores, pool)
+ || svn_wc_match_ignore_list(local_abspath, local_ignores, pool));
}
if (!ignores_match)
SVN_ERR(import_file(editor, root_baton, local_abspath, edit_path,
@@ -699,15 +739,35 @@
apr_hash_t *dirents;
if (!no_ignore)
- SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
+ {
+ int i;
- SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes, ignores,
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
+
+ /* If we are not creating new repository paths, then we are creating
+ importing new paths to an existing directory. If that directory
+ has the svn:ignore property set on it, then we want to ignore
+ immediate children that match the pattern(s) defined by that
+ property. */
+ if (!new_entries->nelts)
+ {
+ for (i = 0; i < local_ignores->nelts; i++)
+ {
+ const char *ignore = APR_ARRAY_IDX(local_ignores, i,
+ const char *);
+ APR_ARRAY_PUSH(ignores, const char *) = ignore;
+ }
+ }
+ }
+
+ SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes,
+ ignores, mandatory_ignores,
filter_callback, filter_baton, ctx,
pool, pool));
SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
- root_baton, depth, excludes, no_ignore,
- ignore_unknown_node_types,
+ root_baton, depth, excludes, mandatory_ignores,
+ no_ignore, ignore_unknown_node_types,
filter_callback, filter_baton,
import_ctx, ctx, pool));
@@ -853,6 +913,11 @@
const char *dir;
apr_hash_t *commit_revprops;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *autoprops;
+ apr_array_header_t *mandatory_ignores;
+ svn_opt_revision_t rev;
+ apr_hash_t *local_ignores_hash;
+ apr_array_header_t *local_ignores_arr;
if (svn_path_is_url(path))
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
@@ -964,13 +1029,35 @@
commit_baton, NULL, TRUE,
scratch_pool));
+ /* Get inherited svn:inheritable-auto-props, svn:inheritable-ignores, and
+ svn:ignores for the location we are importing to. */
+ SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
+ scratch_pool, iterpool));
+ SVN_ERR(svn_client__get_inherited_ignores(&mandatory_ignores, url,
+ ctx, scratch_pool, iterpool));
+ rev.kind = svn_opt_revision_head;
+ SVN_ERR(svn_client_propget5(&local_ignores_hash, NULL, SVN_PROP_IGNORE, url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool));
+ local_ignores_arr = apr_array_make(scratch_pool, 1, sizeof(const char *));
+
+ if (apr_hash_count(local_ignores_hash))
+ {
+ svn_string_t *propval = apr_hash_get(local_ignores_hash, url,
+ APR_HASH_KEY_STRING);
+ if (propval)
+ {
+ svn_cstring_split_append(local_ignores_arr, propval->data,
+ "\n\r\t\v ", FALSE, scratch_pool);
+ }
+ }
+
/* If an error occurred during the commit, abort the edit and return
the error. We don't even care if the abort itself fails. */
if ((err = import(local_abspath, new_entries, editor, edit_baton,
- depth, excludes, no_ignore,
- ignore_unknown_node_types,
- filter_callback, filter_baton,
- ctx, iterpool)))
+ depth, excludes, autoprops, local_ignores_arr,
+ mandatory_ignores, no_ignore, ignore_unknown_node_types,
+ filter_callback, filter_baton, ctx, iterpool)))
{
svn_error_clear(editor->abort_edit(edit_baton, iterpool));
return svn_error_trace(err);
@@ -1341,14 +1428,14 @@
/* Easy part of applying DEPTH to externals. */
if (depth == svn_depth_empty)
- {
- /* Don't recurse. */
- return SVN_NO_ERROR;
- }
- else if (depth != svn_depth_infinity)
- {
- include_dir_externals = FALSE;
- /* We slip in dir externals as explicit targets. When we do that,
+ {
+ /* Don't recurse. */
+ return SVN_NO_ERROR;
+ }
+ else if (depth != svn_depth_infinity)
+ {
+ include_dir_externals = FALSE;
+ /* We slip in dir externals as explicit targets. When we do that,
* depth_immediates should become depth_empty for dir externals targets.
* But adding the dir external to the list of targets makes it get
* handled with depth_immediates itself, and thus will also include the
@@ -1363,7 +1450,7 @@
* ### --depth=immediates --include-externals', commit dir externals
* ### (only immediate children of a target) with depth_empty instead of
* ### not at all. No other effect. So not doing that for now. */
- }
+ }
/* Iterate *and* grow REL_TARGETS at the same time. */
rel_targets_nelts_fixed = rel_targets->nelts;
@@ -1770,9 +1857,16 @@
cb.info = &commit_info;
cb.pool = pool;
+ /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
+ * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
+ * parent of nested working copies. We don't support commits to multiple
+ * repositories so using the first WC to get the RA session is safe. */
cmt_err = svn_error_trace(
svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
- base_abspath, commit_items,
+ APR_ARRAY_IDX(lock_targets,
+ 0,
+ const char *),
+ commit_items,
TRUE, FALSE, ctx, pool));
if (cmt_err)
diff --git a/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c
index 70d744d..b5f84b5 100644
--- a/subversion/libsvn_client/commit_util.c
+++ b/subversion/libsvn_client/commit_util.c
@@ -482,8 +482,8 @@
svn_pool_clear(iterpool);
- SVN_ERR(svn_wc__node_is_status_not_present(¬_present, wc_ctx,
- this_abspath, scratch_pool));
+ SVN_ERR(svn_wc__node_is_not_present(¬_present, NULL, NULL, wc_ctx,
+ this_abspath, scratch_pool));
if (!not_present)
continue;
@@ -1793,9 +1793,8 @@
cb_baton.base_url = base_url;
/* Drive the commit editor! */
- SVN_ERR(svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM,
- paths, do_item_commit, &cb_baton,
- scratch_pool));
+ SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
+ do_item_commit, &cb_baton, scratch_pool));
/* Transmit outstanding text deltas. */
for (hi = apr_hash_first(scratch_pool, file_mods);
diff --git a/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c
index b54664e..a706b89 100644
--- a/subversion/libsvn_client/copy.c
+++ b/subversion/libsvn_client/copy.c
@@ -244,6 +244,7 @@
const char *dst_parent_abspath,
svn_boolean_t lock_src,
svn_boolean_t lock_dst,
+ svn_boolean_t allow_mixed_revisions,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
@@ -252,11 +253,12 @@
dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
scratch_pool);
- SVN_ERR(svn_wc_move(ctx->wc_ctx, pair->src_abspath_or_url,
- dst_abspath, FALSE /* metadata_only */,
- ctx->cancel_func, ctx->cancel_baton,
- ctx->notify_func2, ctx->notify_baton2,
- scratch_pool));
+ SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
+ dst_abspath, FALSE /* metadata_only */,
+ allow_mixed_revisions,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
return SVN_NO_ERROR;
}
@@ -267,17 +269,20 @@
const char *dst_parent_abspath,
svn_boolean_t lock_src,
svn_boolean_t lock_dst,
+ svn_boolean_t allow_mixed_revisions,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
if (lock_dst)
SVN_WC__CALL_WITH_WRITE_LOCK(
do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
- lock_dst, ctx, scratch_pool),
+ lock_dst, allow_mixed_revisions, ctx,
+ scratch_pool),
ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
else
SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
- lock_dst, ctx, scratch_pool));
+ lock_dst, allow_mixed_revisions,
+ ctx, scratch_pool));
return SVN_NO_ERROR;
}
@@ -287,6 +292,7 @@
static svn_error_t *
do_wc_to_wc_moves(const apr_array_header_t *copy_pairs,
const char *dst_path,
+ svn_boolean_t allow_mixed_revisions,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -341,13 +347,15 @@
if (lock_src)
SVN_WC__CALL_WITH_WRITE_LOCK(
do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
- lock_src, lock_dst, ctx, iterpool),
+ lock_src, lock_dst,
+ allow_mixed_revisions, ctx, iterpool),
ctx->wc_ctx, src_parent_abspath,
FALSE, iterpool);
else
SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
- lock_src, lock_dst, ctx,
- iterpool));
+ lock_src, lock_dst,
+ allow_mixed_revisions,
+ ctx, iterpool));
}
svn_pool_destroy(iterpool);
@@ -357,18 +365,21 @@
return svn_error_trace(err);
}
-
+/* Verify that the destinations stored in COPY_PAIRS are valid working copy
+ destinations and set pair->dst_parent_abspath and pair->base_name for each
+ item to the resulting location if they do */
static svn_error_t *
-verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
- svn_boolean_t make_parents,
- svn_boolean_t is_move,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
+verify_wc_dsts(const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ svn_boolean_t is_move,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
int i;
- apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- /* Check that all of our SRCs exist, and all the DSTs don't. */
+ /* Check that DST does not exist, but its parent does */
for (i = 0; i < copy_pairs->nelts; i++)
{
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
@@ -377,20 +388,52 @@
svn_pool_clear(iterpool);
- /* Verify that SRC_PATH exists. */
- SVN_ERR(svn_io_check_path(pair->src_abspath_or_url, &pair->src_kind,
- iterpool));
- if (pair->src_kind == svn_node_none)
- return svn_error_createf(
- SVN_ERR_NODE_UNKNOWN_KIND, NULL,
- _("Path '%s' does not exist"),
- svn_dirent_local_style(pair->src_abspath_or_url, pool));
-
/* If DST_PATH does not exist, then its basename will become a new
file or dir added to its parent (possibly an implicit '.').
Else, just error out. */
+ SVN_ERR(svn_wc_read_kind(&dst_kind, ctx->wc_ctx,
+ pair->dst_abspath_or_url, TRUE /* show_hidden */,
+ iterpool));
+ if (dst_kind != svn_node_none)
+ {
+ svn_boolean_t is_not_present;
+ svn_boolean_t is_excluded;
+ svn_boolean_t is_server_excluded;
+
+ SVN_ERR(svn_wc__node_is_not_present(&is_not_present, &is_excluded,
+ &is_server_excluded, ctx->wc_ctx,
+ pair->dst_abspath_or_url,
+ iterpool));
+
+ if (is_excluded || is_server_excluded)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE,
+ NULL, _("Path '%s' exists, but is excluded"),
+ svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
+ }
+
+ if (! is_not_present)
+ {
+ svn_boolean_t is_deleted;
+
+ SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx,
+ pair->dst_abspath_or_url,
+ scratch_pool));
+
+ if (! is_deleted)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists"),
+ svn_dirent_local_style(pair->dst_abspath_or_url,
+ scratch_pool));
+ }
+ }
+
+ /* Check that there is no unversioned obstruction */
SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
iterpool));
+
if (dst_kind != svn_node_none)
{
if (is_move
@@ -408,7 +451,7 @@
SVN_ERR(svn_path_cstring_from_utf8(&dst,
pair->dst_abspath_or_url,
- pool));
+ scratch_pool));
apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
APR_FILEPATH_TRUENAME, iterpool);
@@ -425,7 +468,7 @@
{
/* Ok, we have a single case only rename. Get out of here */
svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
- pair->dst_abspath_or_url, pool);
+ pair->dst_abspath_or_url, result_pool);
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
@@ -433,13 +476,14 @@
}
return svn_error_createf(
- SVN_ERR_ENTRY_EXISTS, NULL,
- _("Path '%s' already exists"),
- svn_dirent_local_style(pair->dst_abspath_or_url, pool));
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists as unversioned node"),
+ svn_dirent_local_style(pair->dst_abspath_or_url,
+ scratch_pool));
}
svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
- pair->dst_abspath_or_url, pool);
+ pair->dst_abspath_or_url, result_pool);
/* Make sure the destination parent is a directory and produce a clear
error message if it is not. */
@@ -465,7 +509,7 @@
return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
_("Path '%s' is not a directory"),
svn_dirent_local_style(
- pair->dst_parent_abspath, pool));
+ pair->dst_parent_abspath, scratch_pool));
}
}
@@ -474,6 +518,43 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
+ svn_boolean_t make_parents,
+ svn_boolean_t is_move,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ /* Check that all of our SRCs exist. */
+ for (i = 0; i < copy_pairs->nelts; i++)
+ {
+ svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
+ svn_client__copy_pair_t *);
+ svn_pool_clear(iterpool);
+
+ /* Verify that SRC_PATH exists. */
+ SVN_ERR(svn_wc_read_kind(&pair->src_kind, ctx->wc_ctx,
+ pair->src_abspath_or_url, FALSE, iterpool));
+ if (pair->src_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Path '%s' does not exist"),
+ svn_dirent_local_style(
+ pair->src_abspath_or_url,
+ scratch_pool));
+ }
+
+ SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, ctx,
+ result_pool, iterpool));
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
/* Path-specific state used as part of path_driver_cb_baton. */
typedef struct path_driver_info_t
@@ -1066,8 +1147,8 @@
cb_baton.is_move = is_move;
/* Call the path-based editor driver. */
- err = svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM, paths,
- path_driver_cb_func, &cb_baton, pool);
+ err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
+ path_driver_cb_func, &cb_baton, pool);
if (err)
{
/* At least try to abort the edit (and fs txn) before throwing err. */
@@ -1601,86 +1682,24 @@
apr_pool_t *scratch_pool)
{
int i;
- const char *src_uuid = NULL, *dst_uuid = NULL;
svn_boolean_t same_repositories;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
/* We've already checked for physical obstruction by a working file.
But there could also be logical obstruction by an entry whose
working file happens to be missing.*/
- for (i = 0; i < copy_pairs->nelts; i++)
- {
- svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
- svn_client__copy_pair_t *);
- svn_node_kind_t kind;
- svn_boolean_t is_excluded;
- svn_boolean_t is_server_excluded;
-
- svn_pool_clear(iterpool);
-
- SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, pair->dst_abspath_or_url,
- FALSE, iterpool));
- if (kind == svn_node_none)
- continue;
-
- /* ### TODO(#2843): Rework these error report. Maybe we can
- ### simplify the conditions? */
-
- /* Hidden by client exclusion */
- SVN_ERR(svn_wc__node_is_status_excluded(&is_excluded, ctx->wc_ctx,
- pair->dst_abspath_or_url,
- iterpool));
- if (is_excluded)
- {
- return svn_error_createf
- (SVN_ERR_ENTRY_EXISTS,
- NULL, _("'%s' is already under version control"),
- svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
- }
-
- /* Hidden by server exclusion (not authorized) */
- SVN_ERR(svn_wc__node_is_status_server_excluded(&is_server_excluded,
- ctx->wc_ctx,
- pair->dst_abspath_or_url,
- iterpool));
- if (is_server_excluded)
- {
- return svn_error_createf
- (SVN_ERR_ENTRY_EXISTS,
- NULL, _("'%s' is already under version control"),
- svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
- }
-
- /* Working file missing to something other than being scheduled
- for addition or in "deleted" state. */
- if (kind != svn_node_dir)
- {
- svn_boolean_t is_deleted;
- svn_boolean_t is_not_present;
-
- SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx,
- pair->dst_abspath_or_url,
- iterpool));
- SVN_ERR(svn_wc__node_is_status_not_present(&is_not_present,
- ctx->wc_ctx,
- pair->dst_abspath_or_url,
- iterpool));
- if ((! is_deleted) && (! is_not_present))
- return svn_error_createf
- (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
- _("Entry for '%s' exists (though the working file is missing)"),
- svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
- }
- }
+ SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, ctx,
+ scratch_pool, iterpool));
/* Decide whether the two repositories are the same or not. */
{
svn_error_t *src_err, *dst_err;
const char *parent;
const char *parent_abspath;
+ const char *src_uuid, *dst_uuid;
/* Get the repository uuid of SRC_URL */
- src_err = svn_ra_get_uuid2(ra_session, &src_uuid, scratch_pool);
+ src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
return svn_error_trace(src_err);
@@ -1695,7 +1714,7 @@
SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
parent_abspath, ctx,
- scratch_pool, scratch_pool);
+ iterpool, iterpool);
if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
return dst_err;
@@ -1705,7 +1724,6 @@
copy-history is attempted. */
if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
same_repositories = FALSE;
-
else
same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
}
@@ -1856,6 +1874,7 @@
try_copy(const apr_array_header_t *sources,
const char *dst_path_in,
svn_boolean_t is_move,
+ svn_boolean_t allow_mixed_revisions,
svn_boolean_t make_parents,
svn_boolean_t ignore_externals,
const apr_hash_t *revprop_table,
@@ -2141,12 +2160,13 @@
if ((! srcs_are_urls) && (! dst_is_url))
{
SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
- ctx, pool));
+ ctx, pool, pool));
/* Copy or move all targets. */
if (is_move)
- return svn_error_trace(do_wc_to_wc_moves(copy_pairs, dst_path_in, ctx,
- pool));
+ return svn_error_trace(do_wc_to_wc_moves(copy_pairs, dst_path_in,
+ allow_mixed_revisions,
+ ctx, pool));
else
return svn_error_trace(do_wc_to_wc_copies(copy_pairs, ctx, pool));
}
@@ -2195,6 +2215,7 @@
err = try_copy(sources, dst_path,
FALSE /* is_move */,
+ TRUE /* allow_mixed_revisions */,
make_parents,
ignore_externals,
revprop_table,
@@ -2226,6 +2247,7 @@
subpool)
: svn_dirent_join(dst_path, src_basename, subpool),
FALSE /* is_move */,
+ TRUE /* allow_mixed_revisions */,
make_parents,
ignore_externals,
revprop_table,
@@ -2240,10 +2262,11 @@
svn_error_t *
-svn_client_move6(const apr_array_header_t *src_paths,
+svn_client_move7(const apr_array_header_t *src_paths,
const char *dst_path,
svn_boolean_t move_as_child,
svn_boolean_t make_parents,
+ svn_boolean_t allow_mixed_revisions,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
void *commit_baton,
@@ -2277,6 +2300,7 @@
err = try_copy(sources, dst_path,
TRUE /* is_move */,
+ allow_mixed_revisions,
make_parents,
FALSE,
revprop_table,
@@ -2307,6 +2331,7 @@
src_basename, pool)
: svn_dirent_join(dst_path, src_basename, pool),
TRUE /* is_move */,
+ allow_mixed_revisions,
make_parents,
FALSE,
revprop_table,
diff --git a/subversion/libsvn_client/ctx.c b/subversion/libsvn_client/ctx.c
index 38490fb..179ded5 100644
--- a/subversion/libsvn_client/ctx.c
+++ b/subversion/libsvn_client/ctx.c
@@ -76,9 +76,12 @@
}
svn_error_t *
-svn_client_create_context(svn_client_ctx_t **ctx,
- apr_pool_t *pool)
+svn_client_create_context2(svn_client_ctx_t **ctx,
+ apr_hash_t *cfg_hash,
+ apr_pool_t *pool)
{
+ svn_config_t *cfg_config;
+
*ctx = apr_pcalloc(pool, sizeof(svn_client_ctx_t));
(*ctx)->notify_func2 = call_notify_func;
@@ -87,8 +90,23 @@
(*ctx)->conflict_func2 = call_conflict_func;
(*ctx)->conflict_baton2 = *ctx;
- SVN_ERR(svn_wc_context_create(&(*ctx)->wc_ctx, NULL /* config */, pool,
+ (*ctx)->config = cfg_hash;
+
+ if (cfg_hash)
+ cfg_config = apr_hash_get(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+ else
+ cfg_config = NULL;
+
+ SVN_ERR(svn_wc_context_create(&(*ctx)->wc_ctx, cfg_config, pool,
pool));
return SVN_NO_ERROR;
}
+
+svn_error_t *
+svn_client_create_context(svn_client_ctx_t **ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_create_context2(ctx, NULL, pool);
+}
diff --git a/subversion/libsvn_client/delete.c b/subversion/libsvn_client/delete.c
index 24c2edb..8e344b0 100644
--- a/subversion/libsvn_client/delete.c
+++ b/subversion/libsvn_client/delete.c
@@ -206,9 +206,8 @@
pool));
/* Call the path-based editor driver. */
- err = svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM,
- relpaths, path_driver_cb_func,
- (void *)editor, pool);
+ err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE,
+ path_driver_cb_func, (void *)editor, pool);
if (err)
{
diff --git a/subversion/libsvn_client/deprecated.c b/subversion/libsvn_client/deprecated.c
index 2606beb..d1c7b72 100644
--- a/subversion/libsvn_client/deprecated.c
+++ b/subversion/libsvn_client/deprecated.c
@@ -699,6 +699,25 @@
}
svn_error_t *
+svn_client_move6(const apr_array_header_t *src_paths,
+ const char *dst_path,
+ svn_boolean_t move_as_child,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_client_move7(src_paths, dst_path,
+ move_as_child, make_parents,
+ TRUE, /* allow_mixed_revisions */
+ revprop_table,
+ commit_callback, commit_baton,
+ ctx, pool));
+}
+
+svn_error_t *
svn_client_move5(svn_commit_info_t **commit_info_p,
const apr_array_header_t *src_paths,
const char *dst_path,
@@ -1712,6 +1731,26 @@
}
svn_error_t *
+svn_client_propget4(apr_hash_t **props,
+ const char *propname,
+ const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_revnum_t *actual_revnum,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_client_propget5(props, NULL, propname, target,
+ peg_revision, revision,
+ actual_revnum, depth,
+ changelists, ctx,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
svn_client_propget3(apr_hash_t **props,
const char *propname,
const char *path_or_url,
@@ -1851,6 +1890,70 @@
return new_item;
}
+/* Baton for use with wrap_proplist_receiver */
+struct proplist_receiver_wrapper_baton {
+ void *baton;
+ svn_proplist_receiver_t receiver;
+};
+
+/* This implements svn_client_proplist_receiver2_t */
+static svn_error_t *
+proplist_wrapper_receiver(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ struct proplist_receiver_wrapper_baton *plrwb = baton;
+
+ if (plrwb->receiver)
+ return plrwb->receiver(plrwb->baton, path, prop_hash, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static void
+wrap_proplist_receiver(svn_proplist_receiver2_t *receiver2,
+ void **receiver2_baton,
+ svn_proplist_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ struct proplist_receiver_wrapper_baton *plrwb = apr_palloc(pool,
+ sizeof(*plrwb));
+
+ /* Set the user provided old format callback in the baton. */
+ plrwb->baton = receiver_baton;
+ plrwb->receiver = receiver;
+
+ *receiver2_baton = plrwb;
+ *receiver2 = proplist_wrapper_receiver;
+}
+
+svn_error_t *
+svn_client_proplist3(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_proplist_receiver_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+
+ svn_proplist_receiver2_t receiver2;
+ void *receiver2_baton;
+
+ wrap_proplist_receiver(&receiver2, &receiver2_baton, receiver, receiver_baton,
+ pool);
+
+ return svn_error_trace(svn_client_proplist4(target, peg_revision, revision,
+ depth, changelists, FALSE,
+ receiver2, receiver2_baton,
+ ctx, pool, pool));
+}
+
/* Receiver baton used by proplist2() */
struct proplist_receiver_baton {
apr_array_header_t *props;
diff --git a/subversion/libsvn_client/diff.c b/subversion/libsvn_client/diff.c
index 93da2ed..550965a 100644
--- a/subversion/libsvn_client/diff.c
+++ b/subversion/libsvn_client/diff.c
@@ -1044,6 +1044,9 @@
subpool, subpool));
SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream, subpool),
NULL, NULL, subpool));
+
+ /* We have a printed a diff for this path, mark it as visited. */
+ mark_path_as_visited(diff_cmd_baton, path);
}
else /* use libsvn_diff to generate the diff */
{
diff --git a/subversion/libsvn_client/externals.c b/subversion/libsvn_client/externals.c
index 1487897..a6691fa 100644
--- a/subversion/libsvn_client/externals.c
+++ b/subversion/libsvn_client/externals.c
@@ -475,12 +475,16 @@
### We can't enable this now, because that would move the external
### information into the wrong working copy */
const char *definition_abspath = svn_dirent_dirname(local_abspath,subpool);
+ apr_array_header_t *inherited_props;
/* Open an RA session to 'source' URL */
SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
url, dir_abspath,
peg_revision, revision,
ctx, subpool));
+ /* Get the external file's iprops. */
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
+ switch_loc->rev, subpool, subpool));
SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, subpool),
subpool));
@@ -492,6 +496,7 @@
switch_loc->url,
switch_loc->repos_root_url,
switch_loc->repos_uuid,
+ inherited_props,
use_commit_times,
diff3_cmd, preserved_exts,
definition_abspath /* def */,
diff --git a/subversion/libsvn_client/iprops.c b/subversion/libsvn_client/iprops.c
new file mode 100644
index 0000000..6d45a40
--- /dev/null
+++ b/subversion/libsvn_client/iprops.c
@@ -0,0 +1,183 @@
+/*
+ * iprops.c: wrappers around wc inherited property functionality
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_ra.h"
+
+#include "client.h"
+#include "svn_private_config.h"
+
+#include "private/svn_wc_private.h"
+
+
+/*** Code. ***/
+
+/* Determine if ABSPATH needs an inherited property cache (i.e. it is a WC
+ root that is not also the repository root or it is switched). If it does,
+ then set *NEEDS_CACHE to true, set it to false otherwise. */
+static svn_error_t *
+need_to_cache_iprops(svn_boolean_t *needs_cache,
+ const char *abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t is_wc_root;
+ svn_error_t *err;
+
+ /* Our starting assumption. */
+ *needs_cache = FALSE;
+
+ err = svn_wc_is_wc_root2(&is_wc_root, ctx->wc_ctx, abspath,
+ scratch_pool);
+
+ /* ABSPATH can't need a cache if it doesn't exist. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ is_wc_root = FALSE;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ if (is_wc_root)
+ {
+ const char *child_repos_relpath;
+
+ /* We want to cache the inherited properties for WC roots, unless that
+ root points to the root of the repository, then there in nowhere to
+ inherit properties from. */
+ SVN_ERR(svn_wc__node_get_repos_relpath(&child_repos_relpath,
+ ctx->wc_ctx, abspath,
+ scratch_pool, scratch_pool));
+ if (child_repos_relpath[0] != '\0')
+ *needs_cache = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_inheritable_props(apr_hash_t **wcroot_iprops,
+ const char *local_abspath,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *wcroot_iprops = apr_hash_make(result_pool);
+
+ /* If we don't have a base revision for LOCAL_ABSPATH then it can't
+ possibly be a working copy root, nor can it contain any WC roots
+ in the form of switched subtrees. So there is nothing to cache. */
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ apr_hash_t *iprop_paths;
+ apr_hash_index_t *hi;
+ const char *old_session_url = NULL;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__get_cached_iprop_children(&iprop_paths, depth,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool, iterpool));
+
+ /* If we are in the midst of a checkout or an update that is bringing in
+ an external, then svn_wc__get_cached_iprop_children won't return
+ LOCAL_ABSPATH in IPROPS_PATHS because the former has no cached iprops
+ yet. So make sure LOCAL_ABSPATH is present if it's a WC root. */
+ if (!apr_hash_get(iprop_paths, local_abspath, APR_HASH_KEY_STRING))
+ {
+ svn_boolean_t needs_cached_iprops;
+
+ SVN_ERR(need_to_cache_iprops(&needs_cached_iprops, local_abspath,
+ ctx, iterpool));
+ if (needs_cached_iprops)
+ {
+ const char *target_abspath = apr_pstrdup(scratch_pool,
+ local_abspath);
+ apr_hash_set(iprop_paths, target_abspath,
+ APR_HASH_KEY_STRING, target_abspath);
+ }
+ }
+
+ for (hi = apr_hash_first(scratch_pool, iprop_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *child_abspath = svn__apr_hash_index_key(hi);
+ const char *url;
+ apr_array_header_t *inherited_props;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, child_abspath,
+ iterpool, iterpool));
+ if (ra_session)
+ {
+ if (old_session_url)
+ SVN_ERR(svn_ra_reparent(ra_session, url, scratch_pool));
+ else
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
+ ra_session, url,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
+ NULL, url,
+ NULL, NULL,
+ FALSE, TRUE,
+ ctx,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
+ "", revision, result_pool,
+ scratch_pool));
+ apr_hash_set(*wcroot_iprops,
+ apr_pstrdup(result_pool, child_abspath),
+ APR_HASH_KEY_STRING,
+ inherited_props);
+ }
+
+ if (old_session_url)
+ SVN_ERR(svn_ra_reparent(ra_session, old_session_url,
+ iterpool));
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_client/merge.c b/subversion/libsvn_client/merge.c
index 79b546a..36d9e96 100644
--- a/subversion/libsvn_client/merge.c
+++ b/subversion/libsvn_client/merge.c
@@ -1607,8 +1607,8 @@
way svn_wc_merge5() can do the merge. */
if (wc_kind != svn_node_file || is_deleted)
{
- const char *moved_to_abspath;
- svn_error_t *err;
+ svn_boolean_t moved_away;
+ svn_wc_conflict_reason_t reason;
/* Maybe the node is excluded via depth filtering? */
@@ -1638,44 +1638,23 @@
/* This is use case 4 described in the paper attached to issue
* #2282. See also notes/tree-conflicts/detection.txt
*/
- err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
- ctx->wc_ctx, local_abspath,
- scratch_pool, scratch_pool);
- if (err)
- {
- if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
- {
- svn_error_clear(err);
- moved_to_abspath = NULL;
- }
- else
- return svn_error_trace(err);
- }
-
- if (moved_to_abspath)
- {
- /* File has been moved away locally -- apply incoming
- * changes at the new location. */
- local_abspath = moved_to_abspath;
- }
+ SVN_ERR(check_moved_away(&moved_away, ctx->wc_ctx,
+ local_abspath, scratch_pool));
+ if (moved_away)
+ reason = svn_wc_conflict_reason_moved_away;
+ else if (is_deleted)
+ reason = svn_wc_conflict_reason_deleted;
else
- {
- svn_wc_conflict_reason_t reason;
-
- if (is_deleted)
- reason = svn_wc_conflict_reason_deleted;
- else
- reason = svn_wc_conflict_reason_missing;
- SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
- svn_wc_conflict_action_edit, reason));
- if (tree_conflicted)
- *tree_conflicted = TRUE;
- if (content_state)
- *content_state = svn_wc_notify_state_missing;
- if (prop_state)
- *prop_state = svn_wc_notify_state_missing;
- return SVN_NO_ERROR;
- }
+ reason = svn_wc_conflict_reason_missing;
+ SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
+ svn_wc_conflict_action_edit, reason));
+ if (tree_conflicted)
+ *tree_conflicted = TRUE;
+ if (content_state)
+ *content_state = svn_wc_notify_state_missing;
+ if (prop_state)
+ *prop_state = svn_wc_notify_state_missing;
+ return SVN_NO_ERROR;
}
/* ### TODO: Thwart attempts to merge into a path that has
@@ -1904,7 +1883,7 @@
svn_revnum_t copyfrom_rev;
svn_stream_t *new_contents, *new_base_contents;
apr_hash_t *new_base_props, *new_props;
- const svn_wc_conflict_description2_t *existing_conflict;
+ svn_boolean_t existing_tree_conflict;
svn_error_t *err;
/* If this is a merge from the same repository as our
@@ -1941,10 +1920,9 @@
scratch_pool, scratch_pool));
}
- err = svn_wc__get_tree_conflict(&existing_conflict,
- merge_b->ctx->wc_ctx,
- mine_abspath, merge_b->pool,
- merge_b->pool);
+ err = svn_wc_conflicted_p3(NULL, NULL, &existing_tree_conflict,
+ merge_b->ctx->wc_ctx, mine_abspath,
+ merge_b->pool);
if (err)
{
@@ -1952,10 +1930,10 @@
return svn_error_trace(err);
svn_error_clear(err);
- existing_conflict = FALSE;
+ existing_tree_conflict = FALSE;
}
- if (existing_conflict)
+ if (existing_tree_conflict)
{
svn_boolean_t moved_here;
svn_wc_conflict_reason_t reason;
@@ -2747,7 +2725,7 @@
merge_cmd_baton_t *merge_b = baton;
if (merge_b->dry_run)
- svn_hash__clear(merge_b->dry_run_deletions, scratch_pool);
+ SVN_ERR(svn_hash__clear(merge_b->dry_run_deletions, scratch_pool));
return SVN_NO_ERROR;
}
@@ -5753,11 +5731,16 @@
svn_opt_revision_t working_revision = { svn_opt_revision_working, { 0 } };
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_hash_index_t *hi;
+ apr_hash_t *externals;
- SVN_ERR(svn_client_propget4(subtrees_with_mergeinfo, SVN_PROP_MERGEINFO,
- target_abspath, &working_revision,
- &working_revision, NULL, depth, NULL,
- ctx, result_pool, scratch_pool));
+ SVN_ERR(svn_client_propget5(subtrees_with_mergeinfo, NULL,
+ SVN_PROP_MERGEINFO, target_abspath,
+ &working_revision, &working_revision, NULL,
+ depth, NULL, ctx, result_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
+ target_abspath, scratch_pool,
+ scratch_pool));
/* Convert property values to svn_mergeinfo_t. */
for (hi = apr_hash_first(scratch_pool, *subtrees_with_mergeinfo);
@@ -5769,6 +5752,15 @@
svn_mergeinfo_t mergeinfo;
svn_error_t *err;
+ /* svn_client_propget5 picks up file externals with
+ mergeinfo, but we don't want those. */
+ if (apr_hash_get(externals, wc_path, APR_HASH_KEY_STRING))
+ {
+ apr_hash_set(*subtrees_with_mergeinfo, wc_path,
+ APR_HASH_KEY_STRING, NULL);
+ continue;
+ }
+
svn_pool_clear(iterpool);
err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_string->data,
@@ -11159,57 +11151,6 @@
svn_client__pathrev_t *yca;
} source_and_target_t;
-/* "Open" the source and target branches of a merge. That means:
- * - find out their exact repository locations (resolve WC paths and
- * non-numeric revision numbers),
- * - check the branches are suitably related,
- * - establish RA session(s) to the repo,
- * - check the WC for suitability (throw an error if unsuitable)
- *
- * Record this information and return it in a new "merge context" object.
- */
-static svn_error_t *
-open_source_and_target(source_and_target_t **source_and_target,
- const char *source_path_or_url,
- const svn_opt_revision_t *source_peg_revision,
- const char *target_abspath,
- svn_boolean_t allow_mixed_rev,
- svn_boolean_t allow_local_mods,
- svn_boolean_t allow_switched_subtrees,
- svn_client_ctx_t *ctx,
- apr_pool_t *session_pool,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
-
- /* Target */
- SVN_ERR(open_target_wc(&s_t->target, target_abspath,
- allow_mixed_rev, allow_local_mods, allow_switched_subtrees,
- ctx, result_pool, scratch_pool));
- SVN_ERR(svn_client_open_ra_session(&s_t->target_ra_session,
- s_t->target->loc.url,
- ctx, session_pool));
-
- /* Source */
- SVN_ERR(svn_client__ra_session_from_path2(
- &s_t->source_ra_session, &s_t->source,
- source_path_or_url, NULL, source_peg_revision, source_peg_revision,
- ctx, result_pool));
-
- *source_and_target = s_t;
- return SVN_NO_ERROR;
-}
-
-/* "Close" any resources that were acquired in the S_T structure. */
-static svn_error_t *
-close_source_and_target(source_and_target_t *s_t,
- apr_pool_t *scratch_pool)
-{
- /* close s_t->source_/target_ra_session */
- return SVN_NO_ERROR;
-}
-
/* Set *INTERSECTION_P to the intersection of BRANCH_HISTORY with the
* revision range OLDEST_REV to YOUNGEST_REV (inclusive).
*
@@ -11474,10 +11415,10 @@
return SVN_NO_ERROR;
}
-/* The body of svn_client__find_symmetric_merge(), which see.
+/* The body of svn_client_find_automatic_merge(), which see.
*/
static svn_error_t *
-find_symmetric_merge(svn_client__pathrev_t **base_p,
+find_automatic_merge(svn_client__pathrev_t **base_p,
svn_client__pathrev_t **mid_p,
source_and_target_t *s_t,
svn_client_ctx_t *ctx,
@@ -11494,14 +11435,23 @@
svn_mergeinfo_inherited,
FALSE /* squelch_incapable */,
scratch_pool));
- SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(&s_t->target_mergeinfo,
- NULL /* inherited */,
- NULL /* from_repos */,
- FALSE /* repos_only */,
- svn_mergeinfo_inherited,
- s_t->target_ra_session,
- s_t->target->abspath,
- ctx, scratch_pool));
+ if (! s_t->target->abspath)
+ SVN_ERR(svn_client__get_repos_mergeinfo(&s_t->target_mergeinfo,
+ s_t->target_ra_session,
+ s_t->target->loc.url,
+ s_t->target->loc.rev,
+ svn_mergeinfo_inherited,
+ FALSE /* squelch_incapable */,
+ scratch_pool));
+ else
+ SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(&s_t->target_mergeinfo,
+ NULL /* inherited */,
+ NULL /* from_repos */,
+ FALSE /* repos_only */,
+ svn_mergeinfo_inherited,
+ s_t->target_ra_session,
+ s_t->target->abspath,
+ ctx, scratch_pool));
/* Get the location-history of each branch. */
s_t->source_branch.tip = s_t->source;
@@ -11545,42 +11495,99 @@
return SVN_NO_ERROR;
}
+/* Details of an automatic merge. */
+struct svn_client_automatic_merge_t
+{
+ svn_client__pathrev_t *yca, *base, *mid, *right, *target;
+ svn_boolean_t allow_mixed_rev, allow_local_mods, allow_switched_subtrees;
+};
+
svn_error_t *
-svn_client__find_symmetric_merge(svn_client__symmetric_merge_t **merge_p,
+svn_client_find_automatic_merge_no_wc(
+ svn_client_automatic_merge_t **merge_p,
const char *source_path_or_url,
const svn_opt_revision_t *source_revision,
- const char *target_wcpath,
- svn_boolean_t allow_mixed_rev,
- svn_boolean_t allow_local_mods,
- svn_boolean_t allow_switched_subtrees,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
svn_client_ctx_t *ctx,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
+ source_and_target_t *s_t = apr_palloc(scratch_pool, sizeof(*s_t));
+ svn_client__pathrev_t *target_loc;
+ svn_client_automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+
+ /* Source */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &s_t->source_ra_session, &s_t->source,
+ source_path_or_url, NULL, source_revision, source_revision,
+ ctx, result_pool));
+
+ /* Target */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &s_t->target_ra_session, &target_loc,
+ target_path_or_url, NULL, target_revision, target_revision,
+ ctx, result_pool));
+ s_t->target = apr_palloc(scratch_pool, sizeof(*s_t->target));
+ s_t->target->kind = svn_node_none;
+ s_t->target->abspath = NULL; /* indicate the target is not a WC */
+ s_t->target->loc = *target_loc;
+
+ SVN_ERR(find_automatic_merge(&merge->base, &merge->mid, s_t,
+ ctx, result_pool, scratch_pool));
+
+ merge->right = s_t->source;
+ merge->target = &s_t->target->loc;
+ merge->yca = s_t->yca;
+ *merge_p = merge;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_find_automatic_merge(svn_client_automatic_merge_t **merge_p,
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_wcpath,
+ svn_boolean_t allow_mixed_rev,
+ svn_boolean_t allow_local_mods,
+ svn_boolean_t allow_switched_subtrees,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
const char *target_abspath;
- source_and_target_t *s_t;
- svn_client__symmetric_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
+ source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
+ svn_client_automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));
SVN_ERR(svn_dirent_get_absolute(&target_abspath, target_wcpath, scratch_pool));
- /* Open RA sessions to the source and target trees. We're not going
- * to check the target WC for mixed-rev, local mods or switched
- * subtrees yet. After we find out what kind of merge is required,
- * then if a reintegrate-like merge is required we'll do the stricter
- * checks, in do_symmetric_merge_locked(). */
- SVN_ERR(open_source_and_target(&s_t, source_path_or_url, source_revision,
- target_abspath,
- TRUE /*allow_mixed_rev*/,
- TRUE /*allow_local_mods*/,
- TRUE /*allow_switched_subtrees*/,
- ctx, result_pool, result_pool, scratch_pool));
+ /* "Open" the target WC. We're not going to check the target WC for
+ * mixed-rev, local mods or switched subtrees yet. After we find out
+ * what kind of merge is required, then if a reintegrate-like merge is
+ * required we'll do the stricter checks, in do_automatic_merge_locked(). */
+ SVN_ERR(open_target_wc(&s_t->target, target_abspath,
+ TRUE /*allow_mixed_rev*/,
+ TRUE /*allow_local_mods*/,
+ TRUE /*allow_switched_subtrees*/,
+ ctx, result_pool, scratch_pool));
+
+ /* Open RA sessions to the source and target trees. */
+ SVN_ERR(svn_client_open_ra_session(&s_t->target_ra_session,
+ s_t->target->loc.url,
+ ctx, result_pool));
+ /* ### check for null URL (i.e. added path) here, like in reintegrate? */
+ SVN_ERR(svn_client__ra_session_from_path2(
+ &s_t->source_ra_session, &s_t->source,
+ source_path_or_url, NULL, source_revision, source_revision,
+ ctx, result_pool));
/* Check source is in same repos as target. */
SVN_ERR(check_same_repos(s_t->source, source_path_or_url,
&s_t->target->loc, target_wcpath,
TRUE /* strict_urls */, scratch_pool));
- SVN_ERR(find_symmetric_merge(&merge->base, &merge->mid, s_t,
+ SVN_ERR(find_automatic_merge(&merge->base, &merge->mid, s_t,
ctx, result_pool, scratch_pool));
merge->yca = s_t->yca;
merge->right = s_t->source;
@@ -11590,12 +11597,12 @@
*merge_p = merge;
- SVN_ERR(close_source_and_target(s_t, scratch_pool));
+ /* TODO: Close the source and target sessions here? */
return SVN_NO_ERROR;
}
-/* The body of svn_client__do_symmetric_merge(), which see.
+/* The body of svn_client_do_automatic_merge(), which see.
*
* Five locations are inputs: YCA, BASE, MID, RIGHT, TARGET, as shown
* depending on whether the base is on the source branch or the target
@@ -11623,7 +11630,7 @@
* eliminate already-cherry-picked revisions from the source.
*/
static svn_error_t *
-do_symmetric_merge_locked(const svn_client__symmetric_merge_t *merge,
+do_automatic_merge_locked(const svn_client_automatic_merge_t *merge,
const char *target_abspath,
svn_depth_t depth,
svn_boolean_t force,
@@ -11713,7 +11720,7 @@
gaps that are older than the base that we calculated (which is
for the root path of the merge).
- An improvement would be to change find_symmetric_merge() to
+ An improvement would be to change find_automatic_merge() to
find the base for each sutree, and then here use the oldest base
among all subtrees. */
merge_source_t source;
@@ -11742,15 +11749,15 @@
}
svn_error_t *
-svn_client__do_symmetric_merge(const svn_client__symmetric_merge_t *merge,
- const char *target_wcpath,
- svn_depth_t depth,
- svn_boolean_t force,
- svn_boolean_t record_only,
- svn_boolean_t dry_run,
- const apr_array_header_t *merge_options,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
+svn_client_do_automatic_merge(const svn_client_automatic_merge_t *merge,
+ const char *target_wcpath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t record_only,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
{
const char *target_abspath, *lock_abspath;
@@ -11759,16 +11766,43 @@
if (!dry_run)
SVN_WC__CALL_WITH_WRITE_LOCK(
- do_symmetric_merge_locked(merge,
+ do_automatic_merge_locked(merge,
target_abspath, depth,
force, record_only, dry_run,
merge_options, ctx, pool),
ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
else
- SVN_ERR(do_symmetric_merge_locked(merge,
+ SVN_ERR(do_automatic_merge_locked(merge,
target_abspath, depth,
force, record_only, dry_run,
merge_options, ctx, pool));
return SVN_NO_ERROR;
}
+
+svn_boolean_t
+svn_client_automatic_merge_is_reintegrate_like(
+ const svn_client_automatic_merge_t *merge)
+{
+ return merge->mid != NULL;
+}
+
+svn_error_t *
+svn_client__automatic_merge_get_locations(
+ svn_client__pathrev_t **yca,
+ svn_client__pathrev_t **base,
+ svn_client__pathrev_t **right,
+ svn_client__pathrev_t **target,
+ const svn_client_automatic_merge_t *merge,
+ apr_pool_t *result_pool)
+{
+ if (yca)
+ *yca = svn_client__pathrev_dup(merge->yca, result_pool);
+ if (base)
+ *base = svn_client__pathrev_dup(merge->base, result_pool);
+ if (right)
+ *right = svn_client__pathrev_dup(merge->right, result_pool);
+ if (target)
+ *target = svn_client__pathrev_dup(merge->target, result_pool);
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_client/mergeinfo.c b/subversion/libsvn_client/mergeinfo.c
index 494f944..0ef30e8 100644
--- a/subversion/libsvn_client/mergeinfo.c
+++ b/subversion/libsvn_client/mergeinfo.c
@@ -1572,6 +1572,44 @@
return SVN_NO_ERROR;
}
+
+/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path
+ converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO
+ is declared as 'apr_hash_t *' because its key do not obey the rules of
+ 'svn_mergeinfo_t'.
+
+ Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use
+ SCRATCH_POOL for any temporary allocations. */
+static svn_error_t *
+mergeinfo_relpaths_to_urls(apr_hash_t **out_mergeinfo,
+ svn_mergeinfo_t mergeinfo,
+ const char *repos_root_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *out_mergeinfo = NULL;
+ if (mergeinfo)
+ {
+ apr_hash_index_t *hi;
+ apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool);
+
+ for (hi = apr_hash_first(scratch_pool, mergeinfo);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *key = svn__apr_hash_index_key(hi);
+ void *val = svn__apr_hash_index_val(hi);
+
+ apr_hash_set(full_path_mergeinfo,
+ svn_path_url_add_component2(repos_root_url, key + 1,
+ result_pool),
+ APR_HASH_KEY_STRING, val);
+ }
+ *out_mergeinfo = full_path_mergeinfo;
+ }
+
+ return SVN_NO_ERROR;
+}
+
/*** Public APIs ***/
@@ -1614,8 +1652,8 @@
mergeinfo = NULL;
}
- SVN_ERR(svn_mergeinfo__relpaths_to_urls(mergeinfo_p, mergeinfo,
- repos_root, pool, pool));
+ SVN_ERR(mergeinfo_relpaths_to_urls(mergeinfo_p, mergeinfo,
+ repos_root, pool, pool));
return SVN_NO_ERROR;
}
@@ -1729,7 +1767,10 @@
}
}
- /* Open RA sessions to the repository for the source and target.
+ /* Fetch the location history as mergeinfo, for the source branch
+ * (between the given start and end revisions), and, if we're finding
+ * merged revisions, then also for the entire target branch.
+ *
* ### TODO: As the source and target must be in the same repository, we
* should share a single session, tracking the two URLs separately. */
{
diff --git a/subversion/libsvn_client/prop_commands.c b/subversion/libsvn_client/prop_commands.c
index c07c3ab..0c36ab7 100644
--- a/subversion/libsvn_client/prop_commands.c
+++ b/subversion/libsvn_client/prop_commands.c
@@ -38,6 +38,7 @@
#include "svn_pools.h"
#include "svn_props.h"
#include "svn_hash.h"
+#include "svn_sorts.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
@@ -355,11 +356,9 @@
ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
}
-
- svn_error_clear(err);
}
- else
- SVN_ERR(err);
+
+ SVN_ERR(err);
SVN_WC__CALL_WITH_WRITE_LOCK(
svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname,
@@ -594,21 +593,28 @@
/* Helper for the remote case of svn_client_propget.
*
- * Get the value of property PROPNAME in REVNUM, using RA_LIB and
- * SESSION. Store the value ('svn_string_t *') in PROPS, under the
- * path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *').
+ * If PROPS is not null, then get the value of property PROPNAME in REVNUM,
+ using RA_LIB and SESSION. Store the value ('svn_string_t *') in PROPS,
+ under the path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *').
+ *
+ * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a
+ * depth-first ordered array of svn_prop_inherited_item_t * structures
+ * representing the PROPNAME properties inherited by the target. If
+ * INHERITABLE_PROPS in not null and no inheritable properties are found,
+ * then set *INHERITED_PROPS to an empty array.
*
* Recurse according to DEPTH, similarly to svn_client_propget3().
*
* KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
* Yes, caller passes this; it makes the recursion more efficient :-).
*
- * Allocate the keys and values in PERM_POOL, but do all temporary
- * work in WORK_POOL. The two pools can be the same; recursive
- * calls may use a different WORK_POOL, however.
+ * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary
+ * work in SCRATCH_POOL. The two pools can be the same; recursive
+ * calls may use a different SCRATCH_POOL, however.
*/
static svn_error_t *
remote_propget(apr_hash_t *props,
+ apr_array_header_t **inherited_props,
const char *propname,
const char *target_prefix,
const char *target_relative,
@@ -616,26 +622,27 @@
svn_revnum_t revnum,
svn_ra_session_t *ra_session,
svn_depth_t depth,
- apr_pool_t *perm_pool,
- apr_pool_t *work_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
apr_hash_t *dirents;
- apr_hash_t *prop_hash;
+ apr_hash_t *prop_hash = NULL;
const svn_string_t *val;
const char *target_full_url =
- svn_path_url_add_component2(target_prefix, target_relative, work_pool);
+ svn_path_url_add_component2(target_prefix, target_relative,
+ scratch_pool);
if (kind == svn_node_dir)
{
SVN_ERR(svn_ra_get_dir2(ra_session,
(depth >= svn_depth_files ? &dirents : NULL),
NULL, &prop_hash, target_relative, revnum,
- SVN_DIRENT_KIND, work_pool));
+ SVN_DIRENT_KIND, scratch_pool));
}
else if (kind == svn_node_file)
{
SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
- NULL, NULL, &prop_hash, work_pool));
+ NULL, NULL, &prop_hash, scratch_pool));
}
else if (kind == svn_node_none)
{
@@ -650,10 +657,52 @@
target_full_url);
}
- if ((val = apr_hash_get(prop_hash, propname, APR_HASH_KEY_STRING)))
+ if (inherited_props)
{
- apr_hash_set(props, apr_pstrdup(perm_pool, target_full_url),
- APR_HASH_KEY_STRING, svn_string_dup(val, perm_pool));
+ /* We will filter out all but PROPNAME later, making a final copy
+ in RESULT_POOL, so pass SCRATCH_POOL for both pools. */
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, inherited_props,
+ target_relative, revnum,
+ scratch_pool, scratch_pool));
+ }
+
+ /* Make a copy of any inherited PROPNAME properties in RESULT_POOL. */
+ if (inherited_props)
+ {
+ int i;
+ apr_array_header_t *final_iprops =
+ apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
+
+ for (i = 0; i < (*inherited_props)->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX((*inherited_props), i, svn_prop_inherited_item_t *);
+ svn_string_t *iprop_val = apr_hash_get(iprop->prop_hash, propname,
+ APR_HASH_KEY_STRING);
+
+ if (iprop_val)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url =
+ apr_pstrdup(result_pool, iprop->path_or_url);
+ new_iprop->prop_hash = apr_hash_make(result_pool);
+ apr_hash_set(new_iprop->prop_hash,
+ apr_pstrdup(result_pool, propname),
+ APR_HASH_KEY_STRING,
+ svn_string_dup(iprop_val, result_pool));
+ APR_ARRAY_PUSH(final_iprops, svn_prop_inherited_item_t *) =
+ new_iprop;
+ }
+ }
+ *inherited_props = final_iprops;
+ }
+
+ if (prop_hash
+ && (val = apr_hash_get(prop_hash, propname, APR_HASH_KEY_STRING)))
+ {
+ apr_hash_set(props, apr_pstrdup(result_pool, target_full_url),
+ APR_HASH_KEY_STRING, svn_string_dup(val, result_pool));
}
if (depth >= svn_depth_files
@@ -661,9 +710,9 @@
&& apr_hash_count(dirents) > 0)
{
apr_hash_index_t *hi;
- apr_pool_t *iterpool = svn_pool_create(work_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- for (hi = apr_hash_first(work_pool, dirents);
+ for (hi = apr_hash_first(scratch_pool, dirents);
hi;
hi = apr_hash_next(hi))
{
@@ -683,7 +732,7 @@
new_target_relative = svn_relpath_join(target_relative, this_name,
iterpool);
- SVN_ERR(remote_propget(props,
+ SVN_ERR(remote_propget(props, NULL,
propname,
target_prefix,
new_target_relative,
@@ -691,7 +740,7 @@
revnum,
ra_session,
depth_below_here,
- perm_pool, iterpool));
+ result_pool, iterpool));
}
svn_pool_destroy(iterpool);
@@ -792,7 +841,8 @@
/* Note: this implementation is very similar to svn_client_proplist. */
svn_error_t *
-svn_client_propget4(apr_hash_t **props,
+svn_client_propget5(apr_hash_t **props,
+ apr_array_header_t **inherited_props,
const char *propname,
const char *target,
const svn_opt_revision_t *peg_revision,
@@ -805,6 +855,8 @@
apr_pool_t *scratch_pool)
{
svn_revnum_t revnum;
+ svn_boolean_t local_explicit_props;
+ svn_boolean_t local_iprops;
SVN_ERR(error_if_wcprop_name(propname));
if (!svn_path_is_url(target))
@@ -814,9 +866,19 @@
target);
revision = svn_cl__rev_default_to_peg(revision, peg_revision);
- if (! svn_path_is_url(target)
- && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
- && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
+ local_explicit_props =
+ (! svn_path_is_url(target)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
+
+ local_iprops =
+ (local_explicit_props
+ && (peg_revision->kind == svn_opt_revision_working
+ || peg_revision->kind == svn_opt_revision_unspecified )
+ && (revision->kind == svn_opt_revision_working
+ || revision->kind == svn_opt_revision_unspecified ));
+
+ if (local_explicit_props)
{
svn_node_kind_t kind;
svn_boolean_t pristine;
@@ -850,30 +912,120 @@
else if (err)
return svn_error_trace(err);
+ if (inherited_props && local_iprops)
+ SVN_ERR(svn_wc__get_iprops(inherited_props, ctx->wc_ctx,
+ target, propname,
+ scratch_pool, scratch_pool));
+
SVN_ERR(get_prop_from_wc(props, propname, target,
pristine, kind,
depth, changelists, ctx, scratch_pool,
result_pool));
}
- else
+
+ if ((inherited_props && !local_iprops)
+ || !local_explicit_props)
{
- svn_client__pathrev_t *loc;
svn_ra_session_t *ra_session;
svn_node_kind_t kind;
+ svn_opt_revision_t new_operative_rev;
+ svn_opt_revision_t new_peg_rev;
- /* Get an RA plugin for this filesystem object. */
- SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
- target, NULL,
- peg_revision,
- revision, ctx, scratch_pool));
+ /* Peg or operative revisions may be WC specific for
+ TARGET's explicit props, but still require us to
+ contact the repository for the inherited properties. */
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
+ || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t origin_rev;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *local_abspath;
+ const char *copy_root_abspath;
+ svn_boolean_t is_copy;
- SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, target,
+ scratch_pool));
- *props = apr_hash_make(result_pool);
- SVN_ERR(remote_propget(*props, propname, loc->url, "",
- kind, loc->rev, ra_session,
- depth, result_pool, scratch_pool));
- revnum = loc->rev;
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ SVN_ERR(svn_wc__node_get_origin(&is_copy,
+ &origin_rev,
+ &repos_relpath,
+ &repos_root_url,
+ &repos_uuid,
+ ©_root_abspath,
+ ctx->wc_ctx,
+ local_abspath,
+ FALSE, /* scan_deleted */
+ result_pool,
+ scratch_pool));
+ if (repos_relpath)
+ {
+ target = svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool);
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ svn_revnum_t resolved_peg_rev;
+
+ SVN_ERR(svn_client__get_revision_number(
+ &resolved_peg_rev, NULL, ctx->wc_ctx,
+ local_abspath, NULL, peg_revision, scratch_pool));
+ new_peg_rev.kind = svn_opt_revision_number;
+ new_peg_rev.value.number = resolved_peg_rev;
+ peg_revision = &new_peg_rev;
+ }
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t resolved_operative_rev;
+
+ SVN_ERR(svn_client__get_revision_number(
+ &resolved_operative_rev, NULL, ctx->wc_ctx,
+ local_abspath, NULL, revision, scratch_pool));
+ new_operative_rev.kind = svn_opt_revision_number;
+ new_operative_rev.value.number = resolved_operative_rev;
+ revision = &new_operative_rev;
+ }
+ }
+ else
+ {
+ /* TARGET doesn't exist in the repository, so there are
+ obviously not inherited props to be found there. */
+ local_iprops = TRUE;
+ *inherited_props = apr_array_make(
+ result_pool, 0, sizeof(svn_prop_inherited_item_t *));
+ }
+ }
+ }
+
+ /* Do we still have anything to ask the repository about? */
+ if (!local_explicit_props || !local_iprops)
+ {
+ svn_client__pathrev_t *loc;
+
+ /* Get an RA plugin for this filesystem object. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ target, NULL,
+ peg_revision,
+ revision, ctx,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
+ scratch_pool));
+
+ if (!local_explicit_props)
+ *props = apr_hash_make(result_pool);
+
+ SVN_ERR(remote_propget(!local_explicit_props ? *props : NULL,
+ !local_iprops ? inherited_props : NULL,
+ propname, loc->url, "",
+ kind, loc->rev, ra_session,
+ depth, result_pool, scratch_pool));
+ revnum = loc->rev;
+ }
}
if (actual_revnum)
@@ -907,19 +1059,24 @@
}
-/* Call RECEIVER for the given PATH and PROP_HASH.
+/* Call RECEIVER for the given PATH and its PROP_HASH and/or
+ * INHERITED_PROPERTIES.
*
- * If PROP_HASH is null or has zero count, do nothing.
+ * If PROP_HASH is null or has zero count or INHERITED_PROPERTIES is null,
+ * then do nothing.
*/
static svn_error_t*
call_receiver(const char *path,
apr_hash_t *prop_hash,
- svn_proplist_receiver_t receiver,
+ apr_array_header_t *inherited_properties,
+ svn_proplist_receiver2_t receiver,
void *receiver_baton,
- apr_pool_t *pool)
+ apr_pool_t *scratch_pool)
{
- if (prop_hash && apr_hash_count(prop_hash))
- SVN_ERR(receiver(receiver_baton, path, prop_hash, pool));
+ if ((prop_hash && apr_hash_count(prop_hash))
+ || inherited_properties)
+ SVN_ERR(receiver(receiver_baton, path, prop_hash, inherited_properties,
+ scratch_pool));
return SVN_NO_ERROR;
}
@@ -927,20 +1084,23 @@
/* Helper for the remote case of svn_client_proplist.
*
- * Push a new 'svn_client_proplist_item_t *' item onto PROPLIST,
- * containing the properties for "TARGET_PREFIX/TARGET_RELATIVE" in
- * REVNUM, obtained using RA_LIB and SESSION. The item->node_name
- * will be "TARGET_PREFIX/TARGET_RELATIVE", and the value will be a
- * hash mapping 'const char *' property names onto 'svn_string_t *'
- * property values.
+ * If GET_EXPLICIT_PROPS is true, then call RECEIVER for paths at or under
+ * "TARGET_PREFIX/TARGET_RELATIVE@REVNUM" (obtained using RA_SESSION) which
+ * have regular properties. If GET_TARGET_INHERITED_PROPS is true, then send
+ * the target's inherited properties to the callback.
*
- * Allocate the new item and its contents in POOL.
- * Do all looping, recursion, and temporary work in SCRATCHPOOL.
+ * The 'path' and keys for 'prop_hash' and 'inherited_prop' arguments to
+ * RECEIVER are all URLs.
+ *
+ * RESULT_POOL is used to allocated the 'path', 'prop_hash', and
+ * 'inherited_prop' arguments to RECEIVER. SCRATCH_POOL is used for all
+ * other (temporary) allocations.
*
* KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
*
* If the target is a directory, only fetch properties for the files
- * and directories at depth DEPTH.
+ * and directories at depth DEPTH. DEPTH has not effect on inherited
+ * properties.
*/
static svn_error_t *
remote_proplist(const char *target_prefix,
@@ -948,29 +1108,36 @@
svn_node_kind_t kind,
svn_revnum_t revnum,
svn_ra_session_t *ra_session,
+ svn_boolean_t get_explicit_props,
+ svn_boolean_t get_target_inherited_props,
svn_depth_t depth,
- svn_proplist_receiver_t receiver,
+ svn_proplist_receiver2_t receiver,
void *receiver_baton,
- apr_pool_t *pool,
- apr_pool_t *scratchpool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
apr_hash_t *dirents;
- apr_hash_t *prop_hash, *final_hash;
+ apr_hash_t *prop_hash = NULL;
+ apr_hash_t *final_hash = NULL;
apr_hash_index_t *hi;
const char *target_full_url =
- svn_path_url_add_component2(target_prefix, target_relative, scratchpool);
+ svn_path_url_add_component2(target_prefix, target_relative, scratch_pool);
+ apr_array_header_t *inherited_props;
+ /* Note that we pass only the SCRATCH_POOL to svn_ra_get[dir*|file*] because
+ we'll be filtering out non-regular properties from PROP_HASH before we
+ return. */
if (kind == svn_node_dir)
{
SVN_ERR(svn_ra_get_dir2(ra_session,
(depth > svn_depth_empty) ? &dirents : NULL,
NULL, &prop_hash, target_relative, revnum,
- SVN_DIRENT_KIND, scratchpool));
+ SVN_DIRENT_KIND, scratch_pool));
}
else if (kind == svn_node_file)
{
SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
- NULL, NULL, &prop_hash, scratchpool));
+ NULL, NULL, &prop_hash, scratch_pool));
}
else
{
@@ -979,38 +1146,50 @@
target_full_url);
}
- /* Filter out non-regular properties, since the RA layer returns all
- kinds. Copy regular properties keys/vals from the prop_hash
- allocated in SCRATCHPOOL to the "final" hash allocated in POOL. */
- final_hash = apr_hash_make(pool);
- for (hi = apr_hash_first(scratchpool, prop_hash);
- hi;
- hi = apr_hash_next(hi))
+ if (get_target_inherited_props)
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
+ target_relative, revnum,
+ result_pool, scratch_pool));
+ else
+ inherited_props = NULL;
+
+ if (get_explicit_props)
{
- const char *name = svn__apr_hash_index_key(hi);
- apr_ssize_t klen = svn__apr_hash_index_klen(hi);
- svn_string_t *value = svn__apr_hash_index_val(hi);
- svn_prop_kind_t prop_kind;
-
- prop_kind = svn_property_kind2(name);
-
- if (prop_kind == svn_prop_regular_kind)
+ /* Filter out non-regular properties, since the RA layer returns all
+ kinds. Copy regular properties keys/vals from the prop_hash
+ allocated in SCRATCH_POOL to the "final" hash allocated in
+ RESULT_POOL. */
+ final_hash = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(scratch_pool, prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
{
- name = apr_pstrdup(pool, name);
- value = svn_string_dup(value, pool);
- apr_hash_set(final_hash, name, klen, value);
- }
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ svn_prop_kind_t prop_kind;
+
+ prop_kind = svn_property_kind2(name);
+
+ if (prop_kind == svn_prop_regular_kind)
+ {
+ name = apr_pstrdup(result_pool, name);
+ value = svn_string_dup(value, result_pool);
+ apr_hash_set(final_hash, name, klen, value);
+ }
+ }
}
- SVN_ERR(call_receiver(target_full_url, final_hash, receiver, receiver_baton,
- pool));
+ SVN_ERR(call_receiver(target_full_url, final_hash, inherited_props,
+ receiver, receiver_baton, result_pool));
if (depth > svn_depth_empty
+ && get_explicit_props
&& (kind == svn_node_dir) && (apr_hash_count(dirents) > 0))
{
- apr_pool_t *subpool = svn_pool_create(scratchpool);
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
- for (hi = apr_hash_first(scratchpool, dirents);
+ for (hi = apr_hash_first(scratch_pool, dirents);
hi;
hi = apr_hash_next(hi))
{
@@ -1036,10 +1215,12 @@
this_ent->kind,
revnum,
ra_session,
+ TRUE,
+ FALSE,
depth_below_here,
receiver,
receiver_baton,
- pool,
+ result_pool,
subpool));
}
}
@@ -1055,7 +1236,7 @@
struct recursive_proplist_receiver_baton
{
svn_wc_context_t *wc_ctx; /* Working copy context. */
- svn_proplist_receiver_t wrapped_receiver; /* Proplist receiver to call. */
+ svn_proplist_receiver2_t wrapped_receiver; /* Proplist receiver to call. */
void *wrapped_receiver_baton; /* Baton for the proplist receiver. */
/* Anchor, anchor_abspath pair for converting to relative paths */
@@ -1086,20 +1267,238 @@
path = local_abspath;
return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton,
- path, props, scratch_pool));
+ path, props, NULL,
+ scratch_pool));
+}
+
+/* Helper for svn_client_proplist4 when retrieving properties and/or
+ inherited properties from the repository. Except as noted below,
+ all arguments are as per svn_client_proplist4.
+
+ GET_EXPLICIT_PROPS controls if explicit props are retrieved. */
+static svn_error_t *
+get_remote_props(const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ svn_boolean_t get_explicit_props,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t kind;
+ svn_opt_revision_t new_operative_rev;
+ svn_opt_revision_t new_peg_rev;
+ svn_client__pathrev_t *loc;
+
+ /* Peg or operative revisions may be WC specific for
+ PATH_OR_URL's explicit props, but still require us to
+ contact the repository for the inherited properties. */
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
+ || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t origin_rev;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *local_abspath;
+ const char *copy_root_abspath;
+ svn_boolean_t is_copy;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
+ scratch_pool));
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ SVN_ERR(svn_wc__node_get_origin(&is_copy,
+ &origin_rev,
+ &repos_relpath,
+ &repos_root_url,
+ &repos_uuid,
+ ©_root_abspath,
+ ctx->wc_ctx,
+ local_abspath,
+ FALSE, /* scan_deleted */
+ result_pool,
+ scratch_pool));
+ if (repos_relpath)
+ {
+ path_or_url =
+ svn_path_url_add_component2(repos_root_url,
+ repos_relpath,
+ scratch_pool);
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
+ {
+ svn_revnum_t resolved_peg_rev;
+
+ SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev,
+ NULL, ctx->wc_ctx,
+ local_abspath, NULL,
+ peg_revision,
+ scratch_pool));
+ new_peg_rev.kind = svn_opt_revision_number;
+ new_peg_rev.value.number = resolved_peg_rev;
+ peg_revision = &new_peg_rev;
+ }
+
+ if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
+ {
+ svn_revnum_t resolved_operative_rev;
+
+ SVN_ERR(svn_client__get_revision_number(
+ &resolved_operative_rev,
+ NULL, ctx->wc_ctx,
+ local_abspath, NULL,
+ revision,
+ scratch_pool));
+ new_operative_rev.kind = svn_opt_revision_number;
+ new_operative_rev.value.number = resolved_operative_rev;
+ revision = &new_operative_rev;
+ }
+ }
+ else
+ {
+ /* PATH_OR_URL doesn't exist in the repository, so there are
+ obviously not inherited props to be found there. If we
+ aren't looking for explicit props then we're done. */
+ if (!get_explicit_props)
+ return SVN_NO_ERROR;
+ }
+ }
+ }
+
+ /* Get an RA session for this URL. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ path_or_url, NULL,
+ peg_revision,
+ revision, ctx,
+ result_pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
+ result_pool));
+
+ SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session,
+ get_explicit_props,
+ get_target_inherited_props,
+ depth, receiver, receiver_baton,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Helper for svn_client_proplist4 when retrieving properties and
+ possibly inherited properties from the WC. All arguments are as
+ per svn_client_proplist4. */
+static svn_error_t *
+get_local_props(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t pristine;
+ svn_node_kind_t kind;
+ apr_hash_t *changelist_hash = NULL;
+ const char *local_abspath;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
+ scratch_pool));
+
+ pristine = ((revision->kind == svn_opt_revision_committed)
+ || (revision->kind == svn_opt_revision_base));
+
+ SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE,
+ scratch_pool));
+
+ if (kind == svn_node_unknown || kind == svn_node_none)
+ {
+ /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
+ for this function. */
+ return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ if (get_target_inherited_props)
+ {
+ apr_array_header_t *iprops;
+
+ SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath,
+ NULL, scratch_pool, scratch_pool));
+ SVN_ERR(call_receiver(path_or_url, NULL, iprops, receiver,
+ receiver_baton, scratch_pool));
+ }
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
+ changelists, scratch_pool));
+
+ /* Fetch, recursively or not. */
+ if (kind == svn_node_dir)
+ {
+ struct recursive_proplist_receiver_baton rb;
+
+ rb.wc_ctx = ctx->wc_ctx;
+ rb.wrapped_receiver = receiver;
+ rb.wrapped_receiver_baton = receiver_baton;
+
+ if (strcmp(path_or_url, local_abspath) != 0)
+ {
+ rb.anchor = path_or_url;
+ rb.anchor_abspath = local_abspath;
+ }
+ else
+ {
+ rb.anchor = NULL;
+ rb.anchor_abspath = NULL;
+ }
+
+ SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
+ depth, pristine, changelists,
+ recursive_proplist_receiver, &rb,
+ ctx->cancel_func,
+ ctx->cancel_baton, result_pool));
+ }
+ else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
+ changelist_hash, scratch_pool))
+ {
+ apr_hash_t *hash;
+
+ SVN_ERR(pristine_or_working_props(&hash, ctx->wc_ctx, local_abspath,
+ pristine, result_pool,
+ scratch_pool));
+ SVN_ERR(call_receiver(path_or_url, hash, NULL,
+ receiver, receiver_baton, scratch_pool));
+
+ }
+ return SVN_NO_ERROR;
}
svn_error_t *
-svn_client_proplist3(const char *path_or_url,
+svn_client_proplist4(const char *path_or_url,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *revision,
svn_depth_t depth,
const apr_array_header_t *changelists,
- svn_proplist_receiver_t receiver,
+ svn_boolean_t get_target_inherited_props,
+ svn_proplist_receiver2_t receiver,
void *receiver_baton,
svn_client_ctx_t *ctx,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
+ svn_boolean_t local_explicit_props;
+ svn_boolean_t local_iprops;
+
peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
path_or_url);
revision = svn_cl__rev_default_to_peg(revision, peg_revision);
@@ -1107,93 +1506,36 @@
if (depth == svn_depth_unknown)
depth = svn_depth_empty;
- if (! svn_path_is_url(path_or_url)
- && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
- && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
+ /* Are explicit props available locally? */
+ local_explicit_props =
+ (! svn_path_is_url(path_or_url)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
+ && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
+
+ /* If we want iprops are they avaiable locally? */
+ local_iprops =
+ (get_target_inherited_props /* We want iprops */
+ && local_explicit_props /* No local explicit props means no local iprops. */
+ && (peg_revision->kind == svn_opt_revision_working
+ || peg_revision->kind == svn_opt_revision_unspecified )
+ && (revision->kind == svn_opt_revision_working
+ || revision->kind == svn_opt_revision_unspecified ));
+
+ if ((get_target_inherited_props && !local_iprops)
+ || !local_explicit_props)
{
- svn_boolean_t pristine;
- svn_node_kind_t kind;
- apr_hash_t *changelist_hash = NULL;
- const char *local_abspath;
-
- SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, pool));
-
- pristine = ((revision->kind == svn_opt_revision_committed)
- || (revision->kind == svn_opt_revision_base));
-
- SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE,
- pool));
-
- if (kind == svn_node_unknown || kind == svn_node_none)
- {
- /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
- for this function. */
- return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
- _("'%s' is not under version control"),
- svn_dirent_local_style(local_abspath,
- pool));
- }
-
- if (changelists && changelists->nelts)
- SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
- changelists, pool));
-
- /* Fetch, recursively or not. */
- if (kind == svn_node_dir)
- {
- struct recursive_proplist_receiver_baton rb;
-
- rb.wc_ctx = ctx->wc_ctx;
- rb.wrapped_receiver = receiver;
- rb.wrapped_receiver_baton = receiver_baton;
-
- if (strcmp(path_or_url, local_abspath) != 0)
- {
- rb.anchor = path_or_url;
- rb.anchor_abspath = local_abspath;
- }
- else
- {
- rb.anchor = NULL;
- rb.anchor_abspath = NULL;
- }
-
- SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
- depth, pristine, changelists,
- recursive_proplist_receiver, &rb,
- ctx->cancel_func,
- ctx->cancel_baton, pool));
- }
- else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
- changelist_hash, pool))
- {
- apr_hash_t *hash;
-
- SVN_ERR(pristine_or_working_props(&hash, ctx->wc_ctx, local_abspath,
- pristine, pool, pool));
- SVN_ERR(call_receiver(path_or_url, hash,
- receiver, receiver_baton, pool));
-
- }
+ SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth,
+ !local_explicit_props,
+ (get_target_inherited_props && !local_iprops),
+ receiver, receiver_baton, ctx, result_pool,
+ scratch_pool));
}
- else /* remote target */
+
+ if (local_explicit_props)
{
- svn_ra_session_t *ra_session;
- svn_node_kind_t kind;
- apr_pool_t *subpool = svn_pool_create(pool);
- svn_client__pathrev_t *loc;
-
- /* Get an RA session for this URL. */
- SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
- path_or_url, NULL,
- peg_revision,
- revision, ctx, pool));
-
- SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
-
- SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session, depth,
- receiver, receiver_baton, pool, subpool));
- svn_pool_destroy(subpool);
+ SVN_ERR(get_local_props(path_or_url, revision, depth, changelists,
+ local_iprops, receiver, receiver_baton, ctx,
+ result_pool, scratch_pool));
}
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c
index afb74db..2a73d8f 100644
--- a/subversion/libsvn_client/repos_diff.c
+++ b/subversion/libsvn_client/repos_diff.c
@@ -1151,6 +1151,13 @@
const char *deleted_path = svn__apr_hash_index_key(hi);
deleted_path_notify_t *dpn = svn__apr_hash_index_val(hi);
+ /* Ignore paths which are not children of b->path. (There
+ should be none due to editor ordering constraints, but
+ ra_serf drops the ball here -- see issue #3802 for
+ details.) */
+ if (! svn_relpath_skip_ancestor(b->path, deleted_path))
+ continue;
+
notify = svn_wc_create_notify(deleted_path, dpn->action, pool);
notify->kind = dpn->kind;
notify->content_state = notify->prop_state = dpn->state;
diff --git a/subversion/libsvn_client/revisions.c b/subversion/libsvn_client/revisions.c
index a2fb82a..4124633 100644
--- a/subversion/libsvn_client/revisions.c
+++ b/subversion/libsvn_client/revisions.c
@@ -96,7 +96,7 @@
scratch_pool, scratch_pool);
/* Return the same error as older code did (before and at r935091).
- At least svn_client_proplist3 promises SVN_ERR_ENTRY_NOT_FOUND. */
+ At least svn_client_proplist4 promises SVN_ERR_ENTRY_NOT_FOUND. */
if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
svn_error_clear(err);
diff --git a/subversion/libsvn_client/switch.c b/subversion/libsvn_client/switch.c
index 7384a74..a7428fb 100644
--- a/subversion/libsvn_client/switch.c
+++ b/subversion/libsvn_client/switch.c
@@ -79,6 +79,8 @@
svn_revnum_t revnum;
svn_error_t *err = SVN_NO_ERROR;
const char *diff3_cmd;
+ apr_hash_t *wcroot_iprops;
+ apr_array_header_t *inherited_props;
svn_boolean_t use_commit_times;
svn_boolean_t sleep_here = FALSE;
svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here;
@@ -218,6 +220,62 @@
svn_dirent_dirname(local_abspath, pool));
}
+ wcroot_iprops = apr_hash_make(pool);
+
+ /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch?
+ If we are switching LOCAL_ABSPATH to the root of the repository then
+ we don't need to cache inherited properties. In all other cases we
+ *might* need to cache iprops. */
+ if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0)
+ {
+ svn_boolean_t wc_root;
+ svn_boolean_t needs_iprop_cache = TRUE;
+
+ SVN_ERR(svn_wc__strictly_is_wc_root(&wc_root,
+ ctx->wc_ctx,
+ local_abspath,
+ pool));
+
+ /* Switching the WC root to anything but the repos root means
+ we need an iprop cache. */
+ if (!wc_root)
+ {
+ /* We know we are switching a subtree to something other than the
+ repos root, but if we are unswitching that subtree we don't
+ need an iprops cache. */
+ const char *target_parent_url;
+ const char *unswitched_url;
+
+ /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched
+ relative to its parent. */
+ SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx,
+ svn_dirent_dirname(local_abspath,
+ pool),
+ pool, pool));
+ unswitched_url = svn_path_url_add_component2(
+ target_parent_url,
+ svn_dirent_basename(local_abspath, pool),
+ pool);
+
+ /* If LOCAL_ABSPATH will be unswitched relative to its parent, then
+ it doesn't need an iprop cache. Note: It doesn't matter if
+ LOCAL_ABSPATH is withing a switched subtree, only if it's the
+ *root* of a switched subtree.*/
+ if (strcmp(unswitched_url, switch_loc->url) == 0)
+ needs_iprop_cache = FALSE;
+ }
+
+
+ if (needs_iprop_cache)
+ {
+ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
+ "", switch_loc->rev, pool,
+ pool));
+ apr_hash_set(wcroot_iprops, local_abspath, APR_HASH_KEY_STRING,
+ inherited_props);
+ }
+ }
+
SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
/* Fetch the switch (update) editor. If REVISION is invalid, that's
@@ -231,8 +289,8 @@
SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton,
&revnum, ctx->wc_ctx, anchor_abspath,
- target, switch_loc->url, use_commit_times,
- depth,
+ target, switch_loc->url, wcroot_iprops,
+ use_commit_times, depth,
depth_is_sticky, allow_unver_obstructions,
server_supports_depth,
diff3_cmd, preserved_exts,
diff --git a/subversion/libsvn_client/update.c b/subversion/libsvn_client/update.c
index 067ce13..c0975d2 100644
--- a/subversion/libsvn_client/update.c
+++ b/subversion/libsvn_client/update.c
@@ -201,6 +201,8 @@
svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here;
svn_boolean_t clean_checkout = FALSE;
const char *diff3_cmd;
+ apr_hash_t *wcroot_iprops;
+ svn_opt_revision_t opt_rev;
svn_ra_session_t *ra_session;
const char *preserved_exts_str;
apr_array_header_t *preserved_exts;
@@ -211,6 +213,9 @@
SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING) : NULL;
+ if (result_rev)
+ *result_rev = SVN_INVALID_REVNUM;
+
/* An unknown depth can't be sticky. */
if (depth == svn_depth_unknown)
depth_is_sticky = FALSE;
@@ -350,10 +355,18 @@
anchor_loc->url = corrected_url;
}
+ /* Resolve unspecified REVISION now, because we need to retrieve the
+ correct inherited props prior to the editor drive and we need to
+ use the same value of HEAD for both. */
+ opt_rev.kind = revision->kind;
+ opt_rev.value = revision->value;
+ if (opt_rev.kind == svn_opt_revision_unspecified)
+ opt_rev.kind = svn_opt_revision_head;
+
/* ### todo: shouldn't svn_client__get_revision_number be able
to take a URL as easily as a local path? */
SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
- local_abspath, ra_session, revision,
+ local_abspath, ra_session, &opt_rev,
pool));
SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
@@ -363,12 +376,31 @@
dfb.target_revision = revnum;
dfb.anchor_url = anchor_loc->url;
+ err = svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
+ revnum, depth, ra_session, ctx,
+ pool, pool);
+
+ /* We might be trying to update to a non-existant path-rev. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
/* Fetch the update editor. If REVISION is invalid, that's okay;
the RA driver will call editor->set_target_revision later on. */
SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
&revnum, ctx->wc_ctx, anchor_abspath,
- target, use_commit_times, depth,
- depth_is_sticky, allow_unver_obstructions,
+ target, wcroot_iprops, use_commit_times,
+ depth, depth_is_sticky,
+ allow_unver_obstructions,
adds_as_modification,
server_supports_depth,
clean_checkout,
diff --git a/subversion/libsvn_delta/compat.c b/subversion/libsvn_delta/compat.c
index e7863c9..38fcb79 100644
--- a/subversion/libsvn_delta/compat.c
+++ b/subversion/libsvn_delta/compat.c
@@ -1637,9 +1637,12 @@
change->copyfrom_path,
scratch_pool);
else
- /* ### prefix with "/" ? */
copyfrom_url = change->copyfrom_path;
+ /* Make this an FS path by prepending "/" */
+ if (copyfrom_url[0] != '/')
+ copyfrom_url = apr_pstrcat(scratch_pool, "/", copyfrom_url, NULL);
+
copyfrom_rev = change->copyfrom_rev;
}
@@ -1722,27 +1725,8 @@
/* Get a sorted list of Ev1-relative paths. */
paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool);
-
- /* We need to pass SVN_INVALID_REVNUM to the path_driver. It uses this
- revision whenever it opens directory batons. If we specified a "real"
- value, such as eb->root.base_revision, then it might use that for a
- subdirectory *incorrectly*.
-
- Specifically, log_tests 38 demonstrates that erroneous behavior when
- it attempts to open "/A" at revision 0 (not there, of course).
-
- All this said: passing SVN_INVALID_REVNUM to all of those
- open_directory() calls is not the best behavior either, but it does
- happen to magically work. (ugh)
-
- Thankfully, we're moving away from this skitchy behavior to Ev2.
-
- Final note: every other caller of svn_delta_path_driver() passes
- SVN_INVALID_REVNUM, so we can't be the only goofball.
-
- Note: dropping const on the callback_baton. */
SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths,
- apply_change, (void *)eb,
+ FALSE, apply_change, (void *)eb,
scratch_pool));
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_delta/deprecated.c b/subversion/libsvn_delta/deprecated.c
index d60a4c0..4171244 100644
--- a/subversion/libsvn_delta/deprecated.c
+++ b/subversion/libsvn_delta/deprecated.c
@@ -39,15 +39,10 @@
void *callback_baton,
apr_pool_t *scratch_pool)
{
- apr_array_header_t *sorted;
-
/* REVISION is dropped on the floor. */
- /* Construct a copy of PATHS, then sort them in a depth-first order. */
- sorted = apr_array_copy(scratch_pool, paths);
- qsort(sorted->elts, sorted->nelts, sorted->elt_size, svn_sort_compare_paths);
-
- return svn_error_trace(svn_delta_path_driver2(editor, edit_baton, sorted,
+ return svn_error_trace(svn_delta_path_driver2(editor, edit_baton, paths,
+ TRUE,
callback_func, callback_baton,
scratch_pool));
}
diff --git a/subversion/libsvn_delta/editor.c b/subversion/libsvn_delta/editor.c
index e5856eb..303fabd 100644
--- a/subversion/libsvn_delta/editor.c
+++ b/subversion/libsvn_delta/editor.c
@@ -77,23 +77,23 @@
#define END_CALLBACK(editor) ((editor)->within_callback = FALSE)
/* Marker to indicate no further changes are allowed on this node. */
-static const int marker_done;
+static const int marker_done = 0;
#define MARKER_DONE (&marker_done)
/* Marker indicating that add_* may be called for this path, or that it
can be the destination of a copy or move. For copy/move, the path
will switch to MARKER_ALLOW_ALTER, to enable further tweaks. */
-static const int marker_allow_add;
+static const int marker_allow_add = 0;
#define MARKER_ALLOW_ADD (&marker_allow_add)
/* Marker indicating that alter_* may be called for this path. */
-static const int marker_allow_alter;
+static const int marker_allow_alter = 0;
#define MARKER_ALLOW_ALTER (&marker_allow_alter)
/* Just like MARKER_DONE, but also indicates that the node was created
via add_directory(). This allows us to verify that the CHILDREN param
was comprehensive. */
-static const int marker_added_dir;
+static const int marker_added_dir = 0;
#define MARKER_ADDED_DIR (&marker_added_dir)
#define MARK_FINISHED(editor) ((editor)->finished = TRUE)
diff --git a/subversion/libsvn_delta/path_driver.c b/subversion/libsvn_delta/path_driver.c
index 649a6d8..62e703a 100644
--- a/subversion/libsvn_delta/path_driver.c
+++ b/subversion/libsvn_delta/path_driver.c
@@ -133,6 +133,7 @@
svn_delta_path_driver2(const svn_delta_editor_t *editor,
void *edit_baton,
const apr_array_header_t *paths,
+ svn_boolean_t sort_paths,
svn_delta_path_driver_cb_func_t callback_func,
void *callback_baton,
apr_pool_t *pool)
@@ -151,6 +152,16 @@
subpool = svn_pool_create(pool);
iterpool = svn_pool_create(pool);
+
+ /* sort paths if necessary */
+ if (sort_paths && paths->nelts > 1)
+ {
+ apr_array_header_t *sorted = apr_array_copy(subpool, paths);
+ qsort(sorted->elts, sorted->nelts, sorted->elt_size,
+ svn_sort_compare_paths);
+ paths = sorted;
+ }
+
item = apr_pcalloc(subpool, sizeof(*item));
/* If the root of the edit is also a target path, we want to call
diff --git a/subversion/libsvn_delta/svndiff.c b/subversion/libsvn_delta/svndiff.c
index bfa733a..bcd48de 100644
--- a/subversion/libsvn_delta/svndiff.c
+++ b/subversion/libsvn_delta/svndiff.c
@@ -186,18 +186,86 @@
}
static svn_error_t *
+send_simple_insertion_window(svn_txdelta_window_t *window,
+ struct encoder_baton *eb)
+{
+ unsigned char headers[4 + 5 * MAX_ENCODED_INT_LEN + MAX_INSTRUCTION_LEN];
+ unsigned char ibuf[MAX_INSTRUCTION_LEN];
+ unsigned char *header_current;
+ apr_size_t header_len;
+ apr_size_t ip_len, i;
+ apr_size_t len = window->new_data->len;
+
+ /* there is only one target copy op. It must span the whole window */
+ assert(window->ops[0].action_code == svn_txdelta_new);
+ assert(window->ops[0].length == window->tview_len);
+ assert(window->ops[0].offset == 0);
+
+ /* write stream header if necessary */
+ if (eb->header_done == FALSE)
+ {
+ eb->header_done = TRUE;
+ headers[0] = 'S';
+ headers[1] = 'V';
+ headers[2] = 'N';
+ headers[3] = (unsigned char)eb->version;
+ header_current = headers + 4;
+ }
+ else
+ {
+ header_current = headers;
+ }
+
+ /* Encode the action code and length. */
+ if (window->tview_len >> 6 == 0)
+ {
+ ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6));
+ ip_len = 1;
+ }
+ else
+ {
+ ibuf[0] = (0x2 << 6);
+ ip_len = encode_int(ibuf + 1, window->tview_len) - ibuf;
+ }
+
+ /* encode the window header. */
+ header_current[0] = 0; /* source offset == 0 */
+ header_current[1] = 0; /* source length == 0 */
+ header_current = encode_int(header_current + 2, window->tview_len);
+ header_current[0] = (unsigned char)ip_len; /* 1 instruction */
+ header_current = encode_int(&header_current[1], len);
+
+ /* append instructions (1 to a handful of bytes) */
+ for (i = 0; i < ip_len; ++i)
+ header_current[i] = ibuf[i];
+
+ header_len = header_current - headers + ip_len;
+
+ /* Write out the window. */
+ SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len));
+ if (len)
+ SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
window_handler(svn_txdelta_window_t *window, void *baton)
{
struct encoder_baton *eb = baton;
- apr_pool_t *pool = svn_pool_create(eb->pool);
- svn_stringbuf_t *instructions = svn_stringbuf_create_empty(pool);
- svn_stringbuf_t *i1 = svn_stringbuf_create_empty(pool);
- svn_stringbuf_t *header = svn_stringbuf_create_empty(pool);
+ apr_pool_t *pool;
+ svn_stringbuf_t *instructions;
+ svn_stringbuf_t *i1;
+ svn_stringbuf_t *header;
const svn_string_t *newdata;
unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip;
const svn_txdelta_op_t *op;
apr_size_t len;
+ /* use specialized code if there is no source */
+ if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
+ return svn_error_trace(send_simple_insertion_window(window, eb));
+
/* Make sure we write the header. */
if (eb->header_done == FALSE)
{
@@ -229,6 +297,12 @@
return svn_stream_close(output);
}
+ /* create the necessary data buffers */
+ pool = svn_pool_create(eb->pool);
+ instructions = svn_stringbuf_create_empty(pool);
+ i1 = svn_stringbuf_create_empty(pool);
+ header = svn_stringbuf_create_empty(pool);
+
/* Encode the instructions. */
for (op = window->ops; op < window->ops + window->num_ops; op++)
{
@@ -450,7 +524,7 @@
*/
static svn_error_t *
zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out,
- apr_size_t limit, svn_boolean_t copyless_allowed)
+ apr_size_t limit)
{
apr_size_t len;
const unsigned char *oldplace = in;
@@ -469,22 +543,10 @@
inLen -= (in - oldplace);
if (inLen == len)
{
- if (copyless_allowed)
- {
- /* "in" is no longer used but the memory remains allocated for
- * at least as long as "out" will be used by the caller.
- */
- out->data = (char *)in;
- out->len = len;
- out->blocksize = len; /* sic! */
- }
- else
- {
- svn_stringbuf_ensure(out, len);
- memcpy(out->data, in, len);
- out->data[len] = 0;
- out->len = len;
- }
+ svn_stringbuf_ensure(out, len);
+ memcpy(out->data, in, len);
+ out->data[len] = 0;
+ out->len = len;
return SVN_NO_ERROR;
}
@@ -661,9 +723,9 @@
/* these may in fact simply return references to insend */
SVN_ERR(zlib_decode(insend, newlen, ndout,
- SVN_DELTA_WINDOW_SIZE, TRUE));
+ SVN_DELTA_WINDOW_SIZE));
SVN_ERR(zlib_decode(data, insend - data, instout,
- MAX_INSTRUCTION_SECTION_LEN, TRUE));
+ MAX_INSTRUCTION_SECTION_LEN));
newlen = ndout->len;
data = (unsigned char *)instout->data;
@@ -1008,8 +1070,7 @@
svn_stringbuf_t *out,
int compression_level)
{
- return zlib_encode(in->data, in->len, out,
- compression_level);
+ return zlib_encode(in->data, in->len, out, compression_level);
}
svn_error_t *
@@ -1017,6 +1078,5 @@
svn_stringbuf_t *out,
apr_size_t limit)
{
- return zlib_decode((const unsigned char*)in->data, in->len, out, limit,
- FALSE);
+ return zlib_decode((const unsigned char*)in->data, in->len, out, limit);
}
diff --git a/subversion/libsvn_delta/text_delta.c b/subversion/libsvn_delta/text_delta.c
index cfc676a..beb7bb7 100644
--- a/subversion/libsvn_delta/text_delta.c
+++ b/subversion/libsvn_delta/text_delta.c
@@ -998,3 +998,42 @@
return SVN_NO_ERROR;
}
+
+svn_error_t *
+svn_txdelta_send_contents(const unsigned char *contents,
+ apr_size_t len,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_string_t new_data;
+ svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
+ svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
+ window.ops = &op;
+ window.new_data = &new_data;
+
+ /* send CONTENT as a series of max-sized windows */
+ while (len > 0)
+ {
+ /* stuff next chunk into the window */
+ window.tview_len = len < SVN_DELTA_WINDOW_SIZE
+ ? len
+ : SVN_DELTA_WINDOW_SIZE;
+ op.length = window.tview_len;
+ new_data.len = window.tview_len;
+ new_data.data = (const char*)contents;
+
+ /* update remaining */
+ contents += window.tview_len;
+ len -= window.tview_len;
+
+ /* shove it at the handler */
+ SVN_ERR((*handler)(&window, handler_baton));
+ }
+
+ /* indicate end of stream */
+ SVN_ERR((*handler)(NULL, handler_baton));
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_diff/diff_file.c b/subversion/libsvn_diff/diff_file.c
index 5550095..a25e35a 100644
--- a/subversion/libsvn_diff/diff_file.c
+++ b/subversion/libsvn_diff/diff_file.c
@@ -340,30 +340,17 @@
/* Quickly determine whether there is a eol char in CHUNK.
* (mainly copy-n-paste from eol.c#svn_eol__find_eol_start).
*/
-#if SVN_UNALIGNED_ACCESS_IS_OK
-#if APR_SIZEOF_VOIDP == 8
-# define LOWER_7BITS_SET 0x7f7f7f7f7f7f7f7f
-# define BIT_7_SET 0x8080808080808080
-# define R_MASK 0x0a0a0a0a0a0a0a0a
-# define N_MASK 0x0d0d0d0d0d0d0d0d
-#else
-# define LOWER_7BITS_SET 0x7f7f7f7f
-# define BIT_7_SET 0x80808080
-# define R_MASK 0x0a0a0a0a
-# define N_MASK 0x0d0d0d0d
-#endif
-#endif
#if SVN_UNALIGNED_ACCESS_IS_OK
static svn_boolean_t contains_eol(apr_uintptr_t chunk)
{
- apr_uintptr_t r_test = chunk ^ R_MASK;
- apr_uintptr_t n_test = chunk ^ N_MASK;
+ apr_uintptr_t r_test = chunk ^ SVN__R_MASK;
+ apr_uintptr_t n_test = chunk ^ SVN__N_MASK;
- r_test |= (r_test & LOWER_7BITS_SET) + LOWER_7BITS_SET;
- n_test |= (n_test & LOWER_7BITS_SET) + LOWER_7BITS_SET;
+ r_test |= (r_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+ n_test |= (n_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
- return (r_test & n_test & BIT_7_SET) != BIT_7_SET;
+ return (r_test & n_test & SVN__BIT_7_SET) != SVN__BIT_7_SET;
}
#endif
diff --git a/subversion/libsvn_diff/parse-diff.c b/subversion/libsvn_diff/parse-diff.c
index 1a5d2aa..c2bc1a1 100644
--- a/subversion/libsvn_diff/parse-diff.c
+++ b/subversion/libsvn_diff/parse-diff.c
@@ -595,7 +595,6 @@
original_end = hunk_text_end;
if (modified_end == 0)
modified_end = hunk_text_end;
- break;
}
SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool));
@@ -1284,8 +1283,7 @@
/* Run the state machine. */
for (i = 0; i < (sizeof(transitions) / sizeof(transitions[0])); i++)
{
- if (line->len > strlen(transitions[i].expected_input)
- && starts_with(line->data, transitions[i].expected_input)
+ if (starts_with(line->data, transitions[i].expected_input)
&& state == transitions[i].required_state)
{
SVN_ERR(transitions[i].fn(&state, line->data, *patch,
@@ -1302,19 +1300,24 @@
}
else if (state == state_git_tree_seen && line_after_tree_header_read)
{
- /* We have a valid diff header for a patch with only tree changes.
- * Rewind to the start of the line just read, so subsequent calls
- * to this function don't end up skipping the line -- it may
- * contain a patch. */
- SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line,
- scratch_pool));
- break;
+ /* git patches can contain an index line after the file mode line */
+ if (!starts_with(line->data, "index "))
+ {
+ /* We have a valid diff header for a patch with only tree changes.
+ * Rewind to the start of the line just read, so subsequent calls
+ * to this function don't end up skipping the line -- it may
+ * contain a patch. */
+ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line,
+ scratch_pool));
+ break;
+ }
}
else if (state == state_git_tree_seen)
{
line_after_tree_header_read = TRUE;
}
- else if (! valid_header_line && state != state_start)
+ else if (! valid_header_line && state != state_start
+ && !starts_with(line->data, "index "))
{
/* We've encountered an invalid diff header.
*
diff --git a/subversion/libsvn_fs/fs-loader.c b/subversion/libsvn_fs/fs-loader.c
index 70bbcf6..a21a0e8 100644
--- a/subversion/libsvn_fs/fs-loader.c
+++ b/subversion/libsvn_fs/fs-loader.c
@@ -601,7 +601,8 @@
SVN_MUTEX__WITH_LOCK(common_pool_lock,
vtable->pack_fs(fs, path, notify_func, notify_baton,
- cancel_func, cancel_baton, pool));
+ cancel_func, cancel_baton, pool,
+ common_pool));
return SVN_NO_ERROR;
}
@@ -623,6 +624,17 @@
pool));
}
+svn_error_t *
+svn_fs_freeze(svn_fs_t *fs,
+ svn_error_t *(*freeze_body)(void *baton, apr_pool_t *pool),
+ void *baton,
+ apr_pool_t *pool)
+{
+ SVN_ERR(fs->vtable->freeze(fs, freeze_body, baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
/* --- Berkeley-specific functions --- */
@@ -1192,6 +1204,27 @@
}
svn_error_t *
+svn_fs_try_process_file_contents(svn_boolean_t *success,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ /* if the FS doesn't implement this function, report a "failed" attempt */
+ if (root->vtable->try_process_file_contents == NULL)
+ {
+ *success = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(root->vtable->try_process_file_contents(
+ success,
+ root, path,
+ processor, baton, pool));
+}
+
+svn_error_t *
svn_fs_make_file(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
{
SVN_ERR(svn_fs__path_valid(path, pool));
diff --git a/subversion/libsvn_fs/fs-loader.h b/subversion/libsvn_fs/fs-loader.h
index aee76a3..de15595 100644
--- a/subversion/libsvn_fs/fs-loader.h
+++ b/subversion/libsvn_fs/fs-loader.h
@@ -107,7 +107,7 @@
svn_error_t *(*pack_fs)(svn_fs_t *fs, const char *path,
svn_fs_pack_notify_t notify_func, void *notify_baton,
svn_cancel_func_t cancel_func, void *cancel_baton,
- apr_pool_t *pool);
+ apr_pool_t *pool, apr_pool_t *common_pool);
/* Provider-specific functions should go here, even if they could go
in an object vtable, so that they are all kept together. */
@@ -202,6 +202,9 @@
svn_fs_get_locks_callback_t get_locks_func,
void *get_locks_baton,
apr_pool_t *pool);
+ svn_error_t *(*freeze)(svn_fs_t *fs,
+ svn_error_t *(*freeze_body)(void *, apr_pool_t *),
+ void *baton, apr_pool_t *pool);
svn_error_t *(*bdb_set_errcall)(svn_fs_t *fs,
void (*handler)(const char *errpfx,
char *msg));
@@ -303,6 +306,12 @@
svn_error_t *(*file_contents)(svn_stream_t **contents,
svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
+ svn_error_t *(*try_process_file_contents)(svn_boolean_t *success,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
svn_error_t *(*make_file)(svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*apply_textdelta)(svn_txdelta_window_handler_t *contents_p,
diff --git a/subversion/libsvn_fs_base/fs.c b/subversion/libsvn_fs_base/fs.c
index 63c341a..92f6717 100644
--- a/subversion/libsvn_fs_base/fs.c
+++ b/subversion/libsvn_fs_base/fs.c
@@ -471,6 +471,14 @@
return svn_io_file_close(dbconfig_file, fs->pool);
}
+static svn_error_t *
+base_bdb_freeze(svn_fs_t *fs,
+ svn_error_t *(*freeze_body)(void *, apr_pool_t *),
+ void *baton,
+ apr_pool_t *pool)
+{
+ SVN__NOT_IMPLEMENTED();
+}
/* Creating a new filesystem */
@@ -492,6 +500,7 @@
svn_fs_base__unlock,
svn_fs_base__get_lock,
svn_fs_base__get_locks,
+ base_bdb_freeze,
base_bdb_set_errcall,
};
@@ -911,7 +920,8 @@
void *notify_baton,
svn_cancel_func_t cancel,
void *cancel_baton,
- apr_pool_t *pool)
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
{
/* Packing is currently a no op for BDB. */
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_fs_base/tree.c b/subversion/libsvn_fs_base/tree.c
index 1aa93d4..cbac0aa 100644
--- a/subversion/libsvn_fs_base/tree.c
+++ b/subversion/libsvn_fs_base/tree.c
@@ -3676,7 +3676,7 @@
SVN_ERR(svn_stream_write(tb->target_stream,
tb->target_string->data,
&len));
- svn_stringbuf_set(tb->target_string, "");
+ svn_stringbuf_setempty(tb->target_string);
}
/* Is the window NULL? If so, we're done. */
@@ -5387,6 +5387,7 @@
base_file_length,
base_file_checksum,
base_file_contents,
+ NULL,
base_make_file,
base_apply_textdelta,
base_apply_text,
diff --git a/subversion/libsvn_fs_fs/caching.c b/subversion/libsvn_fs_fs/caching.c
index 9528d06..46c1ab1 100644
--- a/subversion/libsvn_fs_fs/caching.c
+++ b/subversion/libsvn_fs_fs/caching.c
@@ -24,6 +24,7 @@
#include "fs_fs.h"
#include "id.h"
#include "dag.h"
+#include "tree.h"
#include "temp_serializer.h"
#include "../libsvn_fs/fs-loader.h"
@@ -32,6 +33,8 @@
#include "svn_private_config.h"
#include "svn_hash.h"
+#include "svn_pools.h"
+
#include "private/svn_debug.h"
#include "private/svn_subr_private.h"
@@ -92,18 +95,34 @@
}
-/* Implements svn_cache__error_handler_t */
+/* Implements svn_cache__error_handler_t
+ * This variant clears the error after logging it.
+ */
static svn_error_t *
-warn_on_cache_errors(svn_error_t *err,
- void *baton,
- apr_pool_t *pool)
+warn_and_continue_on_cache_errors(svn_error_t *err,
+ void *baton,
+ apr_pool_t *pool)
{
svn_fs_t *fs = baton;
(fs->warning)(fs->warning_baton, err);
svn_error_clear(err);
+
return SVN_NO_ERROR;
}
+/* Implements svn_cache__error_handler_t
+ * This variant logs the error and passes it on to the callers.
+ */
+static svn_error_t *
+warn_and_fail_on_cache_errors(svn_error_t *err,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = baton;
+ (fs->warning)(fs->warning_baton, err);
+ return err;
+}
+
#ifdef SVN_DEBUG_CACHE_DUMP_STATS
/* Baton to be used for the dump_cache_statistics() pool cleanup function, */
struct dump_cache_baton_t
@@ -162,13 +181,12 @@
* not transaction-specific CACHE object in FS, if CACHE is not NULL.
*
* All these svn_cache__t instances shall be handled uniformly. Unless
- * NO_HANDLER is true, register an error handler that reports errors
- * as warnings for the given CACHE.
+ * ERROR_HANDLER is NULL, register it for the given CACHE in FS.
*/
static svn_error_t *
init_callbacks(svn_cache__t *cache,
svn_fs_t *fs,
- svn_boolean_t no_handler,
+ svn_cache__error_handler_t error_handler,
apr_pool_t *pool)
{
if (cache != NULL)
@@ -190,9 +208,9 @@
apr_pool_cleanup_null);
#endif
- if (! no_handler)
+ if (error_handler)
SVN_ERR(svn_cache__set_error_handler(cache,
- warn_on_cache_errors,
+ error_handler,
fs,
pool));
@@ -207,6 +225,9 @@
* MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
* otherwise.
*
+ * Unless NO_HANDLER is true, register an error handler that reports errors
+ * as warnings to the FS warning callback.
+ *
* Cache is allocated in POOL.
* */
static svn_error_t *
@@ -219,32 +240,43 @@
svn_cache__deserialize_func_t deserializer,
apr_ssize_t klen,
const char *prefix,
+ svn_fs_t *fs,
+ svn_boolean_t no_handler,
apr_pool_t *pool)
{
- if (memcache)
- {
- SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
- serializer, deserializer, klen,
- prefix, pool));
- }
- else if (membuffer)
- {
- SVN_ERR(svn_cache__create_membuffer_cache(
- cache_p, membuffer, serializer, deserializer,
- klen, prefix, FALSE, pool));
- }
- else if (pages)
- {
- SVN_ERR(svn_cache__create_inprocess(
- cache_p, serializer, deserializer, klen, pages,
- items_per_page, FALSE, prefix, pool));
- }
- else
+ svn_cache__error_handler_t error_handler = no_handler
+ ? NULL
+ : warn_and_fail_on_cache_errors;
+
+ if (memcache)
+ {
+ SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
+ serializer, deserializer, klen,
+ prefix, pool));
+ error_handler = no_handler
+ ? NULL
+ : warn_and_continue_on_cache_errors;
+ }
+ else if (membuffer)
+ {
+ SVN_ERR(svn_cache__create_membuffer_cache(
+ cache_p, membuffer, serializer, deserializer,
+ klen, prefix, FALSE, pool));
+ }
+ else if (pages)
+ {
+ SVN_ERR(svn_cache__create_inprocess(
+ cache_p, serializer, deserializer, klen, pages,
+ items_per_page, FALSE, prefix, pool));
+ }
+ else
{
*cache_p = NULL;
}
- return SVN_NO_ERROR;
+ SVN_ERR(init_callbacks(*cache_p, fs, error_handler, pool));
+
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -290,10 +322,10 @@
svn_fs_fs__deserialize_id,
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "RRI", (char *)NULL),
+ fs,
+ no_handler,
fs->pool));
- SVN_ERR(init_callbacks(ffd->rev_root_id_cache, fs, no_handler, pool));
-
/* Rough estimate: revision DAG nodes have size around 320 bytes, so
* let's put 16 on a page. */
SVN_ERR(create_cache(&(ffd->rev_node_cache),
@@ -304,9 +336,12 @@
svn_fs_fs__dag_deserialize,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "DAG", (char *)NULL),
+ fs,
+ no_handler,
fs->pool));
- SVN_ERR(init_callbacks(ffd->rev_node_cache, fs, no_handler, pool));
+ /* 1st level DAG node cache */
+ ffd->dag_node_cache = svn_fs_fs__create_dag_cache(pool);
/* Very rough estimate: 1K per directory. */
SVN_ERR(create_cache(&(ffd->dir_cache),
@@ -317,10 +352,10 @@
svn_fs_fs__deserialize_dir_entries,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "DIR", (char *)NULL),
+ fs,
+ no_handler,
fs->pool));
- SVN_ERR(init_callbacks(ffd->dir_cache, fs, no_handler, pool));
-
/* Only 16 bytes per entry (a revision number + the corresponding offset).
Since we want ~8k pages, that means 512 entries per page. */
SVN_ERR(create_cache(&(ffd->packed_offset_cache),
@@ -332,12 +367,37 @@
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "PACK-MANIFEST",
(char *)NULL),
+ fs,
+ no_handler,
fs->pool));
- SVN_ERR(init_callbacks(ffd->packed_offset_cache, fs, no_handler, pool));
+ /* initialize node revision cache, if caching has been enabled */
+ SVN_ERR(create_cache(&(ffd->node_revision_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_node_revision,
+ svn_fs_fs__deserialize_node_revision,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "NODEREVS", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
- /* initialize fulltext cache as configured */
- ffd->fulltext_cache = NULL;
+ /* initialize node change list cache, if caching has been enabled */
+ SVN_ERR(create_cache(&(ffd->changes_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_changes,
+ svn_fs_fs__deserialize_changes,
+ sizeof(svn_revnum_t),
+ apr_pstrcat(pool, prefix, "CHANGES", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ /* if enabled, cache fulltext and other derived information */
if (cache_fulltexts)
{
SVN_ERR(create_cache(&(ffd->fulltext_cache),
@@ -346,12 +406,58 @@
0, 0, /* Do not use inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
- APR_HASH_KEY_STRING,
+ sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "TEXT", (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->properties_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_properties,
+ svn_fs_fs__deserialize_properties,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "PROP",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ svn_fs_fs__serialize_mergeinfo,
+ svn_fs_fs__deserialize_mergeinfo,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "MERGEINFO",
+ (char *)NULL),
+ fs,
+ no_handler,
+ fs->pool));
+
+ SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use inprocess cache */
+ /* Values are svn_stringbuf_t */
+ NULL, NULL,
+ APR_HASH_KEY_STRING,
+ apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
+ (char *)NULL),
+ fs,
+ no_handler,
fs->pool));
}
-
- SVN_ERR(init_callbacks(ffd->fulltext_cache, fs, no_handler, pool));
+ else
+ {
+ ffd->fulltext_cache = NULL;
+ ffd->properties_cache = NULL;
+ ffd->mergeinfo_cache = NULL;
+ ffd->mergeinfo_existence_cache = NULL;
+ }
/* initialize revprop cache, if full-text caching has been enabled */
if (cache_revprops)
@@ -362,9 +468,11 @@
0, 0, /* Do not use inprocess cache */
svn_fs_fs__serialize_properties,
svn_fs_fs__deserialize_properties,
- APR_HASH_KEY_STRING,
+ sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "REVPROP",
(char *)NULL),
+ fs,
+ no_handler,
fs->pool));
}
else
@@ -372,9 +480,7 @@
ffd->revprop_cache = NULL;
}
- SVN_ERR(init_callbacks(ffd->revprop_cache, fs, no_handler, pool));
-
- /* initialize txdelta window cache, if that has been enabled */
+ /* if enabled, cache text deltas and their combinations */
if (cache_txdeltas)
{
SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
@@ -386,18 +492,10 @@
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
(char *)NULL),
+ fs,
+ no_handler,
fs->pool));
- }
- else
- {
- ffd->txdelta_window_cache = NULL;
- }
- SVN_ERR(init_callbacks(ffd->txdelta_window_cache, fs, no_handler, pool));
-
- /* initialize txdelta window cache, if that has been enabled */
- if (cache_txdeltas)
- {
SVN_ERR(create_cache(&(ffd->combined_window_cache),
NULL,
membuffer,
@@ -407,28 +505,16 @@
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
(char *)NULL),
+ fs,
+ no_handler,
fs->pool));
}
else
{
+ ffd->txdelta_window_cache = NULL;
ffd->combined_window_cache = NULL;
}
- SVN_ERR(init_callbacks(ffd->combined_window_cache, fs, no_handler, pool));
-
- /* initialize node revision cache, if caching has been enabled */
- SVN_ERR(create_cache(&(ffd->node_revision_cache),
- NULL,
- membuffer,
- 0, 0, /* Do not use inprocess cache */
- svn_fs_fs__serialize_node_revision,
- svn_fs_fs__deserialize_node_revision,
- APR_HASH_KEY_STRING,
- apr_pstrcat(pool, prefix, "NODEREVS", (char *)NULL),
- fs->pool));
-
- SVN_ERR(init_callbacks(ffd->node_revision_cache, fs, no_handler, pool));
-
return SVN_NO_ERROR;
}
@@ -522,6 +608,8 @@
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "TXNDIR",
(char *)NULL),
+ fs,
+ TRUE,
pool));
/* reset the transaction-specific cache if the pool gets cleaned up. */
diff --git a/subversion/libsvn_fs_fs/dag.c b/subversion/libsvn_fs_fs/dag.c
index d58bb53..b4b87a2 100644
--- a/subversion/libsvn_fs_fs/dag.c
+++ b/subversion/libsvn_fs_fs/dag.c
@@ -947,6 +947,24 @@
svn_error_t *
+svn_fs_fs__dag_try_process_file_contents(svn_boolean_t *success,
+ dag_node_t *node,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Go get fresh node-revisions for the nodes. */
+ SVN_ERR(get_node_revision(&noderev, node));
+
+ return svn_fs_fs__try_process_file_contents(success, node->fs,
+ noderev,
+ processor, baton, pool);
+}
+
+
+svn_error_t *
svn_fs_fs__dag_file_length(svn_filesize_t *length,
dag_node_t *file,
apr_pool_t *pool)
diff --git a/subversion/libsvn_fs_fs/dag.h b/subversion/libsvn_fs_fs/dag.h
index 691aedb..867b025 100644
--- a/subversion/libsvn_fs_fs/dag.h
+++ b/subversion/libsvn_fs_fs/dag.h
@@ -403,6 +403,19 @@
dag_node_t *file,
apr_pool_t *pool);
+/* Attempt to fetch the contents of NODE and pass it along with the BATON
+ to the PROCESSOR. Set *SUCCESS only of the data could be provided
+ and the processor had been called.
+
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__dag_try_process_file_contents(svn_boolean_t *success,
+ dag_node_t *node,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
/* Set *STREAM_P to a delta stream that will turn the contents of SOURCE into
the contents of TARGET, allocated in POOL. If SOURCE is null, the empty
diff --git a/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c
index 8a5dadc..7a6ada1 100644
--- a/subversion/libsvn_fs_fs/fs.c
+++ b/subversion/libsvn_fs_fs/fs.c
@@ -38,6 +38,7 @@
#include "tree.h"
#include "lock.h"
#include "id.h"
+#include "rep-cache.h"
#include "svn_private_config.h"
#include "private/svn_fs_util.h"
@@ -123,6 +124,46 @@
return SVN_NO_ERROR;
}
+struct fs_freeze_baton_t {
+ svn_fs_t *fs;
+ svn_error_t *(*freeze_body)(void *, apr_pool_t *);
+ void *baton;
+};
+
+static svn_error_t *
+fs_freeze_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct fs_freeze_baton_t *b = baton;
+ svn_boolean_t exists;
+
+ SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, b->fs, pool));
+ if (exists)
+ SVN_ERR(svn_fs_fs__lock_rep_cache(b->fs, pool));
+
+ SVN_ERR(b->freeze_body(b->baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fs_freeze(svn_fs_t *fs,
+ svn_error_t *(*freeze_body)(void *, apr_pool_t *),
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct fs_freeze_baton_t b;
+
+ b.fs = fs;
+ b.freeze_body = freeze_body;
+ b.baton = baton;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_fs__with_write_lock(fs, fs_freeze_body, &b, pool));
+
+ return SVN_NO_ERROR;
+}
+
/* The vtable associated with a specific open filesystem. */
@@ -143,6 +184,7 @@
svn_fs_fs__unlock,
svn_fs_fs__get_lock,
svn_fs_fs__get_locks,
+ fs_freeze,
fs_set_errcall
};
@@ -263,13 +305,14 @@
void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
- apr_pool_t *pool)
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
{
SVN_ERR(svn_fs__check_fs(fs, FALSE));
SVN_ERR(initialize_fs_struct(fs));
SVN_ERR(svn_fs_fs__open(fs, path, pool));
SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
- SVN_ERR(fs_serialized_init(fs, pool, pool));
+ SVN_ERR(fs_serialized_init(fs, common_pool, pool));
return svn_fs_fs__pack(fs, notify_func, notify_baton,
cancel_func, cancel_baton, pool);
}
diff --git a/subversion/libsvn_fs_fs/fs.h b/subversion/libsvn_fs_fs/fs.h
index b9a1371..47477eb 100644
--- a/subversion/libsvn_fs_fs/fs.h
+++ b/subversion/libsvn_fs_fs/fs.h
@@ -217,6 +217,17 @@
apr_pool_t *common_pool;
} fs_fs_shared_data_t;
+/* Data structure for the 1st level DAG node cache. */
+typedef struct fs_fs_dag_cache_t fs_fs_dag_cache_t;
+
+/* Key type for all caches that use revision + offset / counter as key. */
+typedef struct pair_cache_key_t
+{
+ svn_revnum_t revision;
+
+ apr_int64_t second;
+} pair_cache_key_t;
+
/* Private (non-shared) FSFS-specific data for each svn_fs_t object.
Any caches in here may be NULL. */
typedef struct fs_fs_data_t
@@ -241,8 +252,11 @@
(svn_fs_id_t *). (Not threadsafe.) */
svn_cache__t *rev_root_id_cache;
+ /* Caches native dag_node_t* instances and acts as a 1st level cache */
+ fs_fs_dag_cache_t *dag_node_cache;
+
/* DAG node cache for immutable nodes. Maps (revision, fspath)
- to (dag_node_t *). */
+ to (dag_node_t *). This is the 2nd level cache for DAG nodes. */
svn_cache__t *rev_node_cache;
/* A cache of the contents of immutable directories; maps from
@@ -269,6 +283,9 @@
/* Revision property cache. Maps from (rev,generation) to apr_hash_t. */
svn_cache__t *revprop_cache;
+ /* Node properties cache. Maps from rep key to apr_hash_t. */
+ svn_cache__t *properties_cache;
+
/* Pack manifest cache; a cache mapping (svn_revnum_t) shard number to
a manifest; and a manifest is a mapping from (svn_revnum_t) revision
number offset within a shard to (apr_off_t) byte-offset in the
@@ -285,6 +302,22 @@
/* Cache for node_revision_t objects; the key is (revision, id offset) */
svn_cache__t *node_revision_cache;
+ /* Cache for change lists as APR arrays of change_t * objects; the key
+ is the revision */
+ svn_cache__t *changes_cache;
+
+ /* Cache for svn_mergeinfo_t objects; the key is a combination of
+ revision, inheritance flags and path. */
+ svn_cache__t *mergeinfo_cache;
+
+ /* Cache for presence of svn_mergeinfo_t on a noderev; the key is a
+ combination of revision, inheritance flags and path; value is "1"
+ if the node has mergeinfo, "0" if it doesn't. */
+ svn_cache__t *mergeinfo_existence_cache;
+
+ /* TRUE while the we hold a lock on the write lock file. */
+ svn_boolean_t has_write_lock;
+
/* If set, there are or have been more than one concurrent transaction */
svn_boolean_t concurrent_transactions;
diff --git a/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c
index 6540000..ee950b1 100644
--- a/subversion/libsvn_fs_fs/fs_fs.c
+++ b/subversion/libsvn_fs_fs/fs_fs.c
@@ -98,7 +98,7 @@
/* Give writing processes 10 seconds to replace an existing revprop
file with a new one. After that time, we assume that the writing
process got aborted and that we have re-read revprops. */
-#define REVPROP_CHANGE_TIMEOUT 10 * 1000000
+#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
/* The following are names of atomics that will be used to communicate
* revprop updates across all processes on this machine. */
@@ -589,16 +589,29 @@
return svn_error_trace(err);
}
+/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
+ When registered with the pool holding the lock on the lock file,
+ this makes sure the flag gets reset just before we release the lock. */
+static apr_status_t
+reset_lock_flag(void *baton_void)
+{
+ fs_fs_data_t *ffd = baton_void;
+ ffd->has_write_lock = FALSE;
+ return APR_SUCCESS;
+}
+
/* Obtain a write lock on the file LOCK_FILENAME (protecting with
LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
BATON and that subpool, destroy the subpool (releasing the write
- lock) and return what BODY returned. */
+ lock) and return what BODY returned. If IS_GLOBAL_LOCK is set,
+ set the HAS_WRITE_LOCK flag while we keep the write lock. */
static svn_error_t *
with_some_lock_file(svn_fs_t *fs,
svn_error_t *(*body)(void *baton,
apr_pool_t *pool),
void *baton,
const char *lock_filename,
+ svn_boolean_t is_global_lock,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
@@ -607,6 +620,19 @@
if (!err)
{
fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (is_global_lock)
+ {
+ /* set the "got the lock" flag and register reset function */
+ apr_pool_cleanup_register(subpool,
+ ffd,
+ reset_lock_flag,
+ apr_pool_cleanup_null);
+ ffd->has_write_lock = TRUE;
+ }
+
+ /* nobody else will modify the repo state
+ => read HEAD & pack info once */
if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
SVN_ERR(update_min_unpacked_rev(fs, pool));
SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
@@ -632,6 +658,7 @@
SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
with_some_lock_file(fs, body, baton,
path_lock(fs, pool),
+ TRUE,
pool));
return SVN_NO_ERROR;
@@ -652,6 +679,7 @@
SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
with_some_lock_file(fs, body, baton,
path_txn_current_lock(fs, pool),
+ FALSE,
pool));
return SVN_NO_ERROR;
@@ -2178,16 +2206,6 @@
id_str->data, fs->path);
}
-/* Return a string that uniquely identifies the noderev with the
- * given ID, for use as a cache key.
- */
-static const char *
-get_noderev_cache_key(const svn_fs_id_t *id, apr_pool_t *pool)
-{
- const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(id, pool);
- return id_unparsed->data;
-}
-
/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
* caching has been enabled and the data can be found, IS_CACHED will
* be set to TRUE. The noderev will be allocated from POOL.
@@ -2203,13 +2221,19 @@
{
fs_fs_data_t *ffd = fs->fsap_data;
if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
- *is_cached = FALSE;
+ {
+ *is_cached = FALSE;
+ }
else
- SVN_ERR(svn_cache__get((void **) noderev_p,
- is_cached,
- ffd->node_revision_cache,
- get_noderev_cache_key(id, pool),
- pool));
+ {
+ pair_cache_key_t key = { svn_fs_fs__id_rev(id),
+ svn_fs_fs__id_offset(id) };
+ SVN_ERR(svn_cache__get((void **) noderev_p,
+ is_cached,
+ ffd->node_revision_cache,
+ &key,
+ pool));
+ }
return SVN_NO_ERROR;
}
@@ -2228,10 +2252,14 @@
fs_fs_data_t *ffd = fs->fsap_data;
if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
- return svn_cache__set(ffd->node_revision_cache,
- get_noderev_cache_key(id, scratch_pool),
- noderev_p,
- scratch_pool);
+ {
+ pair_cache_key_t key = { svn_fs_fs__id_rev(id),
+ svn_fs_fs__id_offset(id) };
+ return svn_cache__set(ffd->node_revision_cache,
+ &key,
+ noderev_p,
+ scratch_pool);
+ }
return SVN_NO_ERROR;
}
@@ -2285,6 +2313,10 @@
svn_stream_from_aprfile2(revision_file, FALSE,
pool),
pool));
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ if (svn_fs_fs__id_txn_id(id) == NULL)
+ (*noderev_p)->is_fresh_txn_root = FALSE;
+
/* The noderev is not in cache, yet. Add it, if caching has been enabled. */
return set_cached_node_revision_body(*noderev_p, fs, id, pool);
@@ -2581,6 +2613,10 @@
fs_fs_data_t *ffd = fs->fsap_data;
apr_file_t *noderev_file;
const char *txn_id = svn_fs_fs__id_txn_id(id);
+ const char *sha1 = ffd->rep_sharing_allowed && noderev->data_rep
+ ? svn_checksum_to_cstring(noderev->data_rep->sha1_checksum,
+ pool)
+ : NULL;
noderev->is_fresh_txn_root = fresh_txn_root;
@@ -2599,7 +2635,32 @@
svn_fs_fs__fs_supports_mergeinfo(fs),
pool));
- return svn_io_file_close(noderev_file, pool);
+ SVN_ERR(svn_io_file_close(noderev_file, pool));
+
+ /* if rep sharing has been enabled and the noderev has a data rep and
+ * its SHA-1 is known, store the rep struct under its SHA1. */
+ if (sha1)
+ {
+ apr_file_t *rep_file;
+ const char *file_name = svn_dirent_join(path_txn_dir(fs, txn_id, pool),
+ sha1, pool);
+ const char *rep_string = representation_string(noderev->data_rep,
+ ffd->format,
+ (noderev->kind
+ == svn_node_dir),
+ FALSE,
+ pool);
+ SVN_ERR(svn_io_file_open(&rep_file, file_name,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
+ strlen(rep_string), NULL, pool));
+
+ SVN_ERR(svn_io_file_close(rep_file, pool));
+ }
+
+ return SVN_NO_ERROR;
}
@@ -3099,6 +3160,16 @@
: SVN_NO_ERROR;
}
+/* Make sure the revprop_namespace member in FS is set. */
+static svn_error_t *
+cleanup_revprop_namespace(svn_fs_t *fs)
+{
+ const char *name = svn_dirent_join(fs->path,
+ ATOMIC_REVPROP_NAMESPACE,
+ fs->pool);
+ return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
+}
+
/* Make sure the revprop_generation member in FS is set and, if necessary,
* initialized with the latest value stored on disk.
*/
@@ -3118,7 +3189,7 @@
TRUE));
/* If the generation is at 0, we just created a new namespace
- * (it would be at least 2 otherwise). Read the lastest generation
+ * (it would be at least 2 otherwise). Read the latest generation
* from disk and if we are the first one to initialize the atomic
* (i.e. is still 0), set it to the value just gotten.
*/
@@ -3149,6 +3220,23 @@
: SVN_NO_ERROR;
}
+/* Create an error object with the given MESSAGE and pass it to the
+ WARNING member of FS. */
+static void
+log_revprop_cache_init_warning(svn_fs_t *fs,
+ svn_error_t *underlying_err,
+ const char *message)
+{
+ svn_error_t *err = svn_error_createf(SVN_ERR_FS_REPPROP_CACHE_INIT_FAILURE,
+ underlying_err,
+ message, fs->path);
+
+ if (fs->warning)
+ (fs->warning)(fs->warning_baton, err);
+
+ svn_error_clear(err);
+}
+
/* Test whether revprop cache and necessary infrastructure are
available in FS. */
static svn_boolean_t
@@ -3168,6 +3256,10 @@
* -> disable the revprop cache for good
*/
ffd->revprop_cache = NULL;
+ log_revprop_cache_init_warning(fs, NULL,
+ "Revprop caching for '%s' disabled"
+ " because it would be inefficient.");
+
return FALSE;
}
@@ -3177,8 +3269,11 @@
{
/* failure -> disable revprop cache for good */
- svn_error_clear(error);
ffd->revprop_cache = NULL;
+ log_revprop_cache_init_warning(fs, error,
+ "Revprop caching for '%s' disabled "
+ "because SHM infrastructure for revprop "
+ "caching failed to initialize.");
return FALSE;
}
@@ -3186,6 +3281,46 @@
return TRUE;
}
+/* Baton structure for revprop_generation_fixup. */
+typedef struct revprop_generation_fixup_t
+{
+ /* revprop generation to read */
+ apr_int64_t *generation;
+
+ /* containing the revprop_generation member to query */
+ fs_fs_data_t *ffd;
+} revprop_generation_upgrade_t;
+
+/* If the revprop generation has an odd value, it means the original writer
+ of the revprop got killed. We don't know whether that process as able
+ to change the revprop data but we assume that it was. Therefore, we
+ increase the generation in that case to basically invalidate everyones
+ cache content.
+ Execute this onlx while holding the write lock to the repo in baton->FFD.
+ */
+static svn_error_t *
+revprop_generation_fixup(void *void_baton,
+ apr_pool_t *pool)
+{
+ revprop_generation_upgrade_t *baton = void_baton;
+ assert(baton->ffd->has_write_lock);
+
+ /* Maybe, either the original revprop writer or some other reader has
+ already corrected / bumped the revprop generation. Thus, we need
+ to read it again. */
+ SVN_ERR(svn_named_atomic__read(baton->generation,
+ baton->ffd->revprop_generation));
+
+ /* Cause everyone to re-read revprops upon their next access, if the
+ last revprop write did not complete properly. */
+ while (*baton->generation % 2)
+ SVN_ERR(svn_named_atomic__add(baton->generation,
+ 1,
+ baton->ffd->revprop_generation));
+
+ return SVN_NO_ERROR;
+}
+
/* Read the current revprop generation and return it in *GENERATION.
Also, detect aborted / crashed writers and recover from that.
Use the access object in FS to set the shared mem values. */
@@ -3215,13 +3350,19 @@
*/
if (apr_time_now() > timeout)
{
- /* Cause everyone to re-read revprops upon their next access.
- * Keep in mind that we may not be the only one trying to do it.
+ revprop_generation_upgrade_t baton;
+ baton.generation = ¤t;
+ baton.ffd = ffd;
+
+ /* Ensure that the original writer process no longer exists by
+ * acquiring the write lock to this repository. Then, fix up
+ * the revprop generation.
*/
- while (current % 2)
- SVN_ERR(svn_named_atomic__add(¤t,
- 1,
- ffd->revprop_generation));
+ if (ffd->has_write_lock)
+ SVN_ERR(revprop_generation_fixup(&baton, pool));
+ else
+ SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
+ &baton, pool));
}
}
@@ -3362,12 +3503,10 @@
SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
if (has_revprop_cache(fs, pool))
{
- const char *key;
+ pair_cache_key_t key = {revision, generation};
fs_fs_data_t *ffd = fs->fsap_data;
- key = svn_fs_fs__combine_two_numbers(revision, generation,
- scratch_pool);
- SVN_ERR(svn_cache__set(ffd->revprop_cache, key, *properties,
+ SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
scratch_pool));
}
@@ -3508,8 +3647,8 @@
revprops->packed_revprops = svn_stringbuf_create_empty(pool);
revprops->packed_revprops->data = uncompressed->data + offset;
- revprops->packed_revprops->len = uncompressed->len - offset;
- revprops->packed_revprops->blocksize = uncompressed->blocksize - offset;
+ revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
+ revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
/* STREAM still points to the first entry in the sizes list.
* Init / construct REVPROPS members. */
@@ -3666,13 +3805,13 @@
if (has_revprop_cache(fs, pool))
{
svn_boolean_t is_cached;
- const char *key;
+ pair_cache_key_t key = { rev, 0};
SVN_ERR(read_revprop_generation(&generation, fs, pool));
- key = svn_fs_fs__combine_two_numbers(rev, generation, pool);
+ key.second = generation;
SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
- ffd->revprop_cache, key, pool));
+ ffd->revprop_cache, &key, pool));
if (is_cached)
return SVN_NO_ERROR;
}
@@ -3681,8 +3820,19 @@
* non-packed shard. If that fails, we will fall through to packed
* shard reads. */
if (!is_packed_revprop(fs, rev))
- SVN_ERR(read_non_packed_revprop(proplist_p, fs, rev, generation,
- pool));
+ {
+ svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
+ generation, pool);
+ if (err)
+ {
+ if (!APR_STATUS_IS_ENOENT(err->apr_err)
+ || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
+ }
+ }
/* if revprop packing is available and we have not read the revprops, yet,
* try reading them from a packed shard. If that fails, REV is most
@@ -4188,6 +4338,8 @@
static svn_error_t *
create_rep_state_body(struct rep_state **rep_state,
struct rep_args **rep_args,
+ apr_file_t **file_hint,
+ svn_revnum_t *rev_hint,
representation_t *rep,
svn_fs_t *fs,
apr_pool_t *pool)
@@ -4197,7 +4349,43 @@
struct rep_args *ra;
unsigned char buf[4];
- SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
+ /* If the hint is
+ * - given,
+ * - refers to a packed revision,
+ * - as does the rep we want to read, and
+ * - refers to the same pack file as the rep
+ * ...
+ */
+ if ( file_hint && rev_hint && *file_hint
+ && *rev_hint < ffd->min_unpacked_rev
+ && rep->revision < ffd->min_unpacked_rev
+ && ( (*rev_hint / ffd->max_files_per_dir)
+ == (rep->revision / ffd->max_files_per_dir)))
+ {
+ /* ... we can re-use the same, already open file object
+ */
+ apr_off_t offset;
+ SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
+
+ offset += rep->offset;
+ SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
+
+ rs->file = *file_hint;
+ }
+ else
+ {
+ /* otherwise, create a new file object
+ */
+ SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
+ }
+
+ /* remember the current file, if suggested by the caller */
+ if (file_hint)
+ *file_hint = rs->file;
+ if (rev_hint)
+ *rev_hint = rep->revision;
+
+ /* continue constructing RS and RA */
rs->window_cache = ffd->txdelta_window_cache;
rs->combined_cache = ffd->combined_window_cache;
@@ -4229,15 +4417,26 @@
/* Read the rep args for REP in filesystem FS and create a rep_state
for reading the representation. Return the rep_state in *REP_STATE
- and the rep args in *REP_ARGS, both allocated in POOL. */
+ and the rep args in *REP_ARGS, both allocated in POOL.
+
+ When reading multiple reps, i.e. a skip delta chain, you may provide
+ non-NULL FILE_HINT and REV_HINT. (If FILE_HINT is not NULL, in the first
+ call it should be a pointer to NULL.) The function will use these variables
+ to store the previous call results and tries to re-use them. This may
+ result in significant savings in I/O for packed files.
+ */
static svn_error_t *
create_rep_state(struct rep_state **rep_state,
struct rep_args **rep_args,
+ apr_file_t **file_hint,
+ svn_revnum_t *rev_hint,
representation_t *rep,
svn_fs_t *fs,
apr_pool_t *pool)
{
- svn_error_t *err = create_rep_state_body(rep_state, rep_args, rep, fs, pool);
+ svn_error_t *err = create_rep_state_body(rep_state, rep_args,
+ file_hint, rev_hint,
+ rep, fs, pool);
if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
{
fs_fs_data_t *ffd = fs->fsap_data;
@@ -4300,7 +4499,7 @@
/* The key for the fulltext cache for this rep, if there is a
fulltext cache. */
- const char *fulltext_cache_key;
+ pair_cache_key_t fulltext_cache_key;
/* The text we've been reading, if we're going to cache it. */
svn_stringbuf_t *current_fulltext;
@@ -4491,26 +4690,54 @@
ID, and representation REP.
Also, set *WINDOW_P to the base window content for *LIST, if it
could be found in cache. Otherwise, *LIST will contain the base
- representation for the whole delta chain. */
+ representation for the whole delta chain.
+ Finally, return the expanded size of the representation in
+ *EXPANDED_SIZE. It will take care of cases where only the on-disk
+ size is known. */
static svn_error_t *
build_rep_list(apr_array_header_t **list,
svn_stringbuf_t **window_p,
struct rep_state **src_state,
+ svn_filesize_t *expanded_size,
svn_fs_t *fs,
representation_t *first_rep,
apr_pool_t *pool)
{
representation_t rep;
- struct rep_state *rs;
+ struct rep_state *rs = NULL;
struct rep_args *rep_args;
svn_boolean_t is_cached = FALSE;
+ apr_file_t *last_file = NULL;
+ svn_revnum_t last_revision;
*list = apr_array_make(pool, 1, sizeof(struct rep_state *));
rep = *first_rep;
+ /* The value as stored in the data struct.
+ 0 is either for unknown length or actually zero length. */
+ *expanded_size = first_rep->expanded_size;
+
+ /* for the top-level rep, we need the rep_args */
+ SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
+ &last_revision, &rep, fs, pool));
+
+ /* Unknown size or empty representation?
+ That implies the this being the first iteration.
+ Usually size equals on-disk size, except for empty,
+ compressed representations (delta, size = 4).
+ Please note that for all non-empty deltas have
+ a 4-byte header _plus_ some data. */
+ if (*expanded_size == 0)
+ if (! rep_args->is_delta || first_rep->size != 4)
+ *expanded_size = first_rep->size;
+
while (1)
{
- SVN_ERR(create_rep_state(&rs, &rep_args, &rep, fs, pool));
+ /* fetch state, if that has not been done already */
+ if (!rs)
+ SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
+ &last_revision, &rep, fs, pool));
+
SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
if (is_cached)
{
@@ -4541,6 +4768,8 @@
rep.offset = rep_args->base_offset;
rep.size = rep_args->base_length;
rep.txn_id = NULL;
+
+ rs = NULL;
}
}
@@ -4554,7 +4783,7 @@
rep_read_get_baton(struct rep_read_baton **rb_p,
svn_fs_t *fs,
representation_t *rep,
- const char *fulltext_cache_key,
+ pair_cache_key_t fulltext_cache_key,
apr_pool_t *pool)
{
struct rep_read_baton *b;
@@ -4567,23 +4796,23 @@
b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
b->checksum_finalized = FALSE;
b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
- b->len = rep->expanded_size ? rep->expanded_size : rep->size;
+ b->len = rep->expanded_size;
b->off = 0;
b->fulltext_cache_key = fulltext_cache_key;
b->pool = svn_pool_create(pool);
b->filehandle_pool = svn_pool_create(pool);
- if (fulltext_cache_key)
+ SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
+ &b->src_state, &b->len, fs, rep,
+ b->filehandle_pool));
+
+ if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
b->current_fulltext = svn_stringbuf_create_ensure
((apr_size_t)b->len,
b->filehandle_pool);
else
b->current_fulltext = NULL;
- SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
- &b->src_state, fs, rep,
- b->filehandle_pool));
-
/* Save our output baton. */
*rb_p = b;
@@ -4602,6 +4831,10 @@
SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
+ /* RS->FILE may be shared between RS instances -> make sure we point
+ * to the right data. */
+ SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+
/* Skip windows to reach the current chunk if we aren't there yet. */
while (rs->chunk_index < this_chunk)
{
@@ -4756,8 +4989,8 @@
offset = rs->off - rs->start;
if (copy_len + offset > rb->base_window->len)
copy_len = offset < rb->base_window->len
- ? rb->base_window->len - offset
- : 0;
+ ? (apr_size_t)(rb->base_window->len - offset)
+ : 0ul;
memcpy (cur, rb->base_window->data + offset, copy_len);
}
@@ -4864,7 +5097,7 @@
if (rb->off == rb->len && rb->current_fulltext)
{
fs_fs_data_t *ffd = rb->fs->fsap_data;
- SVN_ERR(svn_cache__set(ffd->fulltext_cache, rb->fulltext_cache_key,
+ SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
rb->current_fulltext, rb->pool));
rb->current_fulltext = NULL;
}
@@ -4895,7 +5128,7 @@
else
{
fs_fs_data_t *ffd = fs->fsap_data;
- const char *fulltext_cache_key = NULL;
+ pair_cache_key_t fulltext_cache_key = {rep->revision, rep->offset};
svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
struct rep_read_baton *rb;
@@ -4904,11 +5137,8 @@
{
svn_stringbuf_t *fulltext;
svn_boolean_t is_cached;
- fulltext_cache_key = svn_fs_fs__combine_two_numbers(rep->revision,
- rep->offset,
- pool);
SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
- ffd->fulltext_cache, fulltext_cache_key,
+ ffd->fulltext_cache, &fulltext_cache_key,
pool));
if (is_cached)
{
@@ -4916,6 +5146,8 @@
return SVN_NO_ERROR;
}
}
+ else
+ fulltext_cache_key.revision = SVN_INVALID_REVNUM;
SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
@@ -4988,8 +5220,8 @@
struct rep_args *rep_args;
/* Read target's base rep if any. */
- SVN_ERR(create_rep_state(&rep_state, &rep_args, target->data_rep,
- fs, pool));
+ SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
+ target->data_rep, fs, pool));
/* If that matches source, then use this delta as is. */
if (rep_args->is_delta
&& (rep_args->is_delta_vs_empty
@@ -5024,6 +5256,70 @@
return SVN_NO_ERROR;
}
+/* Baton for cache_access_wrapper. Wraps the original parameters of
+ * svn_fs_fs__try_process_file_content().
+ */
+typedef struct cache_access_wrapper_baton_t
+{
+ svn_fs_process_contents_func_t func;
+ void* baton;
+} cache_access_wrapper_baton_t;
+
+/* Wrapper to translate between svn_fs_process_contents_func_t and
+ * svn_cache__partial_getter_func_t.
+ */
+static svn_error_t *
+cache_access_wrapper(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ cache_access_wrapper_baton_t *wrapper_baton = baton;
+
+ SVN_ERR(wrapper_baton->func((const unsigned char *)data,
+ data_len - 1, /* cache adds terminating 0 */
+ wrapper_baton->baton,
+ pool));
+
+ /* non-NULL value to signal the calling cache that all went well */
+ *out = baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ representation_t *rep = noderev->data_rep;
+ if (rep)
+ {
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t fulltext_cache_key = {rep->revision, rep->offset};
+
+ if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+ && fulltext_size_is_cachable(ffd, rep->expanded_size))
+ {
+ cache_access_wrapper_baton_t wrapper_baton = {processor, baton};
+ void *dummy = NULL;
+
+ return svn_cache__get_partial(&dummy, success,
+ ffd->fulltext_cache,
+ &fulltext_cache_key,
+ cache_access_wrapper,
+ &wrapper_baton,
+ pool);
+ }
+ }
+
+ *success = FALSE;
+ return SVN_NO_ERROR;
+}
/* Fetch the contents of a directory into ENTRIES. Values are stored
as filename to string mappings; further conversion is necessary to
@@ -5050,10 +5346,27 @@
}
else if (noderev->data_rep)
{
+ /* use a temporary pool for temp objects.
+ * Also undeltify content before parsing it. Otherwise, we could only
+ * parse it byte-by-byte.
+ */
+ apr_pool_t *text_pool = svn_pool_create(pool);
+ apr_size_t len = noderev->data_rep->expanded_size
+ ? (apr_size_t)noderev->data_rep->expanded_size
+ : (apr_size_t)noderev->data_rep->size;
+ svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
+ text->len = len;
+
/* The representation is immutable. Read it normally. */
- SVN_ERR(read_representation(&contents, fs, noderev->data_rep, pool));
- SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
+ SVN_ERR(svn_stream_read(contents, text->data, &text->len));
SVN_ERR(svn_stream_close(contents));
+
+ /* de-serialize hash */
+ contents = svn_stream_from_stringbuf(text, text_pool);
+ SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+
+ svn_pool_destroy(text_pool);
}
return SVN_NO_ERROR;
@@ -5277,11 +5590,10 @@
apr_hash_t *proplist;
svn_stream_t *stream;
- proplist = apr_hash_make(pool);
-
if (noderev->prop_rep && noderev->prop_rep->txn_id)
{
const char *filename = path_txn_node_props(fs, noderev->id, pool);
+ proplist = apr_hash_make(pool);
SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
@@ -5289,9 +5601,31 @@
}
else if (noderev->prop_rep)
{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ representation_t *rep = noderev->prop_rep;
+
+ pair_cache_key_t key = { rep->revision, rep->offset };
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ {
+ svn_boolean_t is_cached;
+ SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
+ ffd->properties_cache, &key, pool));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ proplist = apr_hash_make(pool);
SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
SVN_ERR(svn_stream_close(stream));
+
+ if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
+ SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
+ }
+ else
+ {
+ /* return an empty prop list if the node doesn't have any props */
+ proplist = apr_hash_make(pool);
}
*proplist_p = proplist;
@@ -5396,8 +5730,9 @@
apr_pool_t *pool = apr_hash_pool_get(changes);
svn_fs_path_change2_t *old_change, *new_change;
const char *path;
+ apr_size_t path_len = strlen(change->path);
- if ((old_change = apr_hash_get(changes, change->path, APR_HASH_KEY_STRING)))
+ if ((old_change = apr_hash_get(changes, change->path, path_len)))
{
/* This path already exists in the hash, so we have to merge
this change into the already existing one. */
@@ -5535,8 +5870,8 @@
re-use its value, but there is no way to fetch it. The API makes no
guarantees that this (new) key will not be retained. Thus, we (again)
copy the key into the target pool to ensure a proper lifetime. */
- path = apr_pstrdup(pool, change->path);
- apr_hash_set(changes, path, APR_HASH_KEY_STRING, new_change);
+ path = apr_pstrmemdup(pool, change->path, path_len);
+ apr_hash_set(changes, path, path_len, new_change);
/* Update the copyfrom cache, if any. */
if (copyfrom_cache)
@@ -5554,10 +5889,12 @@
}
/* We need to allocate a copy of the key in the copyfrom_pool if
* we're not doing a deletion and if it isn't already there. */
- if (copyfrom_string && ! apr_hash_get(copyfrom_cache, copyfrom_key,
- APR_HASH_KEY_STRING))
- copyfrom_key = apr_pstrdup(copyfrom_pool, copyfrom_key);
- apr_hash_set(copyfrom_cache, copyfrom_key, APR_HASH_KEY_STRING,
+ if ( copyfrom_string
+ && ( ! apr_hash_count(copyfrom_cache)
+ || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
+ copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
+
+ apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
copyfrom_string);
}
@@ -5737,7 +6074,7 @@
return SVN_NO_ERROR;
}
-/* Fetch all the changed path entries from FILE and store then in
+/* Examine all the changed path entries in CHANGES and store them in
*CHANGED_PATHS. Folding is done to remove redundant or unnecessary
*data. Store a hash of paths to copyfrom "REV PATH" strings in
COPYFROM_HASH if it is non-NULL. If PREFOLDED is true, assume that
@@ -5746,22 +6083,22 @@
remove children of replaced or deleted directories. Do all
allocations in POOL. */
static svn_error_t *
-fetch_all_changes(apr_hash_t *changed_paths,
- apr_hash_t *copyfrom_cache,
- apr_file_t *file,
- svn_boolean_t prefolded,
- apr_pool_t *pool)
+process_changes(apr_hash_t *changed_paths,
+ apr_hash_t *copyfrom_cache,
+ apr_array_header_t *changes,
+ svn_boolean_t prefolded,
+ apr_pool_t *pool)
{
- change_t *change;
apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
/* Read in the changes one by one, folding them into our local hash
as necessary. */
- SVN_ERR(read_change(&change, file, iterpool));
-
- while (change)
+ for (i = 0; i < changes->nelts; ++i)
{
+ change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
+
SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
/* Now, if our change was a deletion or replacement, we have to
@@ -5815,8 +6152,6 @@
/* Clear the per-iteration subpool. */
svn_pool_clear(iterpool);
-
- SVN_ERR(read_change(&change, file, iterpool));
}
/* Destroy the per-iteration subpool. */
@@ -5825,6 +6160,29 @@
return SVN_NO_ERROR;
}
+/* Fetch all the changes from FILE and store them in *CHANGES. Do all
+ allocations in POOL. */
+static svn_error_t *
+read_all_changes(apr_array_header_t **changes,
+ apr_file_t *file,
+ apr_pool_t *pool)
+{
+ change_t *change;
+
+ /* pre-allocate enough room for most change lists
+ (will be auto-expanded as necessary) */
+ *changes = apr_array_make(pool, 30, sizeof(change_t *));
+
+ SVN_ERR(read_change(&change, file, pool));
+ while (change)
+ {
+ APR_ARRAY_PUSH(*changes, change_t*) = change;
+ SVN_ERR(read_change(&change, file, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
svn_fs_t *fs,
@@ -5833,11 +6191,15 @@
{
apr_file_t *file;
apr_hash_t *changed_paths = apr_hash_make(pool);
+ apr_array_header_t *changes;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
- SVN_ERR(fetch_all_changes(changed_paths, NULL, file, FALSE, pool));
+ SVN_ERR(read_all_changes(&changes, file, scratch_pool));
+ SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
+ svn_pool_destroy(scratch_pool);
SVN_ERR(svn_io_file_close(file, pool));
@@ -5846,17 +6208,32 @@
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_hash_t *copyfrom_cache,
- apr_pool_t *pool)
+/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
+ * Allocate the result in POOL.
+ */
+static svn_error_t *
+get_changes(apr_array_header_t **changes,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
{
apr_off_t changes_offset;
- apr_hash_t *changed_paths;
apr_file_t *revision_file;
+ svn_boolean_t found;
+ fs_fs_data_t *ffd = fs->fsap_data;
+ /* try cache lookup first */
+
+ if (ffd->changes_cache)
+ {
+ SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
+ &rev, pool));
+ if (found)
+ return SVN_NO_ERROR;
+ }
+
+ /* read changes from revision file */
+
SVN_ERR(ensure_revision_exists(fs, rev, pool));
SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
@@ -5865,15 +6242,38 @@
rev, pool));
SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
-
- changed_paths = apr_hash_make(pool);
-
- SVN_ERR(fetch_all_changes(changed_paths, copyfrom_cache, revision_file,
- TRUE, pool));
-
- /* Close the revision file. */
+ SVN_ERR(read_all_changes(changes, revision_file, pool));
+
SVN_ERR(svn_io_file_close(revision_file, pool));
+ /* cache for future reference */
+
+ if (ffd->changes_cache)
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_hash_t *copyfrom_cache,
+ apr_pool_t *pool)
+{
+ apr_hash_t *changed_paths;
+ apr_array_header_t *changes;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
+
+ changed_paths = svn_hash__make(pool);
+
+ SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
+ TRUE, pool));
+ svn_pool_destroy(scratch_pool);
+
*changed_paths_p = changed_paths;
return SVN_NO_ERROR;
@@ -6381,25 +6781,20 @@
if (!rep || !rep->txn_id)
{
const char *unique_suffix;
+ apr_hash_t *entries;
- {
- apr_hash_t *entries;
+ /* Before we can modify the directory, we need to dump its old
+ contents into a mutable representation file. */
+ SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
+ subpool));
+ SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
+ SVN_ERR(svn_io_file_open(&file, filename,
+ APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ out = svn_stream_from_aprfile2(file, TRUE, pool);
+ SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
- svn_pool_clear(subpool);
-
- /* Before we can modify the directory, we need to dump its old
- contents into a mutable representation file. */
- SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
- subpool));
- SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
- SVN_ERR(svn_io_file_open(&file, filename,
- APR_WRITE | APR_CREATE | APR_BUFFERED,
- APR_OS_DEFAULT, pool));
- out = svn_stream_from_aprfile2(file, TRUE, pool);
- SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
-
- svn_pool_clear(subpool);
- }
+ svn_pool_clear(subpool);
/* Mark the node-rev's data rep as mutable. */
rep = apr_pcalloc(pool, sizeof(*rep));
@@ -6420,7 +6815,6 @@
}
/* if we have a directory cache for this transaction, update it */
- svn_pool_clear(subpool);
if (ffd->txn_dir_cache)
{
/* build parameters: (name, new entry) pair */
@@ -6632,11 +7026,14 @@
/* Given a node-revision NODEREV in filesystem FS, return the
representation in *REP to use as the base for a text representation
- delta. Perform temporary allocations in *POOL. */
+ delta if PROPS is FALSE. If PROPS has been set, a suitable props
+ base representation will be returned. Perform temporary allocations
+ in *POOL. */
static svn_error_t *
choose_delta_base(representation_t **rep,
svn_fs_t *fs,
node_revision_t *noderev,
+ svn_boolean_t props,
apr_pool_t *pool)
{
int count;
@@ -6685,11 +7082,52 @@
SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
base->predecessor_id, pool));
- *rep = base->data_rep;
+ /* return a suitable base representation */
+ *rep = props ? base->prop_rep : base->data_rep;
return SVN_NO_ERROR;
}
+/* Something went wrong and the pool for the rep write is being
+ cleared before we've finished writing the rep. So we need
+ to remove the rep from the protorevfile and we need to unlock
+ the protorevfile. */
+static apr_status_t
+rep_write_cleanup(void *data)
+{
+ struct rep_write_baton *b = data;
+ const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
+ svn_error_t *err;
+
+ /* Truncate and close the protorevfile. */
+ err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
+ if (err)
+ {
+ apr_status_t rc = err->apr_err;
+ svn_error_clear(err);
+ return rc;
+ }
+ err = svn_io_file_close(b->file, b->pool);
+ if (err)
+ {
+ apr_status_t rc = err->apr_err;
+ svn_error_clear(err);
+ return rc;
+ }
+
+ /* Remove our lock */
+ err = unlock_proto_rev(b->fs, txn_id, b->lockcookie, b->pool);
+ if (err)
+ {
+ apr_status_t rc = err->apr_err;
+ svn_error_clear(err);
+ return rc;
+ }
+
+ return APR_SUCCESS;
+}
+
+
/* Get a rep_write_baton and store it in *WB_P for the representation
indicated by NODEREV in filesystem FS. Perform allocations in
POOL. Only appropriate for file contents, not for props or
@@ -6732,7 +7170,7 @@
SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
/* Get the base for this delta. */
- SVN_ERR(choose_delta_base(&base_rep, fs, noderev, b->pool));
+ SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
/* Write out the rep header. */
@@ -6753,6 +7191,10 @@
/* Now determine the offset of the actual svndiff data. */
SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
+ /* Cleanup in case something goes wrong. */
+ apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
+ apr_pool_cleanup_null);
+
/* Prepare to write the svndiff data. */
svn_txdelta_to_svndiff3(&wh,
&whb,
@@ -6831,6 +7273,30 @@
}
}
+ /* look for intra-revision matches (usually data reps but not limited
+ to them in case props happen to look like some data rep)
+ */
+ if (*old_rep == NULL && rep->txn_id)
+ {
+ svn_node_kind_t kind;
+ const char *file_name
+ = svn_dirent_join(path_txn_dir(fs, rep->txn_id, pool),
+ svn_checksum_to_cstring(rep->sha1_checksum, pool),
+ pool);
+
+ /* in our txn, is there a rep file named with the wanted SHA1?
+ If so, read it and use that rep.
+ */
+ SVN_ERR(svn_io_check_path(file_name, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ svn_stringbuf_t *rep_string;
+ SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
+ SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
+ rep->txn_id, FALSE, pool));
+ }
+ }
+
/* Add information that is missing in the cached data. */
if (*old_rep)
{
@@ -6900,6 +7366,9 @@
b->noderev->data_rep = rep;
}
+ /* Remove cleanup callback. */
+ apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
+
/* Write out the new node-rev information. */
SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
b->pool));
@@ -7041,7 +7510,7 @@
*node_id = apr_pstrdup(pool, str);
- str = svn_cstring_tokenize(" ", &buf);
+ str = svn_cstring_tokenize(" \n", &buf);
if (! str)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Corrupt 'current' file"));
@@ -7151,7 +7620,8 @@
is not NULL, it will be used in addition to the on-disk cache to find
earlier reps with the same content. When such existing reps can be found,
we will truncate the one just written from the file and return the existing
- rep. Perform temporary allocations in POOL. */
+ rep. If PROPS is set, assume that we want to a props representation as
+ the base for our delta. Perform temporary allocations in POOL. */
static svn_error_t *
write_hash_delta_rep(representation_t *rep,
apr_file_t *file,
@@ -7159,6 +7629,7 @@
svn_fs_t *fs,
node_revision_t *noderev,
apr_hash_t *reps_hash,
+ svn_boolean_t props,
apr_pool_t *pool)
{
svn_txdelta_window_handler_t diff_wh;
@@ -7179,7 +7650,7 @@
int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
/* Get the base for this delta. */
- SVN_ERR(choose_delta_base(&base_rep, fs, noderev, pool));
+ SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
SVN_ERR(read_representation(&source, fs, base_rep, pool));
SVN_ERR(get_file_offset(&rep->offset, file, pool));
@@ -7414,7 +7885,7 @@
if (ffd->deltify_directories)
SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
str_entries, fs, noderev, NULL,
- pool));
+ FALSE, pool));
else
SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
fs, NULL, pool));
@@ -7453,7 +7924,7 @@
if (ffd->deltify_properties)
SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
proplist, fs, noderev, reps_hash,
- pool));
+ TRUE, pool));
else
SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
fs, reps_hash, pool));
@@ -8500,7 +8971,10 @@
svn_revnum_t youngest_rev;
svn_node_kind_t youngest_revprops_kind;
- /* First, we need to know the largest revision in the filesystem. */
+ /* Lose potentially corrupted data in temp files */
+ SVN_ERR(cleanup_revprop_namespace(fs));
+
+ /* We need to know the largest revision in the filesystem. */
SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
/* Get the expected youngest revision */
@@ -8593,7 +9067,7 @@
{
svn_boolean_t missing = TRUE;
if (!packed_revprop_available(&missing, fs, max_rev, pool))
- {
+ {
if (missing)
{
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
@@ -9396,7 +9870,7 @@
total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
{
SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
- start_rev, rev-1, sizes, total_size,
+ start_rev, rev-1, sizes, (apr_size_t)total_size,
compression_level, cancel_func, cancel_baton,
iterpool));
@@ -9422,7 +9896,7 @@
/* write the last pack file */
if (sizes->nelts != 0)
SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
- start_rev, rev-1, sizes, total_size,
+ start_rev, rev-1, sizes, (apr_size_t)total_size,
compression_level, cancel_func, cancel_baton,
iterpool));
@@ -9694,7 +10168,7 @@
struct rep_args *rep_args;
/* ### Should this be using read_rep_line() directly? */
- SVN_ERR(create_rep_state(&rs, &rep_args, rep, fs, scratch_pool));
+ SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs, scratch_pool));
return SVN_NO_ERROR;
}
@@ -9741,17 +10215,17 @@
svn_revnum_t i;
for (i = start; i <= end; i++)
{
- svn_fs_root_t *root;
+ svn_fs_root_t *root;
svn_pool_clear(iterpool);
- /* ### TODO: Make sure caches are disabled.
+ /* ### TODO: Make sure caches are disabled.
- When this code is called in the library, we want to ensure we
- use the on-disk data --- rather than some data that was read
- in the possibly-distance past and cached since. */
- SVN_ERR(svn_fs_fs__revision_root(&root, fs, i, iterpool));
- SVN_ERR(svn_fs_fs__verify_root(root, iterpool));
+ When this code is called in the library, we want to ensure we
+ use the on-disk data --- rather than some data that was read
+ in the possibly-distance past and cached since. */
+ SVN_ERR(svn_fs_fs__revision_root(&root, fs, i, iterpool));
+ SVN_ERR(svn_fs_fs__verify_root(root, iterpool));
}
}
@@ -10009,6 +10483,7 @@
const char *src_subdir_packed_shard;
svn_revnum_t revprop_rev;
apr_pool_t *iterpool;
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
/* Copy the packed shard. */
src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
@@ -10026,18 +10501,43 @@
/* Copy revprops belonging to revisions in this pack. */
src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
- iterpool = svn_pool_create(scratch_pool);
- for (revprop_rev = rev;
- revprop_rev < rev + max_files_per_dir;
- revprop_rev++)
- {
- svn_pool_clear(iterpool);
- SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
- revprop_rev, max_files_per_dir,
- iterpool));
+ if ( src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
+ || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
+ {
+ /* copy unpacked revprops rev by rev */
+ iterpool = svn_pool_create(scratch_pool);
+ for (revprop_rev = rev;
+ revprop_rev < rev + max_files_per_dir;
+ revprop_rev++)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
+ revprop_rev, max_files_per_dir,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
}
- svn_pool_destroy(iterpool);
+ else
+ {
+ /* revprop for revision 0 will never be packed */
+ if (rev == 0)
+ SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
+ 0, max_files_per_dir,
+ scratch_pool));
+
+ /* packed revprops folder */
+ packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+ rev / max_files_per_dir);
+ src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+ scratch_pool);
+ SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
+ dst_subdir, packed_shard,
+ TRUE /* copy_perms */,
+ NULL /* cancel_func */, NULL,
+ scratch_pool));
+ }
/* If necessary, update the min-unpacked rev file in the hotcopy. */
if (*dst_min_unpacked_rev < rev + max_files_per_dir)
@@ -10486,7 +10986,7 @@
iterpool));
/* After completing a full shard, update 'current'. */
- if (rev % max_files_per_dir == 0)
+ if (max_files_per_dir && rev % max_files_per_dir == 0)
SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
}
svn_pool_destroy(iterpool);
@@ -10548,14 +11048,16 @@
PATH_TXN_CURRENT, pool));
/* If a revprop generation file exists in the source filesystem,
- * force a fresh revprop caching namespace for the destination by
- * setting the generation to zero. We have no idea if the revprops
- * we copied above really belong to the currently cached generation. */
+ * reset it to zero (since this is on a different path, it will not
+ * overlap with data already in cache). Also, clean up stale files
+ * used for the named atomics implementation. */
SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
&kind, pool));
if (kind == svn_node_file)
SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
+ SVN_ERR(cleanup_revprop_namespace(dst_fs));
+
/* Hotcopied FS is complete. Stamp it with a format file. */
SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
dst_ffd->format, max_files_per_dir, TRUE, pool));
diff --git a/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h
index a1ec670..dc1e1a9 100644
--- a/subversion/libsvn_fs_fs/fs_fs.h
+++ b/subversion/libsvn_fs_fs/fs_fs.h
@@ -150,6 +150,20 @@
node_revision_t *noderev,
apr_pool_t *pool);
+/* Attempt to fetch the text representation of node-revision NODEREV as
+ seen in filesystem FS and pass it along with the BATON to the PROCESSOR.
+ Set *SUCCESS only of the data could be provided and the processing
+ had been called.
+ Use POOL for all allocations.
+ */
+svn_error_t *
+svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool);
+
/* Set *STREAM_P to a delta stream turning the contents of the file SOURCE into
the contents of the file TARGET, allocated in POOL.
If SOURCE is null, the empty string will be used. */
diff --git a/subversion/libsvn_fs_fs/rep-cache-db.sql b/subversion/libsvn_fs_fs/rep-cache-db.sql
index ea2605e..3b240fb 100644
--- a/subversion/libsvn_fs_fs/rep-cache-db.sql
+++ b/subversion/libsvn_fs_fs/rep-cache-db.sql
@@ -57,3 +57,11 @@
-- STMT_DEL_REPS_YOUNGER_THAN_REV
DELETE FROM rep_cache
WHERE revision > ?1
+
+/* An INSERT takes an SQLite reserved lock that prevents other writes
+ but doesn't block reads. The incomplete transaction means that no
+ permanent change is made to the database and the transaction is
+ removed when the database is closed. */
+-- STMT_LOCK_REP
+BEGIN TRANSACTION;
+INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0)
diff --git a/subversion/libsvn_fs_fs/rep-cache.c b/subversion/libsvn_fs_fs/rep-cache.c
index 559ff19..75c417e 100644
--- a/subversion/libsvn_fs_fs/rep-cache.c
+++ b/subversion/libsvn_fs_fs/rep-cache.c
@@ -351,3 +351,17 @@
return SVN_NO_ERROR;
}
+
+svn_error_t *
+svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
+
+ SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/rep-cache.h b/subversion/libsvn_fs_fs/rep-cache.h
index 26a3dbf..4719482 100644
--- a/subversion/libsvn_fs_fs/rep-cache.h
+++ b/subversion/libsvn_fs_fs/rep-cache.h
@@ -88,6 +88,12 @@
svn_revnum_t youngest,
apr_pool_t *pool);
+/* Start a transaction to take an SQLite reserved lock that prevents
+ other writes. */
+svn_error_t *
+svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_fs_fs/structure b/subversion/libsvn_fs_fs/structure
index 275f42c..583321d 100644
--- a/subversion/libsvn_fs_fs/structure
+++ b/subversion/libsvn_fs_fs/structure
@@ -273,12 +273,13 @@
Pack file format
- Top level: <length><packed container>
+ Top level: <packed container>
We always apply data compression to the pack file - using the
SVN_DELTA_COMPRESSION_LEVEL_NONE level if compression is disabled.
- <length> is being encoded using the variable-length svndiff integer
- format.
+ (Note that compression at SVN_DELTA_COMPRESSION_LEVEL_NONE is not
+ a no-op stream transformation although most of the data will remain
+ human readable.)
container := header '\n' (revprops)+
header := start_rev '\n' rev_count '\n' (size '\n')+
@@ -462,7 +463,8 @@
props "<rev> <offset> <length> <size> <digest>" for props rep
<rev> and <offset> give location of rep
<length> gives length of rep, sans header and trailer
- <size> gives size of expanded rep
+ <size> gives size of expanded rep; may be 0 if equal
+ to the length
<digest> gives hex MD5 digest of expanded rep
### in formats >=4, also present:
<sha1-digest> gives hex SHA1 digest of expanded rep
diff --git a/subversion/libsvn_fs_fs/temp_serializer.c b/subversion/libsvn_fs_fs/temp_serializer.c
index ba9c71c..15246bd 100644
--- a/subversion/libsvn_fs_fs/temp_serializer.c
+++ b/subversion/libsvn_fs_fs/temp_serializer.c
@@ -1079,3 +1079,284 @@
return SVN_NO_ERROR;
}
+
+/* Utility function to serialize change CHANGE_P in the given serialization
+ * CONTEXT.
+ */
+static void
+serialize_change(svn_temp_serializer__context_t *context,
+ change_t * const *change_p)
+{
+ const change_t * change = *change_p;
+ if (change == NULL)
+ return;
+
+ /* serialize the change struct itself */
+ svn_temp_serializer__push(context,
+ (const void * const *)change_p,
+ sizeof(*change));
+
+ /* serialize sub-structures */
+ svn_fs_fs__id_serialize(context, &change->noderev_id);
+
+ svn_temp_serializer__add_string(context, &change->path);
+ svn_temp_serializer__add_string(context, &change->copyfrom_path);
+
+ /* return to the caller's nesting level */
+ svn_temp_serializer__pop(context);
+}
+
+/* Utility function to serialize the CHANGE_P within the given
+ * serialization CONTEXT.
+ */
+static void
+deserialize_change(void *buffer, change_t **change_p)
+{
+ change_t * change;
+
+ /* fix-up of the pointer to the struct in question */
+ svn_temp_deserializer__resolve(buffer, (void **)change_p);
+
+ change = *change_p;
+ if (change == NULL)
+ return;
+
+ /* fix-up of sub-structures */
+ svn_fs_fs__id_deserialize(change, (svn_fs_id_t **)&change->noderev_id);
+
+ svn_temp_deserializer__resolve(change, (void **)&change->path);
+ svn_temp_deserializer__resolve(change, (void **)&change->copyfrom_path);
+}
+
+/* Auxiliary structure representing the content of a change_t array.
+ This structure is much easier to (de-)serialize than an APR array.
+ */
+typedef struct changes_data_t
+{
+ /* number of entries in the array */
+ int count;
+
+ /* reference to the changes */
+ change_t **changes;
+} changes_data_t;
+
+svn_error_t *
+svn_fs_fs__serialize_changes(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *array = in;
+ changes_data_t changes;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ int i;
+
+ /* initialize our auxiliary data structure */
+ changes.count = array->nelts;
+ changes.changes = apr_palloc(pool, sizeof(change_t*) * changes.count);
+
+ /* populate it with the array elements */
+ for (i = 0; i < changes.count; ++i)
+ changes.changes[i] = APR_ARRAY_IDX(array, i, change_t*);
+
+ /* serialize it and all its elements */
+ context = svn_temp_serializer__init(&changes,
+ sizeof(changes),
+ changes.count * 100,
+ pool);
+
+ svn_temp_serializer__push(context,
+ (const void * const *)&changes.changes,
+ changes.count * sizeof(change_t*));
+
+ for (i = 0; i < changes.count; ++i)
+ serialize_change(context, &changes.changes[i]);
+
+ svn_temp_serializer__pop(context);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_changes(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ int i;
+ changes_data_t *changes = (changes_data_t *)data;
+ apr_array_header_t *array = apr_array_make(pool, changes->count,
+ sizeof(change_t *));
+
+ /* de-serialize our auxiliary data structure */
+ svn_temp_deserializer__resolve(changes, (void**)&changes->changes);
+
+ /* de-serialize each entry and add it to the array */
+ for (i = 0; i < changes->count; ++i)
+ {
+ deserialize_change((void*)changes->changes,
+ (change_t **)&changes->changes[i]);
+ APR_ARRAY_PUSH(array, change_t *) = changes->changes[i];
+ }
+
+ /* done */
+ *out = array;
+
+ return SVN_NO_ERROR;
+}
+
+/* Auxiliary structure representing the content of a svn_mergeinfo_t hash.
+ This structure is much easier to (de-)serialize than an APR array.
+ */
+typedef struct mergeinfo_data_t
+{
+ /* number of paths in the hash */
+ unsigned count;
+
+ /* COUNT keys (paths) */
+ const char **keys;
+
+ /* COUNT keys lengths (strlen of path) */
+ apr_ssize_t *key_lengths;
+
+ /* COUNT entries, each giving the number of ranges for the key */
+ int *range_counts;
+
+ /* all ranges in a single, concatenated buffer */
+ svn_merge_range_t *ranges;
+} mergeinfo_data_t;
+
+svn_error_t *
+svn_fs_fs__serialize_mergeinfo(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ svn_mergeinfo_t mergeinfo = in;
+ mergeinfo_data_t merges;
+ svn_temp_serializer__context_t *context;
+ svn_stringbuf_t *serialized;
+ apr_hash_index_t *hi;
+ unsigned i;
+ int k;
+ apr_size_t range_count;
+
+ /* initialize our auxiliary data structure */
+ merges.count = apr_hash_count(mergeinfo);
+ merges.keys = apr_palloc(pool, sizeof(*merges.keys) * merges.count);
+ merges.key_lengths = apr_palloc(pool, sizeof(*merges.key_lengths) *
+ merges.count);
+ merges.range_counts = apr_palloc(pool, sizeof(*merges.range_counts) *
+ merges.count);
+
+ i = 0;
+ range_count = 0;
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi), ++i)
+ {
+ svn_rangelist_t *ranges;
+ apr_hash_this(hi, (const void**)&merges.keys[i],
+ &merges.key_lengths[i],
+ (void **)&ranges);
+ merges.range_counts[i] = ranges->nelts;
+ range_count += ranges->nelts;
+ }
+
+ merges.ranges = apr_palloc(pool, sizeof(*merges.ranges) * range_count);
+
+ i = 0;
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *ranges = svn__apr_hash_index_val(hi);
+ for (k = 0; k < ranges->nelts; ++k, ++i)
+ merges.ranges[i] = *APR_ARRAY_IDX(ranges, k, svn_merge_range_t*);
+ }
+
+ /* serialize it and all its elements */
+ context = svn_temp_serializer__init(&merges,
+ sizeof(merges),
+ range_count * 30,
+ pool);
+
+ /* keys array */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.keys,
+ merges.count * sizeof(*merges.keys));
+
+ for (i = 0; i < merges.count; ++i)
+ svn_temp_serializer__add_string(context, &merges.keys[i]);
+
+ svn_temp_serializer__pop(context);
+
+ /* key lengths array */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.key_lengths,
+ merges.count * sizeof(*merges.key_lengths));
+ svn_temp_serializer__pop(context);
+
+ /* range counts array */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.range_counts,
+ merges.count * sizeof(*merges.range_counts));
+ svn_temp_serializer__pop(context);
+
+ /* ranges */
+ svn_temp_serializer__push(context,
+ (const void * const *)&merges.ranges,
+ range_count * sizeof(*merges.ranges));
+ svn_temp_serializer__pop(context);
+
+ /* return the serialized result */
+ serialized = svn_temp_serializer__get(context);
+
+ *data = serialized->data;
+ *data_len = serialized->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_mergeinfo(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ unsigned i;
+ int k, n;
+ mergeinfo_data_t *merges = (mergeinfo_data_t *)data;
+ svn_mergeinfo_t mergeinfo;
+
+ /* de-serialize our auxiliary data structure */
+ svn_temp_deserializer__resolve(merges, (void**)&merges->keys);
+ svn_temp_deserializer__resolve(merges, (void**)&merges->key_lengths);
+ svn_temp_deserializer__resolve(merges, (void**)&merges->range_counts);
+ svn_temp_deserializer__resolve(merges, (void**)&merges->ranges);
+
+ /* de-serialize keys and add entries to the result */
+ n = 0;
+ mergeinfo = svn_hash__make(pool);
+ for (i = 0; i < merges->count; ++i)
+ {
+ svn_rangelist_t *ranges = apr_array_make(pool,
+ merges->range_counts[i],
+ sizeof(svn_merge_range_t*));
+ for (k = 0; k < merges->range_counts[i]; ++k, ++n)
+ APR_ARRAY_PUSH(ranges, svn_merge_range_t*) = &merges->ranges[n];
+
+ svn_temp_deserializer__resolve((void*)merges->keys,
+ (void**)&merges->keys[i]);
+ apr_hash_set(mergeinfo, merges->keys[i], merges->key_lengths[i], ranges);
+ }
+
+ /* done */
+ *out = mergeinfo;
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_fs_fs/temp_serializer.h b/subversion/libsvn_fs_fs/temp_serializer.h
index b22db2b..8429bf2 100644
--- a/subversion/libsvn_fs_fs/temp_serializer.h
+++ b/subversion/libsvn_fs_fs/temp_serializer.h
@@ -235,4 +235,42 @@
void *baton,
apr_pool_t *pool);
+/**
+ * Implements #svn_cache__serialize_func_t for an #apr_array_header_t of
+ * #change_t *.
+ */
+svn_error_t *
+svn_fs_fs__serialize_changes(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for an #apr_array_header_t of
+ * #change_t *.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_changes(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__serialize_func_t for #svn_mergeinfo_t objects.
+ */
+svn_error_t *
+svn_fs_fs__serialize_mergeinfo(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for #svn_mergeinfo_t objects.
+ */
+svn_error_t *
+svn_fs_fs__deserialize_mergeinfo(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
#endif
diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c
index 3890edf..c1f025e 100644
--- a/subversion/libsvn_fs_fs/tree.c
+++ b/subversion/libsvn_fs_fs/tree.c
@@ -44,7 +44,6 @@
#include "svn_private_config.h"
#include "svn_pools.h"
#include "svn_error.h"
-#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_mergeinfo.h"
#include "svn_fs.h"
@@ -60,6 +59,7 @@
#include "temp_serializer.h"
#include "private/svn_mergeinfo_private.h"
+#include "private/svn_subr_private.h"
#include "private/svn_fs_util.h"
#include "private/svn_fspath.h"
#include "../libsvn_fs/fs-loader.h"
@@ -121,8 +121,11 @@
} fs_txn_root_data_t;
/* Declared here to resolve the circular dependencies. */
-static svn_error_t * get_dag(dag_node_t **dag_node_p, svn_fs_root_t *root,
- const char *path, apr_pool_t *pool);
+static svn_error_t * get_dag(dag_node_t **dag_node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_boolean_t needs_lock_cache,
+ apr_pool_t *pool);
static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
dag_node_t *root_dir,
@@ -136,6 +139,254 @@
/*** Node Caching ***/
+/* 1st level cache */
+
+/* An entry in the first-level cache. REVISION and PATH form the key that
+ will ultimately be matched.
+ */
+typedef struct cache_entry_t
+{
+ /* hash value derived from PATH, REVISION.
+ Used to short-circuit failed lookups. */
+ long int hash_value;
+
+ /* revision to which the NODE belongs */
+ svn_revnum_t revision;
+
+ /* path of the NODE */
+ char *path;
+
+ /* cached value of strlen(PATH). */
+ apr_size_t path_len;
+
+ /* the node allocated in the cache's pool. NULL for empty entries. */
+ dag_node_t *node;
+} cache_entry_t;
+
+/* Number of entries in the cache. Keep this low to keep pressure on the
+ CPU caches low as well. A binary value is most efficient. If we walk
+ a directory tree, we want enough entries to store nodes for all files
+ without overwriting the nodes for the parent folder. That way, there
+ will be no unnecessary misses (except for a few random ones caused by
+ hash collision).
+
+ The actual number of instances may be higher but entries that got
+ overwritten are no longer visible.
+ */
+enum { BUCKET_COUNT = 256 };
+
+/* Each pool that has received a DAG node, will hold at least on lock on
+ our cache to ensure that the node remains valid despite being allocated
+ in the cache's pool. This is the structure to represent the lock.
+ */
+typedef struct cache_lock_t
+{
+ /* pool holding the lock */
+ apr_pool_t *pool;
+
+ /* cache being locked */
+ fs_fs_dag_cache_t *cache;
+
+ /* next lock. NULL at EOL */
+ struct cache_lock_t *next;
+
+ /* previous lock. NULL at list head. Only then this==cache->first_lock */
+ struct cache_lock_t *prev;
+} cache_lock_t;
+
+/* The actual cache structure. All nodes will be allocated in POOL.
+ When the number of INSERTIONS (i.e. objects created form that pool)
+ exceeds a certain threshold, the pool will be cleared and the cache
+ with it.
+
+ To ensure that nodes returned from this structure remain valid, the
+ cache will get locked for the lifetime of the _receiving_ pools (i.e.
+ those in which we would allocate the node if there was no cache.).
+ The cache will only be cleared FIRST_LOCK is 0.
+ */
+struct fs_fs_dag_cache_t
+{
+ /* fixed number of (possibly empty) cache entries */
+ cache_entry_t buckets[BUCKET_COUNT];
+
+ /* pool used for all node allocation */
+ apr_pool_t *pool;
+
+ /* number of entries created from POOL since the last cleanup */
+ apr_size_t insertions;
+
+ /* Property lookups etc. have a very high locality (75% re-hit).
+ Thus, remember the last hit location for optimistic lookup. */
+ apr_size_t last_hit;
+
+ /* List of receiving pools that are still alive. */
+ cache_lock_t *first_lock;
+};
+
+/* Cleanup function to be called when a receiving pool gets cleared.
+ Unlocks the cache once.
+ */
+static apr_status_t
+unlock_cache(void *baton_void)
+{
+ cache_lock_t *lock = baton_void;
+
+ /* remove lock from chain. Update the head */
+ if (lock->next)
+ lock->next->prev = lock->prev;
+ if (lock->prev)
+ lock->prev->next = lock->next;
+ else
+ lock->cache->first_lock = lock->next;
+
+ return APR_SUCCESS;
+}
+
+/* Cleanup function to be called when the cache itself gets destroyed.
+ In that case, we must unregister all unlock requests.
+ */
+static apr_status_t
+unregister_locks(void *baton_void)
+{
+ fs_fs_dag_cache_t *cache = baton_void;
+ cache_lock_t *lock;
+
+ for (lock = cache->first_lock; lock; lock = lock->next)
+ apr_pool_cleanup_kill(lock->pool,
+ lock,
+ unlock_cache);
+
+ return APR_SUCCESS;
+}
+
+fs_fs_dag_cache_t*
+svn_fs_fs__create_dag_cache(apr_pool_t *pool)
+{
+ fs_fs_dag_cache_t *result = apr_pcalloc(pool, sizeof(*result));
+ result->pool = svn_pool_create(pool);
+
+ apr_pool_cleanup_register(pool,
+ result,
+ unregister_locks,
+ apr_pool_cleanup_null);
+
+ return result;
+}
+
+/* Prevent the entries in CACHE from being destroyed, for as long as the
+ POOL lives.
+ */
+static void
+lock_cache(fs_fs_dag_cache_t* cache, apr_pool_t *pool)
+{
+ /* we only need to lock / unlock once per pool. Since we will often ask
+ for multiple nodes with the same pool, we can reduce the overhead.
+ However, if e.g. pools are being used in an alternating pattern,
+ we may lock the cache more than once for the same pool (and register
+ just as many cleanup actions).
+ */
+ cache_lock_t *lock = cache->first_lock;
+
+ /* try to find an existing lock for POOL.
+ But limit the time spent on chasing pointers. */
+ int limiter = 8;
+ while (lock && --limiter)
+ if (lock->pool == pool)
+ return;
+
+ /* create a new lock and put it at the beginning of the lock chain */
+ lock = apr_palloc(pool, sizeof(*lock));
+ lock->cache = cache;
+ lock->pool = pool;
+ lock->next = cache->first_lock;
+ lock->prev = NULL;
+
+ if (cache->first_lock)
+ cache->first_lock->prev = lock;
+ cache->first_lock = lock;
+
+ /* instruct POOL to remove the look upon cleanup */
+ apr_pool_cleanup_register(pool,
+ lock,
+ unlock_cache,
+ apr_pool_cleanup_null);
+}
+
+/* Clears the CACHE at regular intervals (destroying all cached nodes)
+ */
+static void
+auto_clear_dag_cache(fs_fs_dag_cache_t* cache)
+{
+ if (cache->first_lock == NULL && cache->insertions > BUCKET_COUNT)
+ {
+ apr_pool_clear(cache->pool);
+
+ memset(cache->buckets, 0, sizeof(cache->buckets));
+ cache->insertions = 0;
+ }
+}
+
+/* For the given REVISION and PATH, return the respective entry in CACHE.
+ If the entry is empty, its NODE member will be NULL and the caller
+ may then set it to the corresponding DAG node allocated in CACHE->POOL.
+ */
+static cache_entry_t *
+cache_lookup( fs_fs_dag_cache_t *cache
+ , svn_revnum_t revision
+ , const char *path)
+{
+ apr_size_t i, bucket_index;
+ apr_size_t path_len = strlen(path);
+ long int hash_value = revision;
+
+ /* optimistic lookup: hit the same bucket again? */
+ cache_entry_t *result = &cache->buckets[cache->last_hit];
+ if ( (result->revision == revision)
+ && (result->path_len == path_len)
+ && !memcmp(result->path, path, path_len))
+ {
+ return result;
+ }
+
+ /* need to do a full lookup. Calculate the hash value
+ (HASH_VALUE has been initialized to REVISION). */
+ for (i = 0; i + 4 <= path_len; i += 4)
+ hash_value = hash_value * 0xd1f3da69 + *(const apr_uint32_t*)(path + i);
+
+ for (; i < path_len; ++i)
+ hash_value = hash_value * 33 + path[i];
+
+ bucket_index = hash_value + (hash_value >> 16);
+ bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
+
+ /* access the corresponding bucket and remember its location */
+ result = &cache->buckets[bucket_index];
+ cache->last_hit = bucket_index;
+
+ /* if it is *NOT* a match, clear the bucket, expect the caller to fill
+ in the node and count it as an insertion */
+ if ( (result->hash_value != hash_value)
+ || (result->revision != revision)
+ || (result->path_len != path_len)
+ || memcmp(result->path, path, path_len))
+ {
+ result->hash_value = hash_value;
+ result->revision = revision;
+ if (result->path_len < path_len)
+ result->path = apr_palloc(cache->pool, path_len + 1);
+ result->path_len = path_len;
+ memcpy(result->path, path, path_len + 1);
+
+ result->node = NULL;
+
+ cache->insertions++;
+ }
+
+ return result;
+}
+
+/* 2nd level cache */
+
/* Find and return the DAG node cache for ROOT and the key that
should be used for PATH. */
static void
@@ -161,32 +412,75 @@
}
/* Return NODE for PATH from ROOT's node cache, or NULL if the node
- isn't cached; the node is copied into POOL. */
+ isn't cached; read it from the FS. *NODE remains valid until either
+ POOL or the FS gets cleared or destroyed (whichever comes first).
+
+ Since locking can be expensive and POOL may be long-living, for
+ nodes that will not need to survive the next call to this function,
+ set NEEDS_LOCK_CACHE to FALSE. */
static svn_error_t *
dag_node_cache_get(dag_node_t **node_p,
svn_fs_root_t *root,
const char *path,
+ svn_boolean_t needs_lock_cache,
apr_pool_t *pool)
{
svn_boolean_t found;
- dag_node_t *node;
+ dag_node_t *node = NULL;
svn_cache__t *cache;
const char *key;
SVN_ERR_ASSERT(*path == '/');
- locate_cache(&cache, &key, root, path, pool);
-
- SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool));
- if (found && node)
+ if (!root->is_txn_root)
{
- /* Patch up the FS, since this might have come from an old FS
- * object. */
- svn_fs_fs__dag_set_fs(node, root->fs);
- *node_p = node;
+ /* immutable DAG node. use the global caches for it */
+
+ fs_fs_data_t *ffd = root->fs->fsap_data;
+ cache_entry_t *bucket;
+
+ auto_clear_dag_cache(ffd->dag_node_cache);
+ bucket = cache_lookup(ffd->dag_node_cache, root->rev, path);
+ if (bucket->node == NULL)
+ {
+ locate_cache(&cache, &key, root, path, pool);
+ SVN_ERR(svn_cache__get((void **)&node, &found, cache, key,
+ ffd->dag_node_cache->pool));
+ if (found && node)
+ {
+ /* Patch up the FS, since this might have come from an old FS
+ * object. */
+ svn_fs_fs__dag_set_fs(node, root->fs);
+ bucket->node = node;
+ }
+ }
+ else
+ {
+ node = bucket->node;
+ }
+
+ /* if we found a node, make sure it remains valid at least as long
+ as it would when allocated in POOL. */
+ if (node && needs_lock_cache)
+ lock_cache(ffd->dag_node_cache, pool);
}
else
- *node_p = NULL;
+ {
+ /* DAG is mutable / may become invalid. Use the TXN-local cache */
+
+ locate_cache(&cache, &key, root, path, pool);
+
+ SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool));
+ if (found && node)
+ {
+ /* Patch up the FS, since this might have come from an old FS
+ * object. */
+ svn_fs_fs__dag_set_fs(node, root->fs);
+ }
+ }
+
+ *node_p = node;
+
return SVN_NO_ERROR;
}
@@ -229,7 +523,7 @@
struct fdic_baton *b = baton;
const char *item_path = key;
- if (svn_dirent_is_ancestor(b->path, item_path))
+ if (svn_fspath__skip_ancestor(b->path, item_path))
APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path);
return SVN_NO_ERROR;
@@ -505,7 +799,7 @@
SVN_ERR(svn_fs_fs__dag_get_copyroot(©root_rev, ©root_path,
child->node));
SVN_ERR(svn_fs_fs__revision_root(©root_root, fs, copyroot_rev, pool));
- SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool));
+ SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, FALSE, pool));
copyroot_id = svn_fs_fs__dag_get_id(copyroot_node);
if (svn_fs_fs__id_compare(copyroot_id, child_id) == -1)
@@ -593,7 +887,9 @@
dag_node_t *here; /* The directory we're currently looking at. */
parent_path_t *parent_path; /* The path from HERE up to the root. */
const char *rest; /* The portion of PATH we haven't traversed yet. */
- const char *canon_path = svn_fs__canonicalize_abspath(path, pool);
+ const char *canon_path = svn_fs__is_canonical_abspath(path)
+ ? path
+ : svn_fs__canonicalize_abspath(path, pool);
const char *path_so_far = "/";
apr_pool_t *iterpool = svn_pool_create(pool);
@@ -642,7 +938,8 @@
/* If we found a directory entry, follow it. First, we
check our node cache, and, failing that, we hit the DAG
layer. */
- SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, pool));
+ SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, TRUE,
+ pool));
if (cached_node)
child = cached_node;
else
@@ -771,7 +1068,8 @@
parent_path->node));
SVN_ERR(svn_fs_fs__revision_root(©root_root, root->fs,
copyroot_rev, pool));
- SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool));
+ SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path,
+ FALSE, pool));
child_id = svn_fs_fs__dag_get_id(parent_path->node);
copyroot_id = svn_fs_fs__dag_get_id(copyroot_node);
@@ -808,29 +1106,47 @@
/* Open the node identified by PATH in ROOT. Set DAG_NODE_P to the
node we find, allocated in POOL. Return the error
- SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
+ SVN_ERR_FS_NOT_FOUND if this node doesn't exist.
+
+ Since locking can be expensive and POOL may be long-living, for
+ nodes that will not need to survive the next call to this function,
+ set NEEDS_LOCK_CACHE to FALSE. */
static svn_error_t *
get_dag(dag_node_t **dag_node_p,
svn_fs_root_t *root,
const char *path,
+ svn_boolean_t needs_lock_cache,
apr_pool_t *pool)
{
parent_path_t *parent_path;
- dag_node_t *node;
+ dag_node_t *node = NULL;
- /* Canonicalize the input PATH. */
- path = svn_fs__canonicalize_abspath(path, pool);
+ /* First we look for the DAG in our cache
+ (if the path may be canonical). */
+ if (*path == '/')
+ SVN_ERR(dag_node_cache_get(&node, root, path, needs_lock_cache, pool));
- /* First we look for the DAG in our cache. */
- SVN_ERR(dag_node_cache_get(&node, root, path, pool));
if (! node)
{
- /* Call open_path with no flags, as we want this to return an error
- if the node for which we are searching doesn't exist. */
- SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool));
- node = parent_path->node;
+ /* Canonicalize the input PATH. */
+ if (!svn_fs__is_canonical_abspath(path))
+ {
+ path = svn_fs__canonicalize_abspath(path, pool);
- /* No need to cache our find -- open_path() will do that for us. */
+ /* Try again with the corrected path. */
+ SVN_ERR(dag_node_cache_get(&node, root, path, needs_lock_cache,
+ pool));
+ }
+
+ if (! node)
+ {
+ /* Call open_path with no flags, as we want this to return an
+ * error if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool));
+ node = parent_path->node;
+
+ /* No need to cache our find -- open_path() will do that for us. */
+ }
}
*dag_node_p = node;
@@ -895,7 +1211,7 @@
{
dag_node_t *node;
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
*id_p = svn_fs_fs__id_copy(svn_fs_fs__dag_get_id(node), pool);
}
return SVN_NO_ERROR;
@@ -910,7 +1226,7 @@
{
dag_node_t *node;
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
return svn_fs_fs__dag_get_revision(revision, node, pool);
}
@@ -925,7 +1241,7 @@
{
dag_node_t *node;
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, TRUE, pool));
*created_path = svn_fs_fs__dag_get_created_path(node);
return SVN_NO_ERROR;
@@ -989,7 +1305,7 @@
dag_node_t *node;
apr_hash_t *proplist;
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, node, pool));
*value_p = NULL;
if (proplist)
@@ -1012,7 +1328,7 @@
apr_hash_t *table;
dag_node_t *node;
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
SVN_ERR(svn_fs_fs__dag_get_proplist(&table, node, pool));
*table_p = table ? table : apr_hash_make(pool);
@@ -1128,8 +1444,8 @@
(SVN_ERR_FS_GENERAL, NULL,
_("Cannot compare property value between two different filesystems"));
- SVN_ERR(get_dag(&node1, root1, path1, pool));
- SVN_ERR(get_dag(&node2, root2, path2, pool));
+ SVN_ERR(get_dag(&node1, root1, path1, TRUE, pool));
+ SVN_ERR(get_dag(&node2, root2, path2, TRUE, pool));
return svn_fs_fs__dag_things_different(changed_p, NULL,
node1, node2);
}
@@ -1142,7 +1458,7 @@
static svn_error_t *
get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
{
- return get_dag(node, root, "", pool);
+ return get_dag(node, root, "/", TRUE, pool);
}
@@ -1806,7 +2122,7 @@
dag_node_t *node;
/* Get the entries for this path in the caller's pool. */
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
return svn_fs_fs__dag_dir_entries(table_p, node, pool);
}
@@ -1960,7 +2276,7 @@
_("Copy from mutable tree not currently supported"));
/* Get the NODE for FROM_PATH in FROM_ROOT.*/
- SVN_ERR(get_dag(&from_node, from_root, from_path, pool));
+ SVN_ERR(get_dag(&from_node, from_root, from_path, TRUE, pool));
/* Build up the parent path from TO_PATH in TO_ROOT. If the last
component does not exist, it's not that big a deal. We'll just
@@ -2037,7 +2353,7 @@
pool));
/* Make a record of this modification in the changes table. */
- SVN_ERR(get_dag(&new_node, to_root, to_path, pool));
+ SVN_ERR(get_dag(&new_node, to_root, to_path, TRUE, pool));
SVN_ERR(add_change(to_root->fs, txn_id, to_path,
svn_fs_fs__dag_get_id(new_node), kind, FALSE, FALSE,
svn_fs_fs__dag_node_kind(from_node),
@@ -2140,7 +2456,7 @@
{
/* There is no cached entry, look it up the old-fashioned
way. */
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, TRUE, pool));
SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(©from_rev, node));
SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©from_path, node));
}
@@ -2212,7 +2528,7 @@
dag_node_t *file;
/* First create a dag_node_t from the root/path pair. */
- SVN_ERR(get_dag(&file, root, path, pool));
+ SVN_ERR(get_dag(&file, root, path, FALSE, pool));
/* Now fetch its length */
return svn_fs_fs__dag_file_length(length_p, file, pool);
@@ -2231,7 +2547,7 @@
{
dag_node_t *file;
- SVN_ERR(get_dag(&file, root, path, pool));
+ SVN_ERR(get_dag(&file, root, path, FALSE, pool));
return svn_fs_fs__dag_file_checksum(checksum, file, kind, pool);
}
@@ -2250,7 +2566,7 @@
svn_stream_t *file_stream;
/* First create a dag_node_t from the root/path pair. */
- SVN_ERR(get_dag(&node, root, path, pool));
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
/* Then create a readable stream from the dag_node_t. */
SVN_ERR(svn_fs_fs__dag_get_contents(&file_stream, node, pool));
@@ -2262,6 +2578,25 @@
/* --- End machinery for svn_fs_file_contents() --- */
+/* --- Machinery for svn_fs_try_process_file_contents() --- */
+
+static svn_error_t *
+fs_try_process_file_contents(svn_boolean_t *success,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_fs_process_contents_func_t processor,
+ void* baton,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+
+ return svn_fs_fs__dag_try_process_file_contents(success, node,
+ processor, baton, pool);
+}
+
+/* --- End machinery for svn_fs_try_process_file_contents() --- */
+
/* --- Machinery for svn_fs_apply_textdelta() --- */
@@ -2353,7 +2688,7 @@
SVN_ERR(svn_stream_write(tb->target_stream,
tb->target_string->data,
&len));
- svn_stringbuf_set(tb->target_string, "");
+ svn_stringbuf_setempty(tb->target_string);
}
/* Is the window NULL? If so, we're done. */
@@ -2636,8 +2971,8 @@
(SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
}
- SVN_ERR(get_dag(&node1, root1, path1, pool));
- SVN_ERR(get_dag(&node2, root2, path2, pool));
+ SVN_ERR(get_dag(&node1, root1, path1, TRUE, pool));
+ SVN_ERR(get_dag(&node2, root2, path2, TRUE, pool));
return svn_fs_fs__dag_things_different(NULL, changed_p,
node1, node2);
}
@@ -2657,10 +2992,10 @@
dag_node_t *source_node, *target_node;
if (source_root && source_path)
- SVN_ERR(get_dag(&source_node, source_root, source_path, pool));
+ SVN_ERR(get_dag(&source_node, source_root, source_path, TRUE, pool));
else
source_node = NULL;
- SVN_ERR(get_dag(&target_node, target_root, target_path, pool));
+ SVN_ERR(get_dag(&target_node, target_root, target_path, TRUE, pool));
/* Create a delta stream that turns the source into the target. */
return svn_fs_fs__dag_get_file_delta_stream(stream_p, source_node,
@@ -3144,7 +3479,7 @@
SVN_ERR(svn_fs_fs__revision_root(©root_root, fs, copyroot_rev,
pool));
- SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, pool));
+ SVN_ERR(get_dag(&node, copyroot_root, copyroot_path, FALSE, pool));
copy_dst = svn_fs_fs__dag_get_created_path(node);
/* If our current path was the very destination of the copy,
@@ -3341,7 +3676,7 @@
svn_pool_clear(iterpool);
kid_path = svn_fspath__join(this_path, dirent->name, iterpool);
- SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool));
+ SVN_ERR(get_dag(&kid_dag, root, kid_path, TRUE, iterpool));
SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, kid_dag));
SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down, kid_dag));
@@ -3401,6 +3736,24 @@
return SVN_NO_ERROR;
}
+/* Return the cache key as a combination of REV_ROOT->REV, the inheritance
+ flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH. The result
+ will be allocated in POOL..
+ */
+static const char *
+mergeinfo_cache_key(const char *path,
+ svn_fs_root_t *rev_root,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *pool)
+{
+ apr_int64_t number = rev_root->rev;
+ number = number * 4
+ + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0)
+ + (adjust_inherited_mergeinfo ? 1 : 0);
+
+ return svn_fs_fs__combine_number_and_string(number, path, pool);
+}
/* Calculates the mergeinfo for PATH under REV_ROOT using inheritance
type INHERIT. Returns it in *MERGEINFO, or NULL if there is none.
@@ -3408,20 +3761,18 @@
used for temporary allocations.
*/
static svn_error_t *
-get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
- svn_fs_root_t *rev_root,
- const char *path,
- svn_mergeinfo_inheritance_t inherit,
- svn_boolean_t adjust_inherited_mergeinfo,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo,
+ svn_fs_root_t *rev_root,
+ const char *path,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
parent_path_t *parent_path, *nearest_ancestor;
apr_hash_t *proplist;
svn_string_t *mergeinfo_string;
- *mergeinfo = NULL;
-
path = svn_fs__canonicalize_abspath(path, scratch_pool);
SVN_ERR(open_path(&parent_path, rev_root, path, 0, NULL, scratch_pool));
@@ -3510,6 +3861,58 @@
return SVN_NO_ERROR;
}
+/* Caching wrapper around get_mergeinfo_for_path_internal().
+ */
+static svn_error_t *
+get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
+ svn_fs_root_t *rev_root,
+ const char *path,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = rev_root->fs->fsap_data;
+ const char *cache_key;
+ svn_boolean_t found = FALSE;
+ svn_stringbuf_t *mergeinfo_exists;
+
+ *mergeinfo = NULL;
+
+ cache_key = mergeinfo_cache_key(path, rev_root, inherit,
+ adjust_inherited_mergeinfo, scratch_pool);
+ if (ffd->mergeinfo_existence_cache)
+ {
+ SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found,
+ ffd->mergeinfo_existence_cache,
+ cache_key, result_pool));
+ if (found && mergeinfo_exists->data[0] == '1')
+ SVN_ERR(svn_cache__get((void **)mergeinfo, &found,
+ ffd->mergeinfo_cache,
+ cache_key, result_pool));
+ }
+
+ if (! found)
+ {
+ SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path,
+ inherit,
+ adjust_inherited_mergeinfo,
+ result_pool, scratch_pool));
+ if (ffd->mergeinfo_existence_cache)
+ {
+ mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0",
+ scratch_pool);
+ SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache,
+ cache_key, mergeinfo_exists, scratch_pool));
+ if (*mergeinfo)
+ SVN_ERR(svn_cache__set(ffd->mergeinfo_cache,
+ cache_key, *mergeinfo, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* Adds mergeinfo for each descendant of PATH (but not PATH itself)
under ROOT to RESULT_CATALOG. Returned values are allocated in
RESULT_POOL; temporary values in POOL. */
@@ -3523,7 +3926,7 @@
dag_node_t *this_dag;
svn_boolean_t go_down;
- SVN_ERR(get_dag(&this_dag, root, path, scratch_pool));
+ SVN_ERR(get_dag(&this_dag, root, path, TRUE, scratch_pool));
SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down,
this_dag));
if (go_down)
@@ -3550,7 +3953,7 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_mergeinfo_catalog_t result_catalog = apr_hash_make(result_pool);
+ svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool);
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
@@ -3650,6 +4053,7 @@
fs_file_length,
fs_file_checksum,
fs_file_contents,
+ fs_try_process_file_contents,
fs_make_file,
fs_apply_textdelta,
fs_apply_text,
@@ -3689,7 +4093,7 @@
root->rev = rev;
frd->root_dir = root_dir;
- frd->copyfrom_cache = apr_hash_make(root->pool);
+ frd->copyfrom_cache = svn_hash__make(root->pool);
root->fsap_data = frd;
diff --git a/subversion/libsvn_fs_fs/tree.h b/subversion/libsvn_fs_fs/tree.h
index e457cc9..34fa0a2 100644
--- a/subversion/libsvn_fs_fs/tree.h
+++ b/subversion/libsvn_fs_fs/tree.h
@@ -23,12 +23,19 @@
#ifndef SVN_LIBSVN_FS_TREE_H
#define SVN_LIBSVN_FS_TREE_H
+#include "fs.h"
+
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
+/* In POOL, create an instance of a DAG node 1st level cache.
+ The POOL will be cleared at regular intervals. */
+fs_fs_dag_cache_t*
+svn_fs_fs__create_dag_cache(apr_pool_t *pool);
+
/* Set *ROOT_P to the root directory of revision REV in filesystem FS.
Allocate the structure in POOL. */
svn_error_t *svn_fs_fs__revision_root(svn_fs_root_t **root_p, svn_fs_t *fs,
diff --git a/subversion/libsvn_fs_util/fs-util.c b/subversion/libsvn_fs_util/fs-util.c
index ccce41c..4cb0b49 100644
--- a/subversion/libsvn_fs_util/fs-util.c
+++ b/subversion/libsvn_fs_util/fs-util.c
@@ -35,6 +35,40 @@
#include "private/svn_fspath.h"
#include "../libsvn_fs/fs-loader.h"
+svn_boolean_t
+svn_fs__is_canonical_abspath(const char *path)
+{
+ size_t path_len;
+ const char *end;
+
+ /* No PATH? No problem. */
+ if (! path)
+ return TRUE;
+
+ /* Empty PATH? That's just "/". */
+ if (! *path)
+ return FALSE;
+
+ /* No leading slash? Fix that. */
+ if (*path != '/')
+ return FALSE;
+
+ /* check for trailing '/' */
+ path_len = strlen(path);
+ if (path_len == 1)
+ return TRUE;
+ if (path[path_len - 1] == '/')
+ return FALSE;
+
+ /* check for "//" */
+ end = path + path_len - 1;
+ for (; path != end; ++path)
+ if ((path[0] == '/') && (path[1] == '/'))
+ return FALSE;
+
+ return TRUE;
+}
+
const char *
svn_fs__canonicalize_abspath(const char *path, apr_pool_t *pool)
{
diff --git a/subversion/libsvn_ra/compat.c b/subversion/libsvn_ra/compat.c
index 081a8f3..9c716d9 100644
--- a/subversion/libsvn_ra/compat.c
+++ b/subversion/libsvn_ra/compat.c
@@ -871,3 +871,90 @@
*revision_deleted = log_path_deleted_baton.revision_deleted;
return SVN_NO_ERROR;
}
+
+
+svn_error_t *
+svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_array_header_t **inherited_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_root_url;
+ const char *session_url;
+ const char *parent_url;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ *inherited_props =
+ apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
+
+ /* Walk to the root of the repository getting inherited
+ props for PATH. */
+ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool));
+ SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
+ parent_url = session_url;
+
+ while (strcmp(repos_root_url, parent_url))
+ {
+ apr_hash_index_t *hi;
+ apr_hash_t *parent_props;
+ apr_hash_t *final_hash = apr_hash_make(result_pool);
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ parent_url = svn_uri_dirname(parent_url, scratch_pool);
+ SVN_ERR(svn_ra_reparent(session, parent_url, iterpool));
+ err = session->vtable->get_dir(session, NULL, NULL,
+ &parent_props, "",
+ revision, SVN_DIRENT_ALL,
+ iterpool);
+
+ /* If the user doesn't have read access to a parent path then
+ skip, but allow them to inherit from further up. */
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
+ || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ for (hi = apr_hash_first(scratch_pool, parent_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (svn_property_kind2(name) == svn_prop_regular_kind)
+ {
+ name = apr_pstrdup(result_pool, name);
+ value = svn_string_dup(value, result_pool);
+ apr_hash_set(final_hash, name, klen, value);
+ }
+ }
+
+ if (apr_hash_count(final_hash))
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = apr_pstrdup(result_pool, parent_url);
+ new_iprop->prop_hash = final_hash;
+ svn_sort__array_insert(&new_iprop, *inherited_props, 0);
+ }
+ }
+
+ /* Reparent session back to original URL. */
+ SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra/deprecated.c b/subversion/libsvn_ra/deprecated.c
index 6fd2442..9562f84 100644
--- a/subversion/libsvn_ra/deprecated.c
+++ b/subversion/libsvn_ra/deprecated.c
@@ -33,6 +33,7 @@
#include "svn_pools.h"
#include "ra_loader.h"
+#include "deprecated.h"
#include "svn_private_config.h"
@@ -209,8 +210,8 @@
const svn_delta_editor_t **editor,
void **edit_baton,
const char *log_msg,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
apr_pool_t *pool)
@@ -221,7 +222,7 @@
APR_HASH_KEY_STRING,
svn_string_create(log_msg, pool));
return svn_ra_get_commit_editor3(session, editor, edit_baton, revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
lock_tokens, keep_locks, pool);
}
@@ -417,3 +418,40 @@
SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse),
status_editor, status_baton, pool);
}
+
+svn_error_t *svn_ra_get_dir(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ SVN_ERR_ASSERT(*path != '/');
+ return session->vtable->get_dir(session, dirents, fetched_rev, props,
+ path, revision, SVN_DIRENT_ALL, pool);
+}
+
+svn_error_t *
+svn_ra_local__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_trace(svn_ra_local_init(abi_version, pool, hash));
+}
+
+svn_error_t *
+svn_ra_svn__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_trace(svn_ra_svn_init(abi_version, pool, hash));
+}
+
+svn_error_t *
+svn_ra_serf__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash)
+{
+ return svn_error_trace(svn_ra_serf_init(abi_version, pool, hash));
+}
diff --git a/subversion/libsvn_ra/deprecated.h b/subversion/libsvn_ra/deprecated.h
new file mode 100644
index 0000000..1010d5e
--- /dev/null
+++ b/subversion/libsvn_ra/deprecated.h
@@ -0,0 +1,59 @@
+/**
+ * @copyright
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+
+
+#ifndef DEPRECATED_H
+#define DEPRECATED_H
+
+#include <apr_hash.h>
+
+#include "svn_editor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Non-deprecated wrapper around svn_ra_local_init. */
+svn_error_t *
+svn_ra_local__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/* Non-deprecated wrapper around svn_ra_svn_init. */
+svn_error_t *
+svn_ra_svn__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+/* Non-deprecated wrapper around svn_ra_serf_init. */
+svn_error_t *
+svn_ra_serf__deprecated_init(int abi_version,
+ apr_pool_t *pool,
+ apr_hash_t *hash);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DEPRECATED_H */
diff --git a/subversion/libsvn_ra/editor.c b/subversion/libsvn_ra/editor.c
index 04ba196..96f5bac 100644
--- a/subversion/libsvn_ra/editor.c
+++ b/subversion/libsvn_ra/editor.c
@@ -42,6 +42,11 @@
void *cb_baton;
};
+struct fb_baton {
+ svn_ra__provide_base_cb_t provide_base_cb;
+ void *cb_baton;
+};
+
/* The shims currently want a callback that provides props for a given
REPOS_RELPATH at a given BASE_REVISION. However, the RA Ev2 interface
has a callback that provides properties for the REPOS_RELPATH from any
@@ -72,13 +77,49 @@
result_pool, scratch_pool));
}
+/* See note above regarding BASE_REVISION.
+ This also pulls down the entire contents of the file stream from the
+ RA layer and stores them in a local file, returning the path.
+*/
+static svn_error_t *
+fetch_base(const char **filename,
+ void *baton,
+ const char *repos_relpath,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct fb_baton *fbb = baton;
+ svn_revnum_t unused_revision;
+ svn_stream_t *contents;
+ svn_stream_t *file_stream;
+ const char *tmp_filename;
+
+ /* Ignored: BASE_REVISION. */
+
+ SVN_ERR(fbb->provide_base_cb(&contents, &unused_revision, fbb->cb_baton,
+ repos_relpath, result_pool, scratch_pool));
+
+ SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
+
+ *filename = apr_pstrdup(result_pool, tmp_filename);
+
+
+
+ return SVN_NO_ERROR;
+}
+
+
svn_error_t *
svn_ra__use_commit_shim(svn_editor_t **editor,
svn_ra_session_t *session,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
svn_ra__provide_base_cb_t provide_base_cb,
@@ -108,7 +149,7 @@
/* Fetch the RA provider's Ev1 commit editor. */
SVN_ERR(session->vtable->get_commit_editor(session, &deditor, &dedit_baton,
revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
lock_tokens, keep_locks,
result_pool));
@@ -173,3 +214,126 @@
return SVN_NO_ERROR;
}
+
+
+struct wrapped_replay_baton_t {
+ svn_ra__replay_revstart_ev2_callback_t revstart_func;
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func;
+ void *replay_baton;
+
+ svn_ra_session_t *session;
+
+ svn_ra__provide_base_cb_t provide_base_cb;
+ svn_ra__provide_props_cb_t provide_props_cb;
+ void *cb_baton;
+
+ /* This will be populated by the revstart wrapper. */
+ svn_editor_t *editor;
+};
+
+static svn_error_t *
+revstart_func_wrapper(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *result_pool)
+{
+ struct wrapped_replay_baton_t *wrb = replay_baton;
+ const char *repos_root;
+ const char *session_url;
+ const char *base_relpath;
+ svn_boolean_t *found_abs_paths;
+ struct fp_baton *fpb;
+ struct svn_delta__extra_baton *exb;
+
+ /* Get the Ev2 editor from the original revstart func. */
+ SVN_ERR(wrb->revstart_func(revision, wrb->replay_baton, &wrb->editor,
+ rev_props, result_pool));
+
+ /* Get or calculate the appropriate repos root and base relpath. */
+ SVN_ERR(svn_ra_get_repos_root2(wrb->session, &repos_root, result_pool));
+ SVN_ERR(svn_ra_get_session_url(wrb->session, &session_url, result_pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session_url, result_pool);
+
+ /* We will assume that when the underlying Ev1 editor is finally driven
+ by the shim, that we will not need to prepend "/" to the paths. Place
+ this on the heap because it is examined much later. Set to FALSE. */
+ found_abs_paths = apr_pcalloc(result_pool, sizeof(*found_abs_paths));
+
+ /* The PROVIDE_PROPS_CB callback does not match what the shims want.
+ Let's jigger things around a little bit here. */
+ fpb = apr_palloc(result_pool, sizeof(*fpb));
+ fpb->provide_props_cb = wrb->provide_props_cb;
+ fpb->cb_baton = wrb->cb_baton;
+
+ /* Create the extra baton. */
+ exb = apr_pcalloc(result_pool, sizeof(*exb));
+
+ /* Create the Ev1 editor from the Ev2 editor provided by the RA layer.
+
+ Note: GET_COPYSRC_KIND_CB is compatible in type/semantics with the
+ shim's FETCH_KIND_FUNC parameter. */
+ SVN_ERR(svn_delta__delta_from_editor(deditor, dedit_baton, wrb->editor,
+ NULL, NULL,
+ found_abs_paths,
+ repos_root, base_relpath,
+ fetch_props, wrb->cb_baton,
+ fetch_base, wrb->cb_baton,
+ exb, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+revfinish_func_wrapper(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct wrapped_replay_baton_t *wrb = replay_baton;
+
+ SVN_ERR(wrb->revfinish_func(revision, replay_baton, wrb->editor, rev_props,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra__use_replay_range_shim(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ void *cb_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* The basic strategy here is to wrap the callback start and finish
+ functions to appropriately return an Ev1 editor which is itself wrapped
+ from the Ev2 one the provided callbacks will give us. */
+
+ struct wrapped_replay_baton_t *wrb = apr_pcalloc(scratch_pool, sizeof(*wrb));
+
+ wrb->revstart_func = revstart_func;
+ wrb->revfinish_func = revfinish_func;
+ wrb->replay_baton = replay_baton;
+ wrb->session = session;
+
+ wrb->provide_base_cb = provide_base_cb;
+ wrb->provide_props_cb = provide_props_cb;
+ wrb->cb_baton = cb_baton;
+
+ return svn_error_trace(svn_ra_replay_range(session, start_revision,
+ end_revision, low_water_mark,
+ send_deltas,
+ revstart_func_wrapper,
+ revfinish_func_wrapper,
+ wrb, scratch_pool));
+}
diff --git a/subversion/libsvn_ra/ra_loader.c b/subversion/libsvn_ra/ra_loader.c
index 8ac4c5d..b1d0c32 100644
--- a/subversion/libsvn_ra/ra_loader.c
+++ b/subversion/libsvn_ra/ra_loader.c
@@ -43,8 +43,12 @@
#include "svn_xml.h"
#include "svn_path.h"
#include "svn_dso.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+
#include "svn_config.h"
#include "ra_loader.h"
+#include "deprecated.h"
#include "private/svn_ra_private.h"
#include "svn_private_config.h"
@@ -75,7 +79,7 @@
svn_schemes,
#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_SVN
svn_ra_svn__init,
- svn_ra_svn_init
+ svn_ra_svn__deprecated_init
#endif
},
@@ -84,7 +88,7 @@
local_schemes,
#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
svn_ra_local__init,
- svn_ra_local_init
+ svn_ra_local__deprecated_init
#endif
},
@@ -93,7 +97,7 @@
dav_schemes,
#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_SERF
svn_ra_serf__init,
- svn_ra_serf_init
+ svn_ra_serf__deprecated_init
#endif
},
@@ -466,6 +470,8 @@
/* Create the session object. */
session = apr_pcalloc(sesspool, sizeof(*session));
+ session->cancel_func = callbacks->cancel_func;
+ session->cancel_baton = callback_baton;
session->vtable = vtable;
session->pool = sesspool;
@@ -577,22 +583,6 @@
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_ra__get_fspath_relative_to_root(svn_ra_session_t *ra_session,
- const char **fspath,
- const char *url,
- apr_pool_t *pool)
-{
- const char *relpath;
-
- SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &relpath, url, pool));
- if (*relpath)
- *fspath = apr_pstrcat(pool, "/", relpath, (char *)NULL);
- else
- *fspath = "/";
- return SVN_NO_ERROR;
-}
-
svn_error_t *svn_ra_get_latest_revnum(svn_ra_session_t *session,
svn_revnum_t *latest_revnum,
apr_pool_t *pool)
@@ -719,19 +709,19 @@
const svn_delta_editor_t **editor,
void **edit_baton,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
apr_pool_t *pool)
{
- remap_commit_callback(&callback, &callback_baton,
- session, callback, callback_baton,
+ remap_commit_callback(&commit_callback, &commit_baton,
+ session, commit_callback, commit_baton,
pool);
return session->vtable->get_commit_editor(session, editor, edit_baton,
revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
lock_tokens, keep_locks, pool);
}
@@ -748,19 +738,6 @@
fetched_rev, props, pool);
}
-svn_error_t *svn_ra_get_dir(svn_ra_session_t *session,
- const char *path,
- svn_revnum_t revision,
- apr_hash_t **dirents,
- svn_revnum_t *fetched_rev,
- apr_hash_t **props,
- apr_pool_t *pool)
-{
- SVN_ERR_ASSERT(*path != '/');
- return session->vtable->get_dir(session, dirents, fetched_rev, props,
- path, revision, SVN_DIRENT_ALL, pool);
-}
-
svn_error_t *svn_ra_get_dir2(svn_ra_session_t *session,
apr_hash_t **dirents,
svn_revnum_t *fetched_rev,
@@ -1155,6 +1132,47 @@
SVN__NOT_IMPLEMENTED();
}
+static svn_error_t *
+replay_range_from_replays(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_revnum_t rev;
+
+ for (rev = start_revision ; rev <= end_revision ; rev++)
+ {
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *rev_props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_rev_proplist(session, rev, &rev_props, iterpool));
+
+ SVN_ERR(revstart_func(rev, replay_baton,
+ &editor, &edit_baton,
+ rev_props,
+ iterpool));
+ SVN_ERR(svn_ra_replay(session, rev, low_water_mark,
+ text_deltas, editor, edit_baton,
+ iterpool));
+ SVN_ERR(revfinish_func(rev, replay_baton,
+ editor, edit_baton,
+ rev_props,
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_ra_replay_range(svn_ra_session_t *session,
svn_revnum_t start_revision,
@@ -1172,40 +1190,17 @@
revstart_func, revfinish_func,
replay_baton, pool);
- if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED))
- {
- apr_pool_t *subpool = svn_pool_create(pool);
- svn_revnum_t rev;
+ if (!err || (err && (err->apr_err != SVN_ERR_RA_NOT_IMPLEMENTED)))
+ return svn_error_trace(err);
- svn_error_clear(err);
- err = SVN_NO_ERROR;
-
- for (rev = start_revision ; rev <= end_revision ; rev++)
- {
- const svn_delta_editor_t *editor;
- void *edit_baton;
- apr_hash_t *rev_props;
-
- svn_pool_clear(subpool);
-
- SVN_ERR(svn_ra_rev_proplist(session, rev, &rev_props, subpool));
-
- SVN_ERR(revstart_func(rev, replay_baton,
- &editor, &edit_baton,
- rev_props,
- subpool));
- SVN_ERR(svn_ra_replay(session, rev, low_water_mark,
- text_deltas, editor, edit_baton,
- subpool));
- SVN_ERR(revfinish_func(rev, replay_baton,
- editor, edit_baton,
- rev_props,
- subpool));
- }
- svn_pool_destroy(subpool);
- }
-
- return err;
+ svn_error_clear(err);
+ return svn_error_trace(replay_range_from_replays(session, start_revision,
+ end_revision,
+ low_water_mark,
+ text_deltas,
+ revstart_func,
+ revfinish_func,
+ replay_baton, pool));
}
svn_error_t *
@@ -1217,9 +1212,38 @@
svn_ra__replay_revstart_ev2_callback_t revstart_func,
svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
+ void *cb_baton,
apr_pool_t *scratch_pool)
{
- SVN__NOT_IMPLEMENTED();
+ if (session->vtable->replay_range_ev2 == NULL)
+ {
+ /* The specific RA layer does not have an implementation. Use our
+ default shim over the normal replay editor. */
+
+ /* This will call the Ev1 replay range handler with modified
+ callbacks. */
+ return svn_error_trace(svn_ra__use_replay_range_shim(
+ session,
+ start_revision,
+ end_revision,
+ low_water_mark,
+ send_deltas,
+ revstart_func,
+ revfinish_func,
+ replay_baton,
+ provide_base_cb,
+ provide_props_cb,
+ cb_baton,
+ scratch_pool));
+ }
+
+ return svn_error_trace(session->vtable->replay_range_ev2(
+ session, start_revision, end_revision,
+ low_water_mark, send_deltas, revstart_func,
+ revfinish_func, replay_baton, scratch_pool));
}
svn_error_t *svn_ra_has_capability(svn_ra_session_t *session,
@@ -1269,21 +1293,51 @@
return err;
}
+svn_error_t *
+svn_ra_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t iprop_capable;
+
+ /* Path must be relative. */
+ SVN_ERR_ASSERT(*path != '/');
+
+ SVN_ERR(svn_ra_has_capability(session, &iprop_capable,
+ SVN_RA_CAPABILITY_INHERITED_PROPS,
+ scratch_pool));
+
+ if (iprop_capable)
+ {
+ SVN_ERR(session->vtable->get_inherited_props(session, iprops, path,
+ revision, result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ /* Fallback for legacy servers. */
+ SVN_ERR(svn_ra__get_inherited_props_walk(session, path, revision, iprops,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_ra__get_commit_ev2(svn_editor_t **editor,
svn_ra_session_t *session,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
svn_ra__provide_base_cb_t provide_base_cb,
svn_ra__provide_props_cb_t provide_props_cb,
svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
void *cb_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
@@ -1293,22 +1347,22 @@
default shim over the normal commit editor. */
/* Remap for RA layers exposing Ev1. */
- remap_commit_callback(&callback, &callback_baton,
- session, callback, callback_baton,
+ remap_commit_callback(&commit_callback, &commit_baton,
+ session, commit_callback, commit_baton,
result_pool);
return svn_error_trace(svn_ra__use_commit_shim(
editor,
session,
revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
lock_tokens,
keep_locks,
provide_base_cb,
provide_props_cb,
get_copysrc_kind_cb,
cb_baton,
- cancel_func, cancel_baton,
+ session->cancel_func, session->cancel_baton,
result_pool, scratch_pool));
}
@@ -1319,17 +1373,16 @@
editor,
session,
revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
lock_tokens,
keep_locks,
provide_base_cb,
provide_props_cb,
get_copysrc_kind_cb,
cb_baton,
- cancel_func, cancel_baton,
+ session->cancel_func, session->cancel_baton,
result_pool, scratch_pool));
}
-
svn_error_t *
diff --git a/subversion/libsvn_ra/ra_loader.h b/subversion/libsvn_ra/ra_loader.h
index c85bfb9..45db729 100644
--- a/subversion/libsvn_ra/ra_loader.h
+++ b/subversion/libsvn_ra/ra_loader.h
@@ -299,7 +299,13 @@
/* See svn_ra__register_editor_shim_callbacks() */
svn_error_t *(*register_editor_shim_callbacks)(svn_ra_session_t *session,
svn_delta_shim_callbacks_t *callbacks);
-
+ /* See svn_ra_get_inherited_props(). */
+ svn_error_t *(*get_inherited_props)(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/* See svn_ra__get_commit_ev2() */
svn_error_t *(*get_commit_ev2)(
svn_editor_t **editor,
@@ -318,12 +324,28 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+ /* See svn_ra__replay_range_ev2() */
+ svn_error_t *(*replay_range_ev2)(
+ svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *scratch_pool);
+
} svn_ra__vtable_t;
/* The RA session object. */
struct svn_ra_session_t {
const svn_ra__vtable_t *vtable;
+ /* Cancellation handlers consumers may want to use. */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
/* Pool used to manage this session. */
apr_pool_t *pool;
@@ -473,6 +495,21 @@
apr_pool_t *pool);
+/**
+ * Fallback logic for svn_ra_get_fileX and svn_ra_get_dirX when those APIs
+ * need to find PATH's inherited properties on a legacy server that
+ * doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS capability.
+ *
+ * All arguments are as per the two aforementioned APIs.
+ */
+svn_error_t *
+svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ apr_array_header_t **inherited_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* Utility function to provide a shim between a returned Ev2 and an RA
provider's Ev1-based commit editor.
@@ -494,6 +531,24 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Utility function to provide a shim between a returned Ev2 and an RA
+ provider's Ev1-based commit editor.
+
+ See svn_ra__replay_range_ev2() for parameter semantics. */
+svn_error_t *
+svn_ra__use_replay_range_shim(svn_ra_session_t *session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra__replay_revstart_ev2_callback_t revstart_func,
+ svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
+ void *replay_baton,
+ svn_ra__provide_base_cb_t provide_base_cb,
+ svn_ra__provide_props_cb_t provide_props_cb,
+ void *cb_baton,
+ apr_pool_t *scratch_pool);
+
#ifdef __cplusplus
}
diff --git a/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c
index 1e074ad..196e01c 100644
--- a/subversion/libsvn_ra_local/ra_plugin.c
+++ b/subversion/libsvn_ra_local/ra_plugin.c
@@ -337,7 +337,7 @@
pool));
/* Build a reporter baton. */
- SVN_ERR(svn_repos_begin_report2(&rbaton,
+ SVN_ERR(svn_repos_begin_report3(&rbaton,
revision,
sess->repos,
sess->fs_path->data,
@@ -351,6 +351,8 @@
edit_baton,
NULL,
NULL,
+ 1024 * 1024, /* process-local transfers
+ should be fast */
pool));
/* Wrap the report baton given us by the repos layer with our own
@@ -752,6 +754,8 @@
revprop_table = apr_hash_copy(pool, revprop_table);
apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
svn_string_create(sess->username, pool));
+ apr_hash_set(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
+ APR_HASH_KEY_STRING, svn_string_create(SVN_VER_NUMBER, pool));
/* Get the repos commit-editor */
return svn_repos_get_commit_editor5
@@ -1038,41 +1042,70 @@
static svn_error_t *
get_node_props(apr_hash_t **props,
+ apr_array_header_t **inherited_props,
svn_ra_local__session_baton_t *sess,
svn_fs_root_t *root,
const char *path,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_revnum_t cmt_rev;
const char *cmt_date, *cmt_author;
/* Create a hash with props attached to the fs node. */
- SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
+ if (props)
+ {
+ SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool));
+ }
+
+ /* Turn FS-path keys into URLs. */
+ if (inherited_props)
+ {
+ int i;
+
+ SVN_ERR(svn_repos_fs_get_inherited_props(inherited_props, root, path,
+ NULL, NULL,
+ result_pool, scratch_pool));
+
+ for (i = 0; i < (*inherited_props)->nelts; i++)
+ {
+ svn_prop_inherited_item_t *i_props =
+ APR_ARRAY_IDX(*inherited_props, i, svn_prop_inherited_item_t *);
+ i_props->path_or_url = svn_path_url_add_component2(
+ sess->repos_url, i_props->path_or_url, result_pool);
+ }
+ }
/* Now add some non-tweakable metadata to the hash as well... */
- /* The so-called 'entryprops' with info about CR & friends. */
- SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
- &cmt_author, root, path, pool));
+ if (props)
+ {
+ /* The so-called 'entryprops' with info about CR & friends. */
+ SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
+ &cmt_author, root, path,
+ scratch_pool));
- apr_hash_set(*props,
- SVN_PROP_ENTRY_COMMITTED_REV,
- APR_HASH_KEY_STRING,
- svn_string_createf(pool, "%ld", cmt_rev));
- apr_hash_set(*props,
- SVN_PROP_ENTRY_COMMITTED_DATE,
- APR_HASH_KEY_STRING,
- cmt_date ? svn_string_create(cmt_date, pool) : NULL);
- apr_hash_set(*props,
- SVN_PROP_ENTRY_LAST_AUTHOR,
- APR_HASH_KEY_STRING,
- cmt_author ? svn_string_create(cmt_author, pool) : NULL);
- apr_hash_set(*props,
- SVN_PROP_ENTRY_UUID,
- APR_HASH_KEY_STRING,
- svn_string_create(sess->uuid, pool));
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_COMMITTED_REV,
+ APR_HASH_KEY_STRING,
+ svn_string_createf(result_pool, "%ld", cmt_rev));
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_COMMITTED_DATE,
+ APR_HASH_KEY_STRING,
+ cmt_date ? svn_string_create(cmt_date,
+ result_pool) : NULL);
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_LAST_AUTHOR,
+ APR_HASH_KEY_STRING,
+ cmt_author ? svn_string_create(cmt_author,
+ result_pool) : NULL);
+ apr_hash_set(*props,
+ SVN_PROP_ENTRY_UUID,
+ APR_HASH_KEY_STRING,
+ svn_string_create(sess->uuid, result_pool));
- /* We have no 'wcprops' in ra_local, but might someday. */
+ /* We have no 'wcprops' in ra_local, but might someday. */
+ }
return SVN_NO_ERROR;
}
@@ -1144,7 +1177,7 @@
/* Handle props if requested. */
if (props)
- SVN_ERR(get_node_props(props, sess, root, abs_path, pool));
+ SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool));
return SVN_NO_ERROR;
}
@@ -1255,7 +1288,7 @@
/* Handle props if requested. */
if (props)
- SVN_ERR(get_node_props(props, sess, root, abs_path, pool));
+ SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool));
return SVN_NO_ERROR;
}
@@ -1489,7 +1522,8 @@
|| strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
|| strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
|| strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
- || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0)
+ || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0)
{
*has = TRUE;
}
@@ -1533,6 +1567,44 @@
}
static svn_error_t *
+svn_ra_local__get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t youngest_rev;
+ svn_ra_local__session_baton_t *sess = session->priv;
+ const char *abs_path = svn_fspath__join(sess->fs_path->data, path,
+ scratch_pool);
+ svn_node_kind_t node_kind;
+
+ /* Open the revision's root. */
+ if (! SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, scratch_pool));
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool));
+ }
+
+ SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool));
+ if (node_kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), abs_path);
+ }
+
+ return svn_error_trace(get_node_props(NULL, iprops, sess, root, abs_path,
+ result_pool, scratch_pool));
+}
+
+static svn_error_t *
svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
svn_delta_shim_callbacks_t *callbacks)
{
@@ -1645,6 +1717,7 @@
svn_ra_local__replay_range,
svn_ra_local__get_deleted_rev,
svn_ra_local__register_editor_shim_callbacks,
+ svn_ra_local__get_inherited_props,
svn_ra_local__get_commit_ev2
};
diff --git a/subversion/libsvn_ra_local/split_url.c b/subversion/libsvn_ra_local/split_url.c
index 639e0ca..62cb5f2 100644
--- a/subversion/libsvn_ra_local/split_url.c
+++ b/subversion/libsvn_ra_local/split_url.c
@@ -74,5 +74,8 @@
- svn_path_component_count(repos_root_dirent));
*repos_url = urlbuf->data;
+ /* Configure hook script environment variables. */
+ SVN_ERR(svn_repos_hooks_setenv(*repos, NULL, pool, pool));
+
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c
index 2771020..b031793 100644
--- a/subversion/libsvn_ra_serf/commit.c
+++ b/subversion/libsvn_ra_serf/commit.c
@@ -38,6 +38,7 @@
#include "svn_private_config.h"
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
+#include "private/svn_skel.h"
#include "ra_serf.h"
#include "../libsvn_ra/ra_loader.h"
@@ -320,6 +321,49 @@
}
+/* This is a wrapper around checkout_node() (which see for
+ documentation) which simply retries the CHECKOUT request when it
+ fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
+ server.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
+ details.
+*/
+static svn_error_t *
+retry_checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int retry_count = 5; /* Magic, arbitrary number. */
+
+ do
+ {
+ svn_error_clear(err);
+
+ err = checkout_node(working_url, commit_ctx, node_url,
+ result_pool, scratch_pool);
+
+ /* There's a small chance of a race condition here if Apache is
+ experiencing heavy commit concurrency or if the network has
+ long latency. It's possible that the value of HEAD changed
+ between the time we fetched the latest baseline and the time
+ we try to CHECKOUT that baseline. If that happens, Apache
+ will throw us a BAD_BASELINE error (deltaV says you can only
+ checkout the latest baseline). We just ignore that specific
+ error and retry a few times, asking for the latest baseline
+ again. */
+ if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
+ return err;
+ }
+ while (err && retry_count--);
+
+ return err;
+}
+
+
static svn_error_t *
checkout_dir(dir_context_t *dir,
apr_pool_t *scratch_pool)
@@ -365,8 +409,8 @@
}
/* Checkout our directory into the activity URL now. */
- err = checkout_node(working, dir->commit, checkout_url,
- dir->pool, scratch_pool);
+ err = retry_checkout_node(working, dir->commit, checkout_url,
+ dir->pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_CONFLICT)
@@ -503,8 +547,8 @@
NULL, scratch_pool, scratch_pool));
/* Checkout our file into the activity URL now. */
- err = checkout_node(&file->working_url, file->commit, checkout_url,
- file->pool, scratch_pool);
+ err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
+ file->pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_FS_CONFLICT)
@@ -718,6 +762,46 @@
return SVN_NO_ERROR;
}
+/* Possible add the lock-token "If:" precondition header to HEADERS if
+ an examination of COMMIT_CTX and RELPATH indicates that this is the
+ right thing to do.
+
+ Generally speaking, if the client provided a lock token for
+ RELPATH, it's the right thing to do. There is a notable instance
+ where this is not the case, however. If the file at RELPATH was
+ explicitly deleted in this commit already, then mod_dav removed its
+ lock token when it fielded the DELETE request, so we don't want to
+ set the lock precondition again. (See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
+*/
+static svn_error_t *
+maybe_set_lock_token_header(serf_bucket_t *headers,
+ commit_context_t *commit_ctx,
+ const char *relpath,
+ apr_pool_t *pool)
+{
+ const char *token;
+
+ if (! (relpath && commit_ctx->lock_tokens))
+ return SVN_NO_ERROR;
+
+ if (! apr_hash_get(commit_ctx->deleted_entries, relpath,
+ APR_HASH_KEY_STRING))
+ {
+ token = apr_hash_get(commit_ctx->lock_tokens, relpath,
+ APR_HASH_KEY_STRING);
+ if (token)
+ {
+ const char *token_header;
+
+ token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *
setup_proppatch_headers(serf_bucket_t *headers,
void *baton,
@@ -732,22 +816,8 @@
proppatch->base_revision));
}
- if (proppatch->relpath && proppatch->commit->lock_tokens)
- {
- const char *token;
-
- token = apr_hash_get(proppatch->commit->lock_tokens, proppatch->relpath,
- APR_HASH_KEY_STRING);
-
- if (token)
- {
- const char *token_header;
-
- token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
-
- serf_bucket_headers_set(headers, "If", token_header);
- }
- }
+ SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
+ proppatch->relpath, pool));
return SVN_NO_ERROR;
}
@@ -950,22 +1020,8 @@
ctx->result_checksum);
}
- if (ctx->commit->lock_tokens)
- {
- const char *token;
-
- token = apr_hash_get(ctx->commit->lock_tokens, ctx->relpath,
- APR_HASH_KEY_STRING);
-
- if (token)
- {
- const char *token_header;
-
- token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
-
- serf_bucket_headers_set(headers, "If", token_header);
- }
- }
+ SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
+ ctx->relpath, pool));
return APR_SUCCESS;
}
@@ -1108,7 +1164,26 @@
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
- *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ apr_hash_t *revprops = baton;
+ svn_skel_t *request_skel;
+ svn_stringbuf_t *skel_str;
+
+ request_skel = svn_skel__make_empty_list(pool);
+ if (revprops)
+ {
+ svn_skel_t *proplist_skel;
+
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
+ svn_skel__prepend(proplist_skel, request_skel);
+ svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
+ skel_str = svn_skel__unparse(request_skel, pool);
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
+ }
+ else
+ {
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ }
+
return SVN_NO_ERROR;
}
@@ -1213,12 +1288,15 @@
proppatch_context_t *proppatch_ctx;
dir_context_t *dir;
apr_hash_index_t *hi;
- const char *proppatch_target;
+ const char *proppatch_target = NULL;
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
{
post_response_ctx_t *prc;
const char *rel_path;
+ svn_boolean_t post_with_revprops =
+ apr_hash_get(ctx->session->supported_posts, "create-txn-with-props",
+ APR_HASH_KEY_STRING) ? TRUE : FALSE;
/* Create our activity URL now on the server. */
handler = apr_pcalloc(ctx->pool, sizeof(*handler));
@@ -1226,7 +1304,8 @@
handler->method = "POST";
handler->body_type = SVN_SKEL_MIME_TYPE;
handler->body_delegate = create_txn_post_body;
- handler->body_delegate_baton = NULL;
+ handler->body_delegate_baton =
+ post_with_revprops ? ctx->revprop_table : NULL;
handler->header_delegate = setup_post_headers;
handler->header_delegate_baton = NULL;
handler->path = ctx->session->me_resource;
@@ -1288,7 +1367,9 @@
dir->removed_props = apr_hash_make(dir->pool);
dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
- proppatch_target = ctx->txn_url;
+ /* If we included our revprops in the POST, we need not
+ PROPPATCH them. */
+ proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
}
else
{
@@ -1369,45 +1450,50 @@
proppatch_target = ctx->baseline_url;
}
-
- /* PROPPATCH our revprops and pass them along. */
- proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
- proppatch_ctx->pool = dir_pool;
- proppatch_ctx->commit = ctx;
- proppatch_ctx->path = proppatch_target;
- proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
- proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
- proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
-
- for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
- hi = apr_hash_next(hi))
+ /* Unless this is NULL -- which means we don't need to PROPPATCH the
+ transaction with our revprops -- then, you know, PROPPATCH the
+ transaction with our revprops. */
+ if (proppatch_target)
{
- const void *key;
- void *val;
- const char *name;
- svn_string_t *value;
- const char *ns;
+ proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = dir_pool;
+ proppatch_ctx->commit = ctx;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
- apr_hash_this(hi, &key, NULL, &val);
- name = key;
- value = val;
+ for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const char *name;
+ svn_string_t *value;
+ const char *ns;
- if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
- {
- ns = SVN_DAV_PROP_NS_SVN;
- name += sizeof(SVN_PROP_PREFIX) - 1;
- }
- else
- {
- ns = SVN_DAV_PROP_NS_CUSTOM;
+ apr_hash_this(hi, &key, NULL, &val);
+ name = key;
+ value = val;
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props,
+ proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
}
- svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
- ns, name, value, proppatch_ctx->pool);
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
}
- SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
-
*root_baton = dir;
return SVN_NO_ERROR;
@@ -1897,7 +1983,8 @@
ctx->stream = svn_stream_create(ctx, pool);
svn_stream_set_write(ctx->stream, svndiff_stream_write);
- svn_txdelta_to_svndiff2(handler, handler_baton, ctx->stream, 0, pool);
+ svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
if (base_checksum)
ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
@@ -2027,7 +2114,7 @@
{
handler->body_delegate = create_put_body;
handler->body_delegate_baton = ctx;
- handler->body_type = "application/vnd.svn-svndiff";
+ handler->body_type = SVN_SVNDIFF_MIME_TYPE;
}
handler->header_delegate = setup_put_headers;
@@ -2183,6 +2270,7 @@
apr_hash_index_t *hi;
const char *repos_root;
const char *base_relpath;
+ svn_boolean_t supports_ephemeral_props;
ctx = apr_pcalloc(pool, sizeof(*ctx));
@@ -2203,6 +2291,23 @@
svn_string_dup(val, pool));
}
+ /* If the server supports ephemeral properties, add some carrying
+ interesting version information. */
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ pool));
+ if (supports_ephemeral_props)
+ {
+ apr_hash_set(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
+ APR_HASH_KEY_STRING,
+ svn_string_create(SVN_VER_NUMBER, pool));
+ apr_hash_set(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
+ APR_HASH_KEY_STRING,
+ svn_string_create(session->useragent, pool));
+ }
+
ctx->callback = callback;
ctx->callback_baton = callback_baton;
diff --git a/subversion/libsvn_ra_serf/inherited_props.c b/subversion/libsvn_ra_serf/inherited_props.c
new file mode 100644
index 0000000..8befbb1
--- /dev/null
+++ b/subversion/libsvn_ra_serf/inherited_props.c
@@ -0,0 +1,351 @@
+/*
+ * inherited_props.c : ra_serf implementation of svn_ra_get_inherited_props
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_tables.h>
+#include <apr_xml.h>
+
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_dav_protocol.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "ra_serf.h"
+
+
+/* The current state of our XML parsing. */
+typedef enum iprops_state_e {
+ NONE = 0,
+ IPROPS_REPORT,
+ IPROPS_ITEM,
+ IPROPS_PATH,
+ IPROPS_PROPNAME,
+ IPROPS_PROPVAL
+} iprops_state_e;
+
+/* Struct for accumulating inherited props. */
+typedef struct iprops_context_t {
+ /* The depth-first ordered array of svn_prop_inherited_item_t *
+ structures we are building. */
+ apr_array_header_t *iprops;
+
+ /* Pool in which to allocate elements of IPROPS. */
+ apr_pool_t *pool;
+
+ /* The repository's root URL. */
+ const char *repos_root_url;
+
+ /* Current CDATA values*/
+ svn_stringbuf_t *curr_path;
+ svn_stringbuf_t *curr_propname;
+ svn_stringbuf_t *curr_propval;
+ const char *curr_prop_val_encoding;
+
+ /* Current element in IPROPS. */
+ svn_prop_inherited_item_t *curr_iprop;
+
+ /* Serf context completion flag for svn_ra_serf__context_run_wait() */
+ svn_boolean_t done;
+
+ /* Path we are finding inherited properties for. This is relative to
+ the RA session passed to svn_ra_serf__get_inherited_props. */
+ const char *path;
+ /* The revision of PATH*/
+ svn_revnum_t revision;
+} iprops_context_t;
+
+static svn_error_t *
+start_element(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state;
+
+ state = parser->state->current_state;
+ if (state == NONE
+ && strcmp(name.name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_REPORT);
+ }
+ else if (state == IPROPS_REPORT &&
+ strcmp(name.name, SVN_DAV__IPROP_ITEM) == 0)
+ {
+ svn_stringbuf_setempty(iprops_ctx->curr_path);
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ svn_stringbuf_setempty(iprops_ctx->curr_propval);
+ iprops_ctx->curr_prop_val_encoding = NULL;
+ iprops_ctx->curr_iprop = NULL;
+ svn_ra_serf__xml_push_state(parser, IPROPS_ITEM);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ const char *prop_val_encoding = svn_xml_get_attr_value("encoding",
+ attrs);
+ iprops_ctx->curr_prop_val_encoding = apr_pstrdup(iprops_ctx->pool,
+ prop_val_encoding);
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPVAL);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PATH) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PATH);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPNAME) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPNAME);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPVAL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_element(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == IPROPS_REPORT &&
+ strcmp(name.name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PATH
+ && strcmp(name.name, SVN_DAV__IPROP_PATH) == 0)
+ {
+ iprops_ctx->curr_iprop = apr_palloc(
+ iprops_ctx->pool, sizeof(svn_prop_inherited_item_t));
+
+ iprops_ctx->curr_iprop->path_or_url =
+ svn_path_url_add_component2(iprops_ctx->repos_root_url,
+ iprops_ctx->curr_path->data,
+ iprops_ctx->pool);
+ iprops_ctx->curr_iprop->prop_hash = apr_hash_make(iprops_ctx->pool);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PROPVAL
+ && strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ const svn_string_t *prop_val;
+
+ if (iprops_ctx->curr_prop_val_encoding)
+ {
+ svn_string_t encoded_prop_val;
+
+ if (strcmp(iprops_ctx->curr_prop_val_encoding, "base64") != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
+
+ encoded_prop_val.data = iprops_ctx->curr_propval->data;
+ encoded_prop_val.len = iprops_ctx->curr_propval->len;
+ prop_val = svn_base64_decode_string(&encoded_prop_val,
+ iprops_ctx->pool);
+ }
+ else
+ {
+ prop_val = svn_string_create_from_buf(iprops_ctx->curr_propval,
+ iprops_ctx->pool);
+ }
+
+ apr_hash_set(iprops_ctx->curr_iprop->prop_hash,
+ apr_pstrdup(iprops_ctx->pool,
+ iprops_ctx->curr_propname->data),
+ APR_HASH_KEY_STRING,
+ prop_val);
+ /* Clear current propname and propval in the event there are
+ multiple properties on the current path. */
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ svn_stringbuf_setempty(iprops_ctx->curr_propval);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PROPNAME
+ && strcmp(name.name, SVN_DAV__IPROP_PROPNAME) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_ITEM
+ && strcmp(name.name, SVN_DAV__IPROP_ITEM) == 0)
+ {
+ APR_ARRAY_PUSH(iprops_ctx->iprops, svn_prop_inherited_item_t *) =
+ iprops_ctx->curr_iprop;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+cdata_handler(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state = parser->state->current_state;
+
+ switch (state)
+ {
+ case IPROPS_PATH:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_path, data, len);
+ break;
+
+ case IPROPS_PROPNAME:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_propname, data, len);
+ break;
+
+ case IPROPS_PROPVAL:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_propval, data, len);
+ break;
+
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_iprops_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ iprops_context_t *iprops_ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT,
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:" SVN_DAV__REVISION,
+ apr_ltoa(pool, iprops_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__PATH,
+ iprops_ctx->path, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT);
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+/* Request a inherited-props-report from the URL attached to RA_SESSION,
+ and fill the IPROPS array hash with the results. */
+svn_error_t *
+svn_ra_serf__get_inherited_props(svn_ra_session_t *ra_session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err, *err2;
+
+ iprops_context_t *iprops_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ const char *req_url;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url,
+ NULL /* latest_revnum */,
+ session,
+ NULL /* conn */,
+ NULL /* url */,
+ revision,
+ result_pool, scratch_pool));
+
+ iprops_ctx = apr_pcalloc(scratch_pool, sizeof(*iprops_ctx));
+ iprops_ctx->done = FALSE;
+ iprops_ctx->repos_root_url = session->repos_root_str;
+ iprops_ctx->pool = result_pool;
+ iprops_ctx->curr_path = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_propname = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_propval = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_iprop = NULL;
+ iprops_ctx->iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ iprops_ctx->path = path;
+ iprops_ctx->revision = revision;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->conn = session->conns[0];
+ handler->session = session;
+ handler->body_delegate = create_iprops_body;
+ handler->body_delegate_baton = iprops_ctx;
+ handler->body_type = "text/xml";
+ handler->handler_pool = scratch_pool;
+
+ parser_ctx = apr_pcalloc(scratch_pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = scratch_pool;
+ parser_ctx->user_data = iprops_ctx;
+ parser_ctx->start = start_element;
+ parser_ctx->end = end_element;
+ parser_ctx->cdata = cdata_handler;
+ parser_ctx->done = &iprops_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&iprops_ctx->done, session,
+ scratch_pool);
+
+ err2 = svn_ra_serf__error_on_status(handler->sline.code, handler->path,
+ handler->location);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return err2;
+ }
+
+ SVN_ERR(err);
+
+ if (iprops_ctx->done)
+ *iprops = iprops_ctx->iprops;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c
index 22a98bd..f39ef56 100644
--- a/subversion/libsvn_ra_serf/options.c
+++ b/subversion/libsvn_ra_serf/options.c
@@ -199,15 +199,38 @@
SVN_RA_CAPABILITY_PARTIAL_REPLAY, APR_HASH_KEY_STRING,
capability_yes);
}
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
+ {
+ apr_hash_set(session->capabilities,
+ SVN_RA_CAPABILITY_INHERITED_PROPS,
+ APR_HASH_KEY_STRING, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
+ {
+ apr_hash_set(session->capabilities,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, APR_HASH_KEY_STRING,
+ capability_yes);
+ }
}
/* SVN-specific headers -- if present, server supports HTTP protocol v2 */
else if (strncmp(key, "SVN", 3) == 0)
{
+ /* If we've not yet seen any information about supported POST
+ requests, we'll initialize the list/hash with "create-txn"
+ (which we know is supported by virtue of the server speaking
+ HTTPv2 at all. */
+ if (! session->supported_posts)
+ {
+ session->supported_posts = apr_hash_make(session->pool);
+ apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
+ }
+
if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
{
session->repos_root = session->session_url;
- session->repos_root.path = apr_pstrdup(session->pool, val);
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(val, session->pool);
session->repos_root_str =
svn_urlpath__canonicalize(
apr_uri_unparse(session->pool, &session->repos_root, 0),
@@ -257,6 +280,21 @@
{
opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
}
+ else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
+ {
+ /* May contain multiple values, separated by commas. */
+ int i;
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ opt_ctx->pool);
+
+ for (i = 0; i < vals->nelts; i++)
+ {
+ const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
+
+ apr_hash_set(session->supported_posts, post_val,
+ APR_HASH_KEY_STRING, (void *)1);
+ }
+ }
}
return 0;
@@ -291,6 +329,10 @@
APR_HASH_KEY_STRING, capability_no);
apr_hash_set(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
APR_HASH_KEY_STRING, capability_no);
+ apr_hash_set(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
+ APR_HASH_KEY_STRING, capability_no);
+ apr_hash_set(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ APR_HASH_KEY_STRING, capability_no);
/* Then see which ones we can discover. */
serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
@@ -299,7 +341,7 @@
opt_ctx->headers_processed = TRUE;
}
- /* Execute the 'real' response handler to XML-parse the repsonse body. */
+ /* Execute the 'real' response handler to XML-parse the response body. */
return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
}
diff --git a/subversion/libsvn_ra_serf/property.c b/subversion/libsvn_ra_serf/property.c
index eff55ca..911af17 100644
--- a/subversion/libsvn_ra_serf/property.c
+++ b/subversion/libsvn_ra_serf/property.c
@@ -322,16 +322,11 @@
else
{
apr_hash_t *gathered;
- const char *path;
SVN_ERR_ASSERT(leaving_state == PROPSTAT);
gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
- path = apr_hash_get(gathered, "path", APR_HASH_KEY_STRING);
- if (path == NULL)
- path = ctx->path;
-
/* If we've squirreled away a note that says we want to ignore
these properties, we'll do so. Otherwise, we need to copy
them from the temporary hash into the ctx->ret_props hash. */
diff --git a/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h
index dfa5e25..262c6b2 100644
--- a/subversion/libsvn_ra_serf/ra_serf.h
+++ b/subversion/libsvn_ra_serf/ra_serf.h
@@ -62,6 +62,10 @@
APR_STRINGIFY(SERF_MINOR_VERSION) "." \
APR_STRINGIFY(SERF_PATCH_VERSION)
+/** Wait duration (in microseconds) used in calls to serf_context_run() */
+#define SVN_RA_SERF__CONTEXT_RUN_DURATION 500000
+
+
/* Forward declarations. */
typedef struct svn_ra_serf__session_t svn_ra_serf__session_t;
@@ -89,7 +93,9 @@
} svn_ra_serf__connection_t;
-/** Max. number of connctions we'll open to the server. */
+/** Max. number of connctions we'll open to the server.
+ * Note: minimum 2 connections are required for ra_serf to function correctly!
+ */
#define MAX_NR_OF_CONNS 4
/*
@@ -209,6 +215,10 @@
const char *vtxn_stub; /* for accessing transactions (i.e. txnprops) */
const char *vtxn_root_stub; /* for accessing TXN/PATH pairs */
+ /* Hash mapping const char * server-supported POST types to
+ disinteresting-but-non-null values. */
+ apr_hash_t *supported_posts;
+
/*** End HTTP v2 stuff ***/
svn_ra_serf__blncache_t *blncache;
@@ -969,12 +979,15 @@
serf_bucket_alloc_t *bkt_alloc);
/*
- * Add the appropriate serf buckets to AGG_BUCKET representing xml tag open
- * with name TAG.
+ * Add the appropriate serf buckets to AGG_BUCKET representing the XML
+ * open tag with name TAG.
*
* Take the tag's attributes from varargs, a NULL-terminated list of
- * alternating <tt>char *</tt> key and <tt>char *</tt> val. Do xml-escaping
- * on each val. Attribute will be ignored if it's value is NULL.
+ * alternating <tt>char *</tt> key and <tt>char *</tt> val. Attribute
+ * will be ignored if it's value is NULL.
+ *
+ * NOTE: Callers are responsible for XML-escaping attribute values as
+ * necessary.
*
* The bucket will be allocated from BKT_ALLOC.
*/
@@ -1647,6 +1660,14 @@
svn_revnum_t *revision_deleted,
apr_pool_t *pool);
+/* Implements the get_inherited_props RA layer function. */
+svn_error_t * svn_ra_serf__get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* Implements svn_ra__vtable_t.get_repos_root(). */
svn_error_t *
svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
@@ -1658,7 +1679,6 @@
svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *session,
svn_delta_shim_callbacks_t *callbacks);
-
/*** Authentication handler declarations ***/
/**
@@ -1699,6 +1719,21 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/** Wrap STATUS from an serf function. If STATUS is not serf error code,
+ * this is equivalent to svn_error_wrap_apr().
+ */
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...);
+
+
+#if defined(SVN_DEBUG)
+/* Wrapper macros to collect file and line information */
+#define svn_ra_serf__wrap_err \
+ (svn_error__locate(__FILE__,__LINE__), (svn_ra_serf__wrap_err))
+
+#endif
#ifdef __cplusplus
}
diff --git a/subversion/libsvn_ra_serf/replay.c b/subversion/libsvn_ra_serf/replay.c
index 9e11653..f408fcf 100644
--- a/subversion/libsvn_ra_serf/replay.c
+++ b/subversion/libsvn_ra_serf/replay.c
@@ -732,6 +732,7 @@
svn_revnum_t rev = start_revision;
const char *report_target;
int active_reports = 0;
+ apr_short_interval_time_t waittime_left = session->timeout;
SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
@@ -855,18 +856,36 @@
### ahead and apply it here, too, in case serf eventually uses
### that parameter.
*/
- status = serf_context_run(session->context, session->timeout,
+ status = serf_context_run(session->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
pool);
err = session->pending_error;
session->pending_error = NULL;
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
if (APR_STATUS_IS_TIMEUP(status))
{
svn_error_clear(err);
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
- NULL,
- _("Connection timed out"));
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ else
+ {
+ waittime_left = session->timeout;
}
/* Substract the number of completely handled responses from our
@@ -885,9 +904,8 @@
SVN_ERR(err);
if (status)
{
- return svn_error_wrap_apr(status,
- _("Error retrieving replay REPORT (%d)"),
- status);
+ return svn_ra_serf__wrap_err(status,
+ _("Error retrieving replay REPORT"));
}
done_reports = NULL;
}
diff --git a/subversion/libsvn_ra_serf/sb_bucket.c b/subversion/libsvn_ra_serf/sb_bucket.c
index c4f2d06..1e83fc7 100644
--- a/subversion/libsvn_ra_serf/sb_bucket.c
+++ b/subversion/libsvn_ra_serf/sb_bucket.c
@@ -62,7 +62,7 @@
status = serf_bucket_read(bkt, SERF_READ_ALL_AVAIL, &data, &len);
if (status != APR_SUCCESS && status != APR_EOF)
- return svn_error_wrap_apr(status, _("Failed to read the request"));
+ return svn_ra_serf__wrap_err(status, _("Failed to read the request"));
SVN_ERR(svn_spillbuf__write(*spillbuf, data, len, scratch_pool));
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
index 5b4aec5..361ee2d 100644
--- a/subversion/libsvn_ra_serf/serf.c
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -307,9 +307,9 @@
session->pool);
if (status)
{
- return svn_error_wrap_apr(status,
- _("Could not resolve proxy server '%s'"),
- proxy_host);
+ return svn_ra_serf__wrap_err(
+ status, _("Could not resolve proxy server '%s'"),
+ proxy_host);
}
session->using_proxy = TRUE;
serf_config_proxy(session->context, proxy_addr);
@@ -381,10 +381,12 @@
_("Illegal URL '%s'"),
session_URL);
}
- /* Contrary to what the comment for apr_uri_t.path says in apr-util 1.2.12 and
- older, for root paths url.path will be "", where serf requires "/". */
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
if (url.path == NULL || url.path[0] == '\0')
- url.path = apr_pstrdup(serf_sess->pool, "/");
+ {
+ url.path = apr_pstrdup(serf_sess->pool, "/");
+ }
if (!url.port)
{
url.port = apr_uri_port_of_scheme(url.scheme);
@@ -397,8 +399,9 @@
serf_sess->capabilities = apr_hash_make(serf_sess->pool);
- serf_sess->http10 = TRUE; /* until we confirm HTTP/1.1 */
- serf_sess->http10 = FALSE; /* ### don't change behavior yet */
+ /* We have to assume that the server only supports HTTP/1.0. Once it's clear
+ HTTP/1.1 is supported, we can upgrade. */
+ serf_sess->http10 = TRUE;
SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
@@ -414,7 +417,7 @@
callbacks->get_client_string(callback_baton, &client_string, pool);
if (client_string)
- serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, "/",
+ serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ",
client_string, (char *)NULL);
else
serf_sess->useragent = USER_AGENT;
@@ -428,7 +431,7 @@
svn_ra_serf__conn_closed, serf_sess->conns[0],
serf_sess->pool);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
/* Set the progress callback. */
serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
@@ -478,9 +481,18 @@
_("Illegal repository URL '%s'"), url);
}
- /* Maybe we should use a string buffer for these strings so we don't
- allocate memory in the session on every reparent? */
- session->session_url.path = apr_pstrdup(session->pool, new_url.path);
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ /* ### Maybe we should use a string buffer for these strings so we
+ ### don't allocate memory in the session on every reparent? */
+ if (new_url.path == NULL || new_url.path[0] == '\0')
+ {
+ session->session_url.path = apr_pstrdup(session->pool, "/");
+ }
+ else
+ {
+ session->session_url.path = apr_pstrdup(session->pool, new_url.path);
+ }
session->session_url_str = apr_pstrdup(session->pool, url);
return SVN_NO_ERROR;
@@ -1138,7 +1150,8 @@
svn_ra_serf__has_capability,
svn_ra_serf__replay_range,
svn_ra_serf__get_deleted_rev,
- svn_ra_serf__register_editor_shim_callbacks
+ svn_ra_serf__register_editor_shim_callbacks,
+ svn_ra_serf__get_inherited_props
};
svn_error_t *
diff --git a/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c
index ed3cba8..b31eb2b 100644
--- a/subversion/libsvn_ra_serf/update.c
+++ b/subversion/libsvn_ra_serf/update.c
@@ -313,6 +313,10 @@
/* Do we want the server to send copyfrom args or not? */
svn_boolean_t send_copyfrom_args;
+ /* Is the server including properties inline for newly added
+ files/dirs? */
+ svn_boolean_t add_props_included;
+
/* Path -> lock token mapping. */
apr_hash_t *lock_path_tokens;
@@ -358,6 +362,9 @@
/* Are we done parsing the REPORT response? */
svn_boolean_t done;
+ /* Did we get a complete (non-truncated) report? */
+ svn_boolean_t report_completed;
+
/* The XML parser context for the REPORT response. */
svn_ra_serf__xml_parser_t *parser_ctx;
};
@@ -926,12 +933,23 @@
return error_fetch(request, fetch_ctx, err);
}
- if (val && svn_cstring_casecmp(val, "application/vnd.svn-svndiff") == 0)
+ if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
{
fetch_ctx->delta_stream =
svn_txdelta_parse_svndiff(info->textdelta,
info->textdelta_baton,
TRUE, info->editor_pool);
+
+ /* Validate the delta base claimed by the server matches
+ what we asked for! */
+ val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
+ if (val && (strcmp(val, info->delta_base) != 0))
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request returned unexpected "
+ "delta base: %s"), val);
+ return error_fetch(request, fetch_ctx, err);
+ }
}
else
{
@@ -961,7 +979,7 @@
status = serf_bucket_read(response, 8000, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
fetch_ctx->read_size += len;
@@ -981,7 +999,7 @@
/* Skip on to the next iteration of this loop. */
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
continue;
}
@@ -1057,11 +1075,11 @@
svn_pool_destroy(info->pool);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
}
/* not reached */
@@ -1103,7 +1121,7 @@
status = serf_bucket_read(response, 8000, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
fetch_ctx->read_size += len;
@@ -1122,7 +1140,7 @@
/* Skip on to the next iteration of this loop. */
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
continue;
}
@@ -1152,7 +1170,7 @@
if (status)
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
}
/* not reached */
@@ -1216,9 +1234,9 @@
if (!info->url)
{
- return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
- _("The OPTIONS response did not include the "
- "requested checked-in value"));
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
}
/* If needed, create the PROPFIND to retrieve the file's properties. */
@@ -1388,7 +1406,13 @@
state = parser->state->current_state;
- if (state == NONE && strcmp(name.name, "target-revision") == 0)
+ if (state == NONE && strcmp(name.name, "update-report") == 0)
+ {
+ const char *val = svn_xml_get_attr_value("inline-props", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ ctx->add_props_included = TRUE;
+ }
+ else if (state == NONE && strcmp(name.name, "target-revision") == 0)
{
const char *rev;
@@ -1528,7 +1552,11 @@
/* Mark that we don't have a base. */
info->base_rev = SVN_INVALID_REVNUM;
dir->base_rev = info->base_rev;
- dir->fetch_props = TRUE;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ dir->fetch_props = TRUE;
dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
dir->base_name, dir->pool);
@@ -1585,9 +1613,13 @@
info = push_state(parser, ctx, ADD_FILE);
info->base_rev = SVN_INVALID_REVNUM;
- info->fetch_props = TRUE;
info->fetch_file = TRUE;
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ info->fetch_props = TRUE;
+
info->base_name = apr_pstrdup(info->pool, file_name);
info->name = NULL;
@@ -1862,8 +1894,15 @@
if (state == NONE)
{
- /* nothing to close yet. */
- return SVN_NO_ERROR;
+ if (strcmp(name.name, "update-report") == 0)
+ {
+ ctx->report_completed = TRUE;
+ }
+ else
+ {
+ /* nothing to close yet. */
+ return SVN_NO_ERROR;
+ }
}
if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
@@ -1886,9 +1925,9 @@
if (!checked_in_url &&
(!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
{
- return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
- _("The OPTIONS response did not include the "
- "requested checked-in value"));
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
}
info->dir->url = checked_in_url;
@@ -1896,7 +1935,7 @@
/* At this point, we should have the checked-in href.
* If needed, create the PROPFIND to retrieve the dir's properties.
*/
- if (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)
+ if (info->dir->fetch_props)
{
/* Unconditionally set fetch_props now. */
info->dir->fetch_props = TRUE;
@@ -2299,7 +2338,7 @@
sess->conns[cur],
sess->pool);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
sess->num_conns++;
}
@@ -2353,6 +2392,7 @@
svn_stringbuf_t *buf = NULL;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_error_t *err;
+ apr_short_interval_time_t waittime_left = sess->timeout;
svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
@@ -2435,7 +2475,9 @@
and what items are allocated within. */
iterpool_inner = svn_pool_create(iterpool);
- status = serf_context_run(sess->context, sess->timeout, iterpool_inner);
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool_inner);
err = sess->pending_error;
sess->pending_error = SVN_NO_ERROR;
@@ -2445,19 +2487,35 @@
err = handler->server_error->error;
}
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
if (APR_STATUS_IS_TIMEUP(status))
{
svn_error_clear(err);
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
- NULL,
- _("Connection timed out"));
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
}
SVN_ERR(err);
if (status)
{
- return svn_error_wrap_apr(status, _("Error retrieving REPORT (%d)"),
- status);
+ return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
}
/* Open extra connections if we have enough requests to send. */
@@ -2607,7 +2665,12 @@
SVN_ERR(close_all_dirs(report->root_dir));
}
- err = report->update_editor->close_edit(report->update_baton, iterpool);
+ /* If we got a complete report, close the edit. Otherwise, abort it. */
+ if (report->report_completed)
+ err = report->update_editor->close_edit(report->update_baton, iterpool);
+ else
+ err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing update-report close tag"));
svn_pool_destroy(iterpool);
return svn_error_trace(err);
@@ -2750,6 +2813,10 @@
make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
}
+ /* Subversion 1.8+ servers can be told to send properties for newly
+ added items inline even when doing a skelta response. */
+ make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
+
make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
index f5d07e2..e340cbf 100644
--- a/subversion/libsvn_ra_serf/util.c
+++ b/subversion/libsvn_ra_serf/util.c
@@ -691,6 +691,11 @@
serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
}
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
+#endif
+
/* These headers need to be sent with every request; see issue #3255
("mod_dav_svn does not pass client capabilities to start-commit
hooks") for why. */
@@ -707,7 +712,8 @@
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool;
-
+ apr_short_interval_time_t waittime_left = sess->timeout;
+
assert(sess->pending_error == SVN_NO_ERROR);
iterpool = svn_pool_create(scratch_pool);
@@ -722,17 +728,36 @@
if (sess->cancel_func)
SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
- status = serf_context_run(sess->context, sess->timeout, iterpool);
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool);
err = sess->pending_error;
sess->pending_error = SVN_NO_ERROR;
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
if (APR_STATUS_IS_TIMEUP(status))
{
svn_error_clear(err);
- return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
- NULL,
- _("Connection timed out"));
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
}
SVN_ERR(err);
@@ -745,7 +770,7 @@
_("Error running context"));
}
- return svn_error_wrap_apr(status, _("Error running context"));
+ return svn_ra_serf__wrap_err(status, _("Error running context"));
}
/* Debugging purposes only! */
@@ -811,7 +836,12 @@
SVN_ERR(svn_cstring_atoi64(&val, err_code));
ctx->error->apr_err = (apr_status_t)val;
}
- else
+
+ /* If there's no error code provided, or if the provided code is
+ 0 (which can happen sometimes depending on how the error is
+ constructed on the server-side), just pick a generic error
+ code to run with. */
+ if (! ctx->error->apr_err)
{
ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
}
@@ -928,7 +958,7 @@
status = drain_bucket(response);
if (status)
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
return SVN_NO_ERROR;
}
@@ -1467,7 +1497,7 @@
surface. */
err = drain_bucket(response);
if (err && !SERF_BUCKET_READ_ERROR(err))
- return svn_error_wrap_apr(err, NULL);
+ return svn_ra_serf__wrap_err(err, NULL);
return SVN_NO_ERROR;
}
@@ -1489,7 +1519,7 @@
status = serf_bucket_response_status(response, &sl);
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
/* Woo-hoo. Nothing here to see. */
@@ -1541,7 +1571,7 @@
if (SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
ctx->read_size += len;
@@ -1562,7 +1592,7 @@
/* Skip on to the next iteration of this loop. */
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
continue;
}
@@ -1606,7 +1636,7 @@
if (APR_STATUS_IS_EAGAIN(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
if (APR_STATUS_IS_EOF(status))
@@ -1627,7 +1657,7 @@
add_done_item(ctx);
}
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
/* feed me! */
@@ -1817,7 +1847,7 @@
&& handler->sline.code != 304)
{
err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
- svn_error_wrap_apr(status, NULL),
+ svn_ra_serf__wrap_err(status, NULL),
_("Premature EOF seen from server"
" (http status=%d)"),
handler->sline.code);
@@ -1966,7 +1996,8 @@
if (err
&& (!SERF_BUCKET_READ_ERROR(err->apr_err)
- || APR_STATUS_IS_ECONNRESET(err->apr_err)))
+ || APR_STATUS_IS_ECONNRESET(err->apr_err)
+ || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
{
/* These errors are special cased in serf
### We hope no handler returns these by accident. */
@@ -2212,7 +2243,8 @@
/* Now recreate the root_url. */
session->repos_root = session->session_url;
- session->repos_root.path = apr_pstrdup(session->pool, url_buf->data);
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
session->repos_root_str =
svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
&session->repos_root, 0),
@@ -2397,9 +2429,8 @@
XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
}
- /* ### should we bail on anything < 200 or >= 300 ??
- ### actually: < 200 should really be handled by the core. */
- if (ectx->handler->sline.code == 404)
+ /* ### TODO: sline.code < 200 should really be handled by the core */
+ if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
{
/* By deferring to expect_empty_body(), it will make a choice on
how to handle the body. Whatever the decision, the core handler
@@ -2418,7 +2449,7 @@
status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
#if 0
/* ### move restart/skip into the core handler */
@@ -2472,7 +2503,7 @@
if (status && !SERF_BUCKET_READ_ERROR(status))
{
- return svn_error_wrap_apr(status, NULL);
+ return svn_ra_serf__wrap_err(status, NULL);
}
}
diff --git a/subversion/libsvn_ra_serf/util_error.c b/subversion/libsvn_ra_serf/util_error.c
new file mode 100644
index 0000000..364c860
--- /dev/null
+++ b/subversion/libsvn_ra_serf/util_error.c
@@ -0,0 +1,100 @@
+/*
+ * util_error.c : serf utility routines for wrapping serf status codes
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+#include <serf.h>
+
+#include "svn_utf.h"
+#include "private/svn_error_private.h"
+
+#include "ra_serf.h"
+
+/*
+ * Undefine the helpers for creating errors.
+ *
+ * *NOTE*: Any use of these functions in any other function may need
+ * to call svn_error__locate() because the macro that would otherwise
+ * do this is being undefined and the filename and line number will
+ * not be properly set in the static error_file and error_line
+ * variables.
+ */
+#undef svn_error_create
+#undef svn_error_createf
+#undef svn_error_quick_wrap
+#undef svn_error_wrap_apr
+#undef svn_ra_serf__wrap_err
+
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...)
+{
+ const char *serf_err_msg = serf_error_string(status);
+ svn_error_t *err;
+ va_list ap;
+
+ err = svn_error_create(status, NULL, NULL);
+
+ if (serf_err_msg || fmt)
+ {
+ const char *msg;
+ const char *err_msg;
+ char errbuf[255]; /* Buffer for APR error message. */
+
+ if (serf_err_msg)
+ {
+ err_msg = serf_err_msg;
+ }
+ else
+ {
+ svn_error_t *utf8_err;
+
+ /* Grab the APR error message. */
+ apr_strerror(status, errbuf, sizeof(errbuf));
+ utf8_err = svn_utf_cstring_to_utf8(&err_msg, errbuf, err->pool);
+ if (utf8_err)
+ err_msg = NULL;
+ svn_error_clear(utf8_err);
+ }
+
+ /* Append it to the formatted message. */
+ if (fmt)
+ {
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ }
+ else
+ {
+ msg = "ra_serf";
+ }
+ if (err_msg)
+ {
+ err->message = apr_pstrcat(err->pool, msg, ": ", err_msg, NULL);
+ }
+ else
+ {
+ err->message = msg;
+ }
+ }
+
+ return err;
+}
diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
index f7198b0..3fa4b6d 100644
--- a/subversion/libsvn_ra_serf/xml.c
+++ b/subversion/libsvn_ra_serf/xml.c
@@ -205,9 +205,23 @@
}
}
}
+ else
+ {
+ const svn_ra_serf__ns_t *ns;
- /* If there is no prefix, or if the prefix is not found, then the
- name is NOT within a namespace. */
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (! ns->namespace[0])
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = name;
+ return;
+ }
+ }
+ }
+
+ /* If the prefix is not found, then the name is NOT within a
+ namespace. */
returned_prop_name->namespace = "";
returned_prop_name->name = name;
}
diff --git a/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c
index 16a70a6..c6c4b89 100644
--- a/subversion/libsvn_ra_svn/client.c
+++ b/subversion/libsvn_ra_svn/client.c
@@ -258,9 +258,10 @@
{
ra_svn_reporter_baton_t *b = baton;
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "set-path", "crb(?c)w",
- path, rev, start_empty, lock_token,
- svn_depth_to_word(depth)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_set_path,
+ path, rev, start_empty, lock_token,
+ svn_depth_to_word(depth)));
return SVN_NO_ERROR;
}
@@ -269,7 +270,8 @@
{
ra_svn_reporter_baton_t *b = baton;
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-path", "c", path));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_delete_path, path));
return SVN_NO_ERROR;
}
@@ -283,9 +285,11 @@
{
ra_svn_reporter_baton_t *b = baton;
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "link-path", "ccrb(?c)w",
- path, url, rev, start_empty, lock_token,
- svn_depth_to_word(depth)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_link_path,
+ path, url, rev, start_empty,
+ lock_token,
+ svn_depth_to_word(depth)));
return SVN_NO_ERROR;
}
@@ -294,7 +298,8 @@
{
ra_svn_reporter_baton_t *b = baton;
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "finish-report", ""));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, b->pool,
+ svn_ra_svn_cmd_finish_report));
SVN_ERR(handle_auth_request(b->sess_baton, b->pool));
SVN_ERR(svn_ra_svn_drive_editor2(b->conn, b->pool, b->editor, b->edit_baton,
NULL, FALSE));
@@ -307,7 +312,8 @@
{
ra_svn_reporter_baton_t *b = baton;
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "abort-report", ""));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, b->pool,
+ svn_ra_svn_cmd_abort_report));
return SVN_NO_ERROR;
}
@@ -454,8 +460,9 @@
|| apr_file_open_stdout(&out_file, pool))
return;
- conn = svn_ra_svn_create_conn2(NULL, in_file, out_file,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0,
+ 0, pool);
err = svn_error_wrap_apr(status, _("Error in child process: %s"), desc);
svn_error_clear(svn_ra_svn_write_cmd_failure(conn, pool, err));
svn_error_clear(err);
@@ -522,8 +529,9 @@
apr_file_inherit_unset(proc->out);
/* Guard against dotfile output to stdout on the server. */
- *conn = svn_ra_svn_create_conn2(NULL, proc->out, proc->in,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ *conn = svn_ra_svn_create_conn3(NULL, proc->out, proc->in,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ 0, 0, pool);
err = svn_ra_svn_skip_leading_garbage(*conn, pool);
if (err)
return svn_error_quick_wrap(
@@ -586,17 +594,34 @@
sess->callbacks = callbacks;
sess->callbacks_baton = callbacks_baton;
sess->bytes_read = sess->bytes_written = 0;
-
+
if (tunnel_argv)
SVN_ERR(make_tunnel(tunnel_argv, &conn, pool));
else
{
SVN_ERR(make_connection(uri->hostname, uri->port, &sock, pool));
- conn = svn_ra_svn_create_conn2(sock, NULL, NULL,
+ conn = svn_ra_svn_create_conn3(sock, NULL, NULL,
SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
- pool);
+ 0, 0, pool);
}
+ /* Build the useragent string, querying the client for any
+ customizations it wishes to note. For historical reasons, we
+ still deliver the hard-coded client version info
+ (SVN_RA_SVN__DEFAULT_USERAGENT) and the customized client string
+ separately in the protocol/capabilities handshake below. But the
+ commit logic wants the combined form for use with the
+ SVN_PROP_TXN_USER_AGENT ephemeral property because that's
+ consistent with our DAV approach. */
+ if (sess->callbacks->get_client_string != NULL)
+ SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
+ &client_string, pool));
+ if (client_string)
+ sess->useragent = apr_pstrcat(pool, SVN_RA_SVN__DEFAULT_USERAGENT " ",
+ client_string, (char *)NULL);
+ else
+ sess->useragent = SVN_RA_SVN__DEFAULT_USERAGENT;
+
/* Make sure we set conn->session before reading from it,
* because the reader and writer functions expect a non-NULL value. */
sess->conn = conn;
@@ -623,10 +648,6 @@
return svn_error_create(SVN_ERR_RA_SVN_BAD_VERSION, NULL,
_("Server does not support edit pipelining"));
- if (sess->callbacks->get_client_string != NULL)
- SVN_ERR(sess->callbacks->get_client_string(sess->callbacks_baton,
- &client_string, pool));
-
/* In protocol version 2, we send back our protocol version, our
* capability list, and the URL, and subsequently there is an auth
* request. */
@@ -639,7 +660,9 @@
SVN_RA_SVN_CAP_DEPTH,
SVN_RA_SVN_CAP_MERGEINFO,
SVN_RA_SVN_CAP_LOG_REVPROPS,
- url, "SVN/" SVN_VER_NUMBER, client_string));
+ url,
+ SVN_RA_SVN__DEFAULT_USERAGENT,
+ client_string));
SVN_ERR(handle_auth_request(sess, pool));
/* This is where the security layer would go into effect if we
@@ -753,7 +776,8 @@
svn_ra_svn__session_baton_t *new_sess;
apr_uri_t uri;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "reparent", "c", url));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_reparent, url));
err = handle_auth_request(sess, pool);
if (! err)
{
@@ -802,7 +826,8 @@
svn_ra_svn__session_baton_t *sess_baton = session->priv;
svn_ra_svn_conn_t *conn = sess_baton->conn;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-latest-rev", ""));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_get_latest_rev));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
return SVN_NO_ERROR;
@@ -815,7 +840,8 @@
svn_ra_svn__session_baton_t *sess_baton = session->priv;
svn_ra_svn_conn_t *conn = sess_baton->conn;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-dated-rev", "c",
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_get_dated_rev,
svn_time_to_cstring(tm, pool)));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "r", rev));
@@ -859,11 +885,14 @@
}
if (has_atomic_revprops)
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop2", "rc(?s)(b?s)",
- rev, name, value, dont_care, old_value));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_change_rev_prop2,
+ rev, name, value, dont_care,
+ old_value));
else
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "change-rev-prop", "rc?s",
- rev, name, value));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_change_rev_prop,
+ rev, name, value));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, ""));
@@ -900,7 +929,8 @@
svn_ra_svn_conn_t *conn = sess_baton->conn;
apr_array_header_t *proplist;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-proplist", "r", rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_rev_proplist, rev));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "l", &proplist));
SVN_ERR(svn_ra_svn_parse_proplist(proplist, pool, props));
@@ -914,7 +944,8 @@
svn_ra_svn__session_baton_t *sess_baton = session->priv;
svn_ra_svn_conn_t *conn = sess_baton->conn;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "rev-prop", "rc", rev, name));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_rev_prop, rev, name));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?s)", value));
return SVN_NO_ERROR;
@@ -966,6 +997,21 @@
_("Server doesn't support setting arbitrary "
"revision properties during commit"));
+ /* If the server supports ephemeral txnprops, add the one that
+ reports the client's version level string. */
+ if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_COMMIT_REVPROPS) &&
+ svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS))
+ {
+ apr_hash_set(revprop_table,
+ SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
+ APR_HASH_KEY_STRING,
+ svn_string_create(SVN_VER_NUMBER, pool));
+ apr_hash_set(revprop_table,
+ SVN_PROP_TXN_USER_AGENT,
+ APR_HASH_KEY_STRING,
+ svn_string_create(sess_baton->useragent, pool));
+ }
+
/* Tell the server we're starting the commit.
Send log message here for backwards compatibility with servers
before 1.5. */
@@ -998,6 +1044,7 @@
ccb = apr_palloc(pool, sizeof(*ccb));
ccb->sess_baton = sess_baton;
ccb->pool = pool;
+ ccb->new_rev = NULL;
ccb->callback = callback;
ccb->callback_baton = callback_baton;
@@ -1009,6 +1056,80 @@
return SVN_NO_ERROR;
}
+/* Parse IPROPLIST, an array of svn_ra_svn_item_t structures, as a list of
+ const char * repos relative paths and properties for those paths, storing
+ the result as an array of svn_prop_inherited_item_t *items. */
+static svn_error_t *
+parse_iproplist(apr_array_header_t **inherited_props,
+ const apr_array_header_t *iproplist,
+ svn_ra_session_t *session,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+
+{
+ int i;
+ const char *repos_root_url;
+ apr_pool_t *iterpool;
+
+ if (iproplist == NULL)
+ {
+ /* If the server doesn't have the SVN_RA_CAPABILITY_INHERITED_PROPS
+ capability we shouldn't be asking for inherited props, but if we
+ did and the server sent back nothing then we'll want to handle
+ that. */
+ *inherited_props = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(ra_svn_get_repos_root(session, &repos_root_url, scratch_pool));
+
+ *inherited_props = apr_array_make(
+ result_pool, iproplist->nelts, sizeof(svn_prop_inherited_item_t *));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < iproplist->nelts; i++)
+ {
+ apr_array_header_t *iprop_list;
+ char *parent_rel_path;
+ apr_hash_t *iprops;
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(result_pool, sizeof(*new_iprop));
+ svn_ra_svn_item_t *elt = &APR_ARRAY_IDX(iproplist, i,
+ svn_ra_svn_item_t);
+ if (elt->kind != SVN_RA_SVN_LIST)
+ return svn_error_create(
+ SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Inherited proplist element not a list"));
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_ra_svn_parse_tuple(elt->u.list, iterpool, "cl",
+ &parent_rel_path, &iprop_list));
+ SVN_ERR(svn_ra_svn_parse_proplist(iprop_list, iterpool, &iprops));
+ new_iprop->path_or_url = svn_path_url_add_component2(repos_root_url,
+ parent_rel_path,
+ result_pool);
+ new_iprop->prop_hash = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(iterpool, iprops);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ apr_hash_set(new_iprop->prop_hash,
+ apr_pstrdup(result_pool, name),
+ APR_HASH_KEY_STRING,
+ svn_string_dup(value, result_pool));
+ }
+ APR_ARRAY_PUSH(*inherited_props, svn_prop_inherited_item_t *) =
+ new_iprop;
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *ra_svn_get_file(svn_ra_session_t *session, const char *path,
svn_revnum_t rev, svn_stream_t *stream,
svn_revnum_t *fetched_rev,
@@ -1023,8 +1144,9 @@
svn_checksum_ctx_t *checksum_ctx;
apr_pool_t *iterpool;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-file", "c(?r)bb", path,
- rev, (props != NULL), (stream != NULL)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_get_file, path, rev,
+ (props != NULL), (stream != NULL)));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?c)rl",
&expected_digest,
@@ -1254,9 +1376,10 @@
svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
/* Tell the server we want to start an update. */
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "update", "(?r)cbwb", rev, target,
- recurse, svn_depth_to_word(depth),
- send_copyfrom_args));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool, svn_ra_svn_cmd_update,
+ rev, target, recurse,
+ svn_depth_to_word(depth),
+ send_copyfrom_args));
SVN_ERR(handle_auth_request(sess_baton, pool));
/* Fetch a reporter for the caller to drive. The reporter will drive
@@ -1279,9 +1402,9 @@
svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
/* Tell the server we want to start a switch. */
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "switch", "(?r)cbcw", rev,
- target, recurse, switch_url,
- svn_depth_to_word(depth)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool, svn_ra_svn_cmd_switch,
+ rev, target, recurse, switch_url,
+ svn_depth_to_word(depth)));
SVN_ERR(handle_auth_request(sess_baton, pool));
/* Fetch a reporter for the caller to drive. The reporter will drive
@@ -1304,9 +1427,9 @@
svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
/* Tell the server we want to start a status operation. */
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "status", "cb(?r)w",
- target, recurse, rev,
- svn_depth_to_word(depth)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool, svn_ra_svn_cmd_status,
+ target, recurse, rev,
+ svn_depth_to_word(depth)));
SVN_ERR(handle_auth_request(sess_baton, pool));
/* Fetch a reporter for the caller to drive. The reporter will drive
@@ -1332,10 +1455,10 @@
svn_boolean_t recurse = DEPTH_TO_RECURSE(depth);
/* Tell the server we want to start a diff. */
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "diff", "(?r)cbbcbw", rev,
- target, recurse, ignore_ancestry,
- versus_url, text_deltas,
- svn_depth_to_word(depth)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool, svn_ra_svn_cmd_diff,
+ rev, target, recurse, ignore_ancestry,
+ versus_url, text_deltas,
+ svn_depth_to_word(depth)));
SVN_ERR(handle_auth_request(sess_baton, pool));
/* Fetch a reporter for the caller to drive. The reporter will drive
@@ -1567,7 +1690,9 @@
svn_ra_svn_conn_t *conn = sess_baton->conn;
const char *kind_word;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "check-path", "c(?r)", path, rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_check_path,
+ path, rev));
SVN_ERR(handle_auth_request(sess_baton, pool));
SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "w", &kind_word));
*kind = svn_node_kind_from_word(kind_word);
@@ -1596,7 +1721,8 @@
apr_array_header_t *list = NULL;
svn_dirent_t *the_dirent;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "stat", "c(?r)", path, rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool, svn_ra_svn_cmd_stat,
+ path, rev));
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
N_("'stat' not implemented")));
@@ -1774,9 +1900,10 @@
rev_pool = svn_pool_create(pool);
chunk_pool = svn_pool_create(pool);
- SVN_ERR(svn_ra_svn_write_cmd(sess_baton->conn, pool, "get-file-revs",
- "c(?r)(?r)b", path, start, end,
- include_merged_revisions));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(sess_baton->conn, pool,
+ svn_ra_svn_cmd_get_file_revs,
+ path, start, end,
+ include_merged_revisions));
/* Servers before 1.1 don't support this command. Check for this here. */
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
@@ -1905,9 +2032,10 @@
path = key;
revnum = val;
- SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "lock", "c(?c)b(?r)",
- path, comment,
- steal_lock, *revnum));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, iterpool,
+ svn_ra_svn_cmd_lock,
+ path, comment,
+ steal_lock, *revnum));
/* Servers before 1.2 doesn't support locking. Check this here. */
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
@@ -1970,8 +2098,9 @@
else
token = NULL;
- SVN_ERR(svn_ra_svn_write_cmd(conn, iterpool, "unlock", "c(?c)b",
- path, token, break_lock));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, iterpool,
+ svn_ra_svn_cmd_unlock,
+ path, token, break_lock));
/* Servers before 1.2 don't support locking. Check this here. */
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, iterpool),
@@ -2258,7 +2387,8 @@
svn_ra_svn_conn_t* conn = sess->conn;
apr_array_header_t *list;
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-lock", "c", path));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_get_lock, path));
/* Servers before 1.2 doesn't support locking. Check this here. */
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
@@ -2310,8 +2440,9 @@
SVN_ERR(path_relative_to_root(session, &abs_path, full_url, pool));
abs_path = svn_fspath__canonicalize(abs_path, pool);
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-locks", "c(w)", path,
- svn_depth_to_word(depth)));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_get_locks, path,
+ svn_depth_to_word(depth)));
/* Servers before 1.2 doesn't support locking. Check this here. */
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
@@ -2368,8 +2499,9 @@
{
svn_ra_svn__session_baton_t *sess = session->priv;
- SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay", "rrb", revision,
- low_water_mark, send_deltas));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(sess->conn, pool,
+ svn_ra_svn_cmd_replay, revision,
+ low_water_mark, send_deltas));
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
N_("Server doesn't support the replay "
@@ -2398,9 +2530,10 @@
svn_revnum_t rev;
svn_boolean_t drive_aborted = FALSE;
- SVN_ERR(svn_ra_svn_write_cmd(sess->conn, pool, "replay-range", "rrrb",
- start_revision, end_revision,
- low_water_mark, send_deltas));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(sess->conn, pool,
+ svn_ra_svn_cmd_replay_range,
+ start_revision, end_revision,
+ low_water_mark, send_deltas));
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess, pool),
N_("Server doesn't support the "
@@ -2474,6 +2607,12 @@
else if (strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0)
*has = svn_ra_svn_has_capability(sess->conn,
SVN_RA_SVN_CAP_ATOMIC_REVPROPS);
+ else if (strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0)
+ *has = svn_ra_svn_has_capability(sess->conn,
+ SVN_RA_SVN_CAP_INHERITED_PROPS);
+ else if (strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0)
+ *has = svn_ra_svn_has_capability(sess->conn,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS);
else /* Don't know any other capabilities, so error. */
{
return svn_error_createf
@@ -2497,8 +2636,9 @@
svn_ra_svn_conn_t *conn = sess_baton->conn;
/* Transmit the parameters. */
- SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "get-deleted-rev", "crr",
- path, peg_revision, end_revision));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_get_deleted_rev,
+ path, peg_revision, end_revision));
/* Servers before 1.6 don't support this command. Check for this here. */
SVN_ERR(handle_unsupported_cmd(handle_auth_request(sess_baton, pool),
@@ -2519,6 +2659,28 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+ra_svn_get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_svn__session_baton_t *sess_baton = session->priv;
+ svn_ra_svn_conn_t *conn = sess_baton->conn;
+ apr_array_header_t *iproplist;
+
+ SVN_ERR(svn_ra_svn_write_templated_cmd(conn, scratch_pool,
+ svn_ra_svn_cmd_get_iprops,
+ path, revision));
+ SVN_ERR(handle_auth_request(sess_baton, scratch_pool));
+ SVN_ERR(svn_ra_svn_read_cmd_response(conn, scratch_pool, "l", &iproplist));
+ SVN_ERR(parse_iproplist(iprops, iproplist, session, result_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
static const svn_ra__vtable_t ra_svn_vtable = {
svn_ra_svn_version,
@@ -2556,7 +2718,8 @@
ra_svn_has_capability,
ra_svn_replay_range,
ra_svn_get_deleted_rev,
- ra_svn_register_editor_shim_callbacks
+ ra_svn_register_editor_shim_callbacks,
+ ra_svn_get_inherited_props
};
svn_error_t *
diff --git a/subversion/libsvn_ra_svn/cyrus_auth.c b/subversion/libsvn_ra_svn/cyrus_auth.c
index 5fc866f..990a873 100644
--- a/subversion/libsvn_ra_svn/cyrus_auth.c
+++ b/subversion/libsvn_ra_svn/cyrus_auth.c
@@ -871,12 +871,12 @@
/* The username callback. */
callbacks[0].id = SASL_CB_AUTHNAME;
- callbacks[0].proc = get_username_cb;
+ callbacks[0].proc = (void*)get_username_cb;
callbacks[0].context = &cred_baton;
/* The password callback. */
callbacks[1].id = SASL_CB_PASS;
- callbacks[1].proc = get_password_cb;
+ callbacks[1].proc = (void*)get_password_cb;
callbacks[1].context = &cred_baton;
/* Mark the end of the array. */
diff --git a/subversion/libsvn_ra_svn/editorp.c b/subversion/libsvn_ra_svn/editorp.c
index a2189c6..b216e53 100644
--- a/subversion/libsvn_ra_svn/editorp.c
+++ b/subversion/libsvn_ra_svn/editorp.c
@@ -121,13 +121,23 @@
/* Check for an early error status report from the consumer. If we
* get one, abort the edit and return the error. */
-static svn_error_t *check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
+static svn_error_t *
+check_for_error_internal(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
{
SVN_ERR_ASSERT(!eb->got_status);
+
+ /* reset TX counter */
+ eb->conn->written_since_error_check = 0;
+
+ /* if we weren't asked to always check, wait for at least the next TX */
+ eb->conn->may_check_for_error = eb->conn->error_check_interval == 0;
+
+ /* any incoming data? */
if (svn_ra_svn__input_waiting(eb->conn, pool))
{
eb->got_status = TRUE;
- SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(eb->conn, pool,
+ svn_ra_svn_cmd_abort_edit));
SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
/* We shouldn't get here if the consumer is doing its job. */
return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
@@ -136,13 +146,22 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+check_for_error(ra_svn_edit_baton_t *eb, apr_pool_t *pool)
+{
+ return eb->conn->may_check_for_error
+ ? check_for_error_internal(eb, pool)
+ : SVN_NO_ERROR;
+}
+
static svn_error_t *ra_svn_target_rev(void *edit_baton, svn_revnum_t rev,
apr_pool_t *pool)
{
ra_svn_edit_baton_t *eb = edit_baton;
SVN_ERR(check_for_error(eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "target-rev", "r", rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(eb->conn, pool,
+ svn_ra_svn_cmd_target_rev, rev));
return SVN_NO_ERROR;
}
@@ -153,8 +172,9 @@
const char *token = make_token('d', eb, pool);
SVN_ERR(check_for_error(eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "open-root", "(?r)c", rev,
- token));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(eb->conn, pool,
+ svn_ra_svn_cmd_open_root, rev,
+ token));
*root_baton = ra_svn_make_baton(eb->conn, pool, eb, token);
return SVN_NO_ERROR;
}
@@ -165,8 +185,9 @@
ra_svn_baton_t *b = parent_baton;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "delete-entry", "c(?r)c",
- path, rev, b->token));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_delete_entry,
+ path, rev, b->token));
return SVN_NO_ERROR;
}
@@ -181,8 +202,10 @@
SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
|| (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-dir", "ccc(?cr)", path,
- b->token, token, copy_path, copy_rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_add_dir, path,
+ b->token, token, copy_path,
+ copy_rev));
*child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
return SVN_NO_ERROR;
}
@@ -195,8 +218,9 @@
const char *token = make_token('d', b->eb, pool);
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-dir", "ccc(?r)",
- path, b->token, token, rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_open_dir,
+ path, b->token, token, rev));
*child_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
return SVN_NO_ERROR;
}
@@ -208,8 +232,9 @@
ra_svn_baton_t *b = dir_baton;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-dir-prop", "cc(?s)",
- b->token, name, value));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_change_dir_prop,
+ b->token, name, value));
return SVN_NO_ERROR;
}
@@ -218,7 +243,9 @@
ra_svn_baton_t *b = dir_baton;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-dir", "c", b->token));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_close_dir,
+ b->token));
return SVN_NO_ERROR;
}
@@ -233,8 +260,9 @@
return SVN_NO_ERROR;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "absent-dir", "cc", path,
- b->token));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_absent_dir, path,
+ b->token));
return SVN_NO_ERROR;
}
@@ -251,8 +279,10 @@
SVN_ERR_ASSERT((copy_path && SVN_IS_VALID_REVNUM(copy_rev))
|| (!copy_path && !SVN_IS_VALID_REVNUM(copy_rev)));
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "add-file", "ccc(?cr)", path,
- b->token, token, copy_path, copy_rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_add_file, path,
+ b->token, token, copy_path,
+ copy_rev));
*file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
return SVN_NO_ERROR;
}
@@ -267,8 +297,9 @@
const char *token = make_token('c', b->eb, pool);
SVN_ERR(check_for_error(b->eb, b->pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "open-file", "ccc(?r)",
- path, b->token, token, rev));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_open_file,
+ path, b->token, token, rev));
*file_baton = ra_svn_make_baton(b->conn, pool, b->eb, token);
return SVN_NO_ERROR;
}
@@ -282,8 +313,9 @@
SVN_ERR(check_for_error(b->eb, b->pool));
str.data = data;
str.len = *len;
- return svn_ra_svn_write_cmd(b->conn, b->pool, "textdelta-chunk", "cs",
- b->token, &str);
+ return svn_ra_svn_write_templated_cmd(b->conn, b->pool,
+ svn_ra_svn_cmd_textdelta_chunk,
+ b->token, &str);
}
static svn_error_t *ra_svn_svndiff_close_handler(void *baton)
@@ -291,8 +323,9 @@
ra_svn_baton_t *b = baton;
SVN_ERR(check_for_error(b->eb, b->pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, b->pool, "textdelta-end", "c",
- b->token));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, b->pool,
+ svn_ra_svn_cmd_textdelta_end,
+ b->token));
return SVN_NO_ERROR;
}
@@ -307,8 +340,9 @@
/* Tell the other side we're starting a text delta. */
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "apply-textdelta", "c(?c)",
- b->token, base_checksum));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_apply_textdelta,
+ b->token, base_checksum));
/* Transform the window stream to an svndiff stream. Reuse the
* file baton for the stream handler, since it has all the
@@ -337,8 +371,9 @@
ra_svn_baton_t *b = file_baton;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "change-file-prop", "cc(?s)",
- b->token, name, value));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_change_file_prop,
+ b->token, name, value));
return SVN_NO_ERROR;
}
@@ -349,8 +384,9 @@
ra_svn_baton_t *b = file_baton;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "close-file", "c(?c)",
- b->token, text_checksum));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_close_file,
+ b->token, text_checksum));
return SVN_NO_ERROR;
}
@@ -365,8 +401,9 @@
return SVN_NO_ERROR;
SVN_ERR(check_for_error(b->eb, pool));
- SVN_ERR(svn_ra_svn_write_cmd(b->conn, pool, "absent-file", "cc", path,
- b->token));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(b->conn, pool,
+ svn_ra_svn_cmd_absent_file, path,
+ b->token));
return SVN_NO_ERROR;
}
@@ -377,11 +414,13 @@
SVN_ERR_ASSERT(!eb->got_status);
eb->got_status = TRUE;
- SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "close-edit", ""));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(eb->conn, pool,
+ svn_ra_svn_cmd_close_edit));
err = svn_ra_svn_read_cmd_response(eb->conn, pool, "");
if (err)
{
- svn_error_clear(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
+ svn_error_clear(svn_ra_svn_write_templated_cmd(eb->conn, pool,
+ svn_ra_svn_cmd_abort_edit));
return err;
}
if (eb->callback)
@@ -395,7 +434,8 @@
if (eb->got_status)
return SVN_NO_ERROR;
- SVN_ERR(svn_ra_svn_write_cmd(eb->conn, pool, "abort-edit", ""));
+ SVN_ERR(svn_ra_svn_write_templated_cmd(eb->conn, pool,
+ svn_ra_svn_cmd_abort_edit));
SVN_ERR(svn_ra_svn_read_cmd_response(eb->conn, pool, ""));
return SVN_NO_ERROR;
}
@@ -911,30 +951,47 @@
while (!state.done)
{
svn_pool_clear(subpool);
- SVN_ERR(svn_ra_svn_read_tuple(conn, subpool, "wl", &cmd, ¶ms));
- for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
+ if (editor)
{
- if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
- break;
- }
- if (ra_svn_edit_cmds[i].cmd)
- err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
- else if (strcmp(cmd, "failure") == 0)
- {
- /* While not really an editor command this can occur when
- reporter->finish_report() fails before the first editor command */
- if (aborted)
- *aborted = TRUE;
- err = svn_ra_svn__handle_failure_status(params, pool);
- return svn_error_compose_create(
- err,
- editor->abort_edit(edit_baton, subpool));
+ SVN_ERR(svn_ra_svn_read_tuple(conn, subpool, "wl", &cmd, ¶ms));
+ for (i = 0; ra_svn_edit_cmds[i].cmd; i++)
+ if (strcmp(cmd, ra_svn_edit_cmds[i].cmd) == 0)
+ break;
+
+ if (ra_svn_edit_cmds[i].cmd)
+ err = (*ra_svn_edit_cmds[i].handler)(conn, subpool, params, &state);
+ else if (strcmp(cmd, "failure") == 0)
+ {
+ /* While not really an editor command this can occur when
+ reporter->finish_report() fails before the first editor
+ command */
+ if (aborted)
+ *aborted = TRUE;
+ err = svn_ra_svn__handle_failure_status(params, pool);
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, subpool));
+ }
+ else
+ {
+ err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+ _("Unknown command '%s'"), cmd);
+ err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
+ }
}
else
{
- err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
- _("Unknown command '%s'"), cmd);
- err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
+ const char* command = NULL;
+ SVN_ERR(svn_ra_svn__read_command_only(conn, subpool, &command));
+ if (strcmp(command, "close-edit") == 0)
+ {
+ state.done = TRUE;
+ if (aborted)
+ *aborted = FALSE;
+ err = svn_ra_svn_write_cmd_response(conn, pool, "");
+ }
+ else
+ err = NULL;
}
if (err && err->apr_err == SVN_ERR_RA_SVN_CMD_ERR)
@@ -944,7 +1001,8 @@
if (!state.done)
{
/* Abort the edit and use non-blocking I/O to write the error. */
- svn_error_clear(editor->abort_edit(edit_baton, subpool));
+ if (editor)
+ svn_error_clear(editor->abort_edit(edit_baton, subpool));
svn_ra_svn__set_block_handler(conn, blocked_write, &state);
}
write_err = svn_ra_svn_write_cmd_failure(
diff --git a/subversion/libsvn_ra_svn/marshal.c b/subversion/libsvn_ra_svn/marshal.c
index e75eef4..c8b156d 100644
--- a/subversion/libsvn_ra_svn/marshal.c
+++ b/subversion/libsvn_ra_svn/marshal.c
@@ -54,15 +54,34 @@
#define SUSPICIOUSLY_HUGE_STRING_SIZE_THRESHOLD (0x100000)
+/* When zero copy has been enabled, don't use blocking writes. Instead,
+ * time out after this many microseconds. */
+
+#define ZERO_COPY_TIMEOUT 1000000
+
+/* Return the APR socket timeout to be used for the connection depending
+ * on whether there is a blockage handler or zero copy has been activated. */
+static apr_interval_time_t
+get_timeout(svn_ra_svn_conn_t *conn)
+{
+ return conn->block_handler ? 0
+ : (conn->zero_copy_limit ? ZERO_COPY_TIMEOUT
+ : -1);
+}
+
/* --- CONNECTION INITIALIZATION --- */
-svn_ra_svn_conn_t *svn_ra_svn_create_conn2(apr_socket_t *sock,
+svn_ra_svn_conn_t *svn_ra_svn_create_conn3(apr_socket_t *sock,
apr_file_t *in_file,
apr_file_t *out_file,
int compression_level,
+ apr_size_t zero_copy_limit,
+ apr_size_t error_check_interval,
apr_pool_t *pool)
{
- svn_ra_svn_conn_t *conn = apr_palloc(pool, sizeof(*conn));
+ svn_ra_svn_conn_t *conn;
+ void *mem = apr_palloc(pool, sizeof(*conn) + SVN_RA_SVN__PAGE_SIZE);
+ conn = (void*)APR_ALIGN((apr_uintptr_t)mem, SVN_RA_SVN__PAGE_SIZE);
assert((sock && !in_file && !out_file) || (!sock && in_file && out_file));
#ifdef SVN_HAVE_SASL
@@ -73,10 +92,14 @@
conn->read_ptr = conn->read_buf;
conn->read_end = conn->read_buf;
conn->write_pos = 0;
+ conn->written_since_error_check = 0;
+ conn->error_check_interval = error_check_interval;
+ conn->may_check_for_error = error_check_interval == 0;
conn->block_handler = NULL;
conn->block_baton = NULL;
conn->capabilities = apr_hash_make(pool);
conn->compression_level = compression_level;
+ conn->zero_copy_limit = zero_copy_limit;
conn->pool = pool;
if (sock != NULL)
@@ -86,6 +109,7 @@
if (!(apr_socket_addr_get(&sa, APR_REMOTE, sock) == APR_SUCCESS
&& apr_sockaddr_ip_get(&conn->remote_ip, sa) == APR_SUCCESS))
conn->remote_ip = NULL;
+ svn_ra_svn__stream_timeout(conn->stream, get_timeout(conn));
}
else
{
@@ -96,14 +120,25 @@
return conn;
}
+svn_ra_svn_conn_t *svn_ra_svn_create_conn2(apr_socket_t *sock,
+ apr_file_t *in_file,
+ apr_file_t *out_file,
+ int compression_level,
+ apr_pool_t *pool)
+{
+ return svn_ra_svn_create_conn3(sock, in_file, out_file,
+ compression_level, 0, 0, pool);
+}
+
/* backward-compatible implementation using the default compression level */
svn_ra_svn_conn_t *svn_ra_svn_create_conn(apr_socket_t *sock,
apr_file_t *in_file,
apr_file_t *out_file,
apr_pool_t *pool)
{
- return svn_ra_svn_create_conn2(sock, in_file, out_file,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ return svn_ra_svn_create_conn3(sock, in_file, out_file,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, 0, 0,
+ pool);
}
svn_error_t *svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn,
@@ -146,6 +181,12 @@
return conn->compression_level;
}
+apr_size_t
+svn_ra_svn_zero_copy_limit(svn_ra_svn_conn_t *conn)
+{
+ return conn->zero_copy_limit;
+}
+
const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn)
{
return conn->remote_ip;
@@ -156,11 +197,9 @@
ra_svn_block_handler_t handler,
void *baton)
{
- apr_interval_time_t interval = (handler) ? 0 : -1;
-
conn->block_handler = handler;
conn->block_baton = baton;
- svn_ra_svn__stream_timeout(conn->stream, interval);
+ svn_ra_svn__stream_timeout(conn->stream, get_timeout(conn));
}
svn_boolean_t svn_ra_svn__input_waiting(svn_ra_svn_conn_t *conn,
@@ -171,20 +210,6 @@
/* --- WRITE BUFFER MANAGEMENT --- */
-/* Write bytes into the write buffer until either the write buffer is
- * full or we reach END. */
-static const char *writebuf_push(svn_ra_svn_conn_t *conn, const char *data,
- const char *end)
-{
- apr_ssize_t buflen, copylen;
-
- buflen = sizeof(conn->write_buf) - conn->write_pos;
- copylen = (buflen < end - data) ? buflen : end - data;
- memcpy(conn->write_buf + conn->write_pos, data, copylen);
- conn->write_pos += copylen;
- return data + copylen;
-}
-
/* Write data to socket or output file as appropriate. */
static svn_error_t *writebuf_output(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
const char *data, apr_size_t len)
@@ -223,6 +248,10 @@
}
}
+ conn->written_since_error_check += len;
+ conn->may_check_for_error
+ = conn->written_since_error_check >= conn->error_check_interval;
+
if (subpool)
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
@@ -242,19 +271,23 @@
static svn_error_t *writebuf_write(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
const char *data, apr_size_t len)
{
- const char *end = data + len;
-
- if (conn->write_pos > 0 && conn->write_pos + len > sizeof(conn->write_buf))
+ /* data >= 8k is sent immediately */
+ if (len >= sizeof(conn->write_buf) / 2)
{
- /* Fill and then empty the write buffer. */
- data = writebuf_push(conn, data, end);
- SVN_ERR(writebuf_flush(conn, pool));
+ if (conn->write_pos > 0)
+ SVN_ERR(writebuf_flush(conn, pool));
+
+ return writebuf_output(conn, pool, data, len);
}
- if (end - data > (apr_ssize_t)sizeof(conn->write_buf))
- SVN_ERR(writebuf_output(conn, pool, data, end - data));
- else
- writebuf_push(conn, data, end);
+ /* ensure room for the data to add */
+ if (conn->write_pos + len > sizeof(conn->write_buf))
+ SVN_ERR(writebuf_flush(conn, pool));
+
+ /* buffer the new data block as well */
+ memcpy(conn->write_buf + conn->write_pos, data, len);
+ conn->write_pos += len;
+
return SVN_NO_ERROR;
}
@@ -331,6 +364,31 @@
return SVN_NO_ERROR;
}
+/* Treat the next LEN input bytes from CONN as "read" */
+static svn_error_t *readbuf_skip(svn_ra_svn_conn_t *conn, apr_uint64_t len)
+{
+ do
+ {
+ apr_size_t buflen = conn->read_end - conn->read_ptr;
+ apr_size_t copylen = (buflen < len) ? buflen : (apr_size_t)len;
+ conn->read_ptr += copylen;
+ len -= copylen;
+ if (len == 0)
+ break;
+
+ buflen = sizeof(conn->read_buf);
+ SVN_ERR(svn_ra_svn__stream_read(conn->stream, conn->read_buf, &buflen));
+ if (buflen == 0)
+ return svn_error_create(SVN_ERR_RA_SVN_CONNECTION_CLOSED, NULL, NULL);
+
+ conn->read_end = conn->read_buf + buflen;
+ conn->read_ptr = conn->read_buf;
+ }
+ while (len > 0);
+
+ return SVN_NO_ERROR;
+}
+
/* Read data from the socket into the read buffer, which must be empty. */
static svn_error_t *readbuf_fill(svn_ra_svn_conn_t *conn, apr_pool_t *pool)
{
@@ -562,13 +620,569 @@
/* --- WRITING TUPLES --- */
+static svn_error_t *
+vwrite_tuple_cstring(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ SVN_ERR_ASSERT(cstr);
+ return svn_ra_svn_write_cstring(conn, pool, cstr);
+}
+
+static svn_error_t *
+vwrite_tuple_cstring_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ return cstr ? svn_ra_svn_write_cstring(conn, pool, cstr) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_string(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const svn_string_t *str = va_arg(*ap, const svn_string_t *);
+ SVN_ERR_ASSERT(str);
+ return svn_ra_svn_write_string(conn, pool, str);
+}
+
+static svn_error_t *
+vwrite_tuple_string_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const svn_string_t *str = va_arg(*ap, const svn_string_t *);
+ return str ? svn_ra_svn_write_string(conn, pool, str) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_start_list(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ return svn_ra_svn_start_list(conn, pool);
+}
+
+static svn_error_t *
+vwrite_tuple_end_list(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ return svn_ra_svn_end_list(conn, pool);
+}
+
+static svn_error_t *
+vwrite_tuple_word(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ SVN_ERR_ASSERT(cstr);
+ return svn_ra_svn_write_word(conn, pool, cstr);
+}
+
+static svn_error_t *
+vwrite_tuple_word_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, const char *);
+ return cstr ? svn_ra_svn_write_word(conn, pool, cstr) : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_revision(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ svn_revnum_t rev = va_arg(*ap, svn_revnum_t);
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev));
+ return svn_ra_svn_write_number(conn, pool, rev);
+}
+
+static svn_error_t *
+vwrite_tuple_revision_opt(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ svn_revnum_t rev = va_arg(*ap, svn_revnum_t);
+ return SVN_IS_VALID_REVNUM(rev)
+ ? svn_ra_svn_write_number(conn, pool, rev)
+ : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_tuple_number(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ return svn_ra_svn_write_number(conn, pool, va_arg(*ap, apr_uint64_t));
+}
+
+static svn_error_t *
+vwrite_tuple_boolean(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ const char *cstr = va_arg(*ap, svn_boolean_t) ? "true" : "false";
+ return svn_ra_svn_write_word(conn, pool, cstr);
+}
+
+static svn_error_t *
+vwrite_cmd_open_root(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_delete_entry(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_add_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_open_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_change_dir_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_string_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_absent_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_add_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_open_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_change_file_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_string_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_close_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_absent_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_textdelta_chunk(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_string(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_apply_textdelta(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_no_op(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_change_rev_prop2(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_string_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_string_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_string_opt(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_switch(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_status(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_cstring_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_word(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_replay_range(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_get_deleted_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+vwrite_cmd_get_iprops(svn_ra_svn_conn_t *conn, apr_pool_t *pool, va_list *ap)
+{
+ SVN_ERR(vwrite_tuple_cstring(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_revision_opt(conn, pool, ap));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
+
+ return SVN_NO_ERROR;
+}
+
+typedef svn_error_t *
+(*vwrite_tuple_func)(svn_ra_svn_conn_t *, apr_pool_t *, va_list *);
+
+typedef struct cmd_template_t
+{
+ const char *start_sequence;
+ apr_size_t start_sequence_length;
+ vwrite_tuple_func write_ops;
+} cmd_template_t;
+
+static const cmd_template_t cmd_templates[svn_ra_svn_cmd__last]
+ = { {"( target-rev ( " , 15, vwrite_tuple_revision },
+ {"( open-root ( " , 14, vwrite_cmd_open_root },
+ {"( delete-entry ( " , 17, vwrite_cmd_delete_entry },
+ {"( add-dir ( " , 12, vwrite_cmd_add_dir },
+ {"( open-dir ( " , 13, vwrite_cmd_open_dir },
+ {"( change-dir-prop ( " , 20, vwrite_cmd_change_dir_prop },
+ {"( close-dir ( " , 14, vwrite_tuple_cstring },
+ {"( absent-dir ( " , 15, vwrite_cmd_absent_dir },
+ {"( add-file ( " , 13, vwrite_cmd_add_file },
+ {"( open-file ( " , 14, vwrite_cmd_open_file },
+ {"( change-file-prop ( ", 21, vwrite_cmd_change_file_prop },
+ {"( close-file ( " , 15, vwrite_cmd_close_file },
+ {"( absent-file ( " , 16, vwrite_cmd_absent_file },
+ {"( textdelta-chunk ( " , 20, vwrite_cmd_textdelta_chunk },
+ {"( textdelta-end ( " , 18, vwrite_tuple_cstring },
+ {"( apply-textdelta ( " , 20, vwrite_cmd_apply_textdelta },
+ {"( close-edit ( " , 15, vwrite_cmd_no_op },
+ {"( abort-edit ( " , 15, vwrite_cmd_no_op },
+
+ {"( set-path ( " , 13, vwrite_cmd_set_path },
+ {"( delete-path ( " , 16, vwrite_tuple_cstring },
+ {"( link-path ( " , 14, vwrite_cmd_link_path },
+ {"( finish-report ( " , 18, vwrite_cmd_no_op },
+ {"( abort-report ( " , 17, vwrite_cmd_no_op },
+
+ {"( reparent ( " , 13, vwrite_tuple_cstring },
+ {"( get-latest-rev ( " , 19, vwrite_cmd_no_op },
+ {"( get-dated-rev ( " , 18, vwrite_tuple_cstring },
+ {"( change-rev-prop2 ( ", 21, vwrite_cmd_change_rev_prop2 },
+ {"( change-rev-prop ( " , 20, vwrite_cmd_change_rev_prop },
+ {"( rev-proplist ( " , 17, vwrite_tuple_revision },
+ {"( rev-prop ( " , 13, vwrite_cmd_rev_prop },
+ {"( get-file ( " , 13, vwrite_cmd_get_file },
+ {"( update ( " , 11, vwrite_cmd_update },
+ {"( switch ( " , 11, vwrite_cmd_switch },
+ {"( status ( " , 11, vwrite_cmd_status },
+ {"( diff ( " , 9, vwrite_cmd_diff },
+ {"( check-path ( " , 15, vwrite_cmd_check_path },
+ {"( stat ( " , 9, vwrite_cmd_stat },
+ {"( get-file-revs ( " , 18, vwrite_cmd_get_file_revs },
+ {"( lock ( " , 9, vwrite_cmd_lock },
+ {"( unlock ( " , 11, vwrite_cmd_unlock },
+ {"( get-lock ( " , 13, vwrite_tuple_cstring },
+ {"( get-locks ( " , 14, vwrite_cmd_get_locks },
+ {"( replay ( " , 11, vwrite_cmd_replay },
+ {"( replay-range ( " , 17, vwrite_cmd_replay_range },
+ {"( get-deleted-rev ( " , 20, vwrite_cmd_get_deleted_rev },
+ {"( get-iprops ( " , 15, vwrite_cmd_get_iprops },
+ {"( finish-replay ( " , 18, vwrite_cmd_no_op }
+ };
+
+
+
+
static svn_error_t *vwrite_tuple(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
- const char *fmt, va_list ap)
+ const char *fmt, va_list *ap)
{
svn_boolean_t opt = FALSE;
- svn_revnum_t rev;
- const char *cstr;
- const svn_string_t *str;
if (*fmt == '!')
fmt++;
@@ -577,53 +1191,30 @@
for (; *fmt; fmt++)
{
if (*fmt == 'c')
- {
- cstr = va_arg(ap, const char *);
- if (cstr)
- SVN_ERR(svn_ra_svn_write_cstring(conn, pool, cstr));
- else
- SVN_ERR_ASSERT(opt);
- }
+ SVN_ERR(opt ? vwrite_tuple_cstring_opt(conn, pool, ap)
+ : vwrite_tuple_cstring(conn, pool, ap));
else if (*fmt == 's')
- {
- str = va_arg(ap, const svn_string_t *);
- if (str)
- SVN_ERR(svn_ra_svn_write_string(conn, pool, str));
- else
- SVN_ERR_ASSERT(opt);
- }
+ SVN_ERR(opt ? vwrite_tuple_string_opt(conn, pool, ap)
+ : vwrite_tuple_string(conn, pool, ap));
else if (*fmt == '(' && !opt)
- SVN_ERR(svn_ra_svn_start_list(conn, pool));
+ SVN_ERR(vwrite_tuple_start_list(conn, pool, ap));
else if (*fmt == ')')
{
- SVN_ERR(svn_ra_svn_end_list(conn, pool));
+ SVN_ERR(vwrite_tuple_end_list(conn, pool, ap));
opt = FALSE;
}
else if (*fmt == '?')
opt = TRUE;
else if (*fmt == 'w')
- {
- cstr = va_arg(ap, const char *);
- if (cstr)
- SVN_ERR(svn_ra_svn_write_word(conn, pool, cstr));
- else
- SVN_ERR_ASSERT(opt);
- }
+ SVN_ERR(opt ? vwrite_tuple_word_opt(conn, pool, ap)
+ : vwrite_tuple_word(conn, pool, ap));
else if (*fmt == 'r')
- {
- rev = va_arg(ap, svn_revnum_t);
- if (SVN_IS_VALID_REVNUM(rev))
- SVN_ERR(svn_ra_svn_write_number(conn, pool, rev));
- else
- SVN_ERR_ASSERT(opt);
- }
+ SVN_ERR(opt ? vwrite_tuple_revision_opt(conn, pool, ap)
+ : vwrite_tuple_revision(conn, pool, ap));
else if (*fmt == 'n' && !opt)
- SVN_ERR(svn_ra_svn_write_number(conn, pool, va_arg(ap, apr_uint64_t)));
+ SVN_ERR(vwrite_tuple_number(conn, pool, ap));
else if (*fmt == 'b' && !opt)
- {
- cstr = va_arg(ap, svn_boolean_t) ? "true" : "false";
- SVN_ERR(svn_ra_svn_write_word(conn, pool, cstr));
- }
+ SVN_ERR(vwrite_tuple_boolean(conn, pool, ap));
else if (*fmt == '!' && !*(fmt + 1))
return SVN_NO_ERROR;
else
@@ -640,7 +1231,7 @@
va_list ap;
va_start(ap, fmt);
- err = vwrite_tuple(conn, pool, fmt, ap);
+ err = vwrite_tuple(conn, pool, fmt, &ap);
va_end(ap);
return err;
}
@@ -800,6 +1391,95 @@
return SVN_NO_ERROR;
}
+/* Given the first non-whitespace character FIRST_CHAR, read the first
+ * command (word) encountered in CONN into *ITEM. If ITEM is NULL, skip
+ * to the end of the current list. Use POOL for allocations. */
+static svn_error_t *
+read_command_only(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char **item, char first_char)
+{
+ char c = first_char;
+
+ /* Determine the item type and read it in. Make sure that c is the
+ * first character at the end of the item so we can test to make
+ * sure it's whitespace. */
+ if (svn_ctype_isdigit(c))
+ {
+ /* It's a number or a string. Read the number part, either way. */
+ apr_uint64_t val, prev_val=0;
+ val = c - '0';
+ while (1)
+ {
+ prev_val = val;
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ if (!svn_ctype_isdigit(c))
+ break;
+ val = val * 10 + (c - '0');
+ if (prev_val >= (APR_UINT64_MAX / 10)) /* > maximum value? */
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Number is larger than maximum"));
+ }
+ if (c == ':')
+ {
+ /* It's a string. */
+ SVN_ERR(readbuf_skip(conn, val));
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+ }
+ else if (svn_ctype_isalpha(c))
+ {
+ /* It's a word. */
+ if (item)
+ {
+ /* This is the word we want to read */
+
+ char *buf = apr_palloc(pool, 32);
+ apr_size_t len = 1;
+ buf[0] = c;
+
+ while (1)
+ {
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ if (!svn_ctype_isalnum(c) && c != '-')
+ break;
+ buf[len] = c;
+ if (++len == 32)
+ return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
+ _("Word too long"));
+ }
+ buf[len] = 0;
+ *item = buf;
+ }
+ else
+ {
+ /* we don't need the actual word, just skip it */
+ do
+ {
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+ while (svn_ctype_isalnum(c) || c == '-');
+ }
+ }
+ else if (c == '(')
+ {
+ /* Read in the list items. */
+ while (1)
+ {
+ SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c));
+ if (c == ')')
+ break;
+
+ if (item && *item == NULL)
+ SVN_ERR(read_command_only(conn, pool, item, c));
+ else
+ SVN_ERR(read_command_only(conn, pool, NULL, c));
+ }
+ SVN_ERR(readbuf_getchar(conn, pool, &c));
+ }
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *svn_ra_svn_read_item(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
svn_ra_svn_item_t **item)
{
@@ -948,6 +1628,18 @@
return err;
}
+svn_error_t *svn_ra_svn__read_command_only(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char **command)
+{
+ char c;
+ SVN_ERR(readbuf_getchar_skip_whitespace(conn, pool, &c));
+
+ *command = NULL;
+ return read_command_only(conn, pool, command, c);
+}
+
+
svn_error_t *svn_ra_svn_parse_proplist(const apr_array_header_t *list,
apr_pool_t *pool,
apr_hash_t **props)
@@ -1148,11 +1840,28 @@
SVN_ERR(svn_ra_svn_start_list(conn, pool));
SVN_ERR(svn_ra_svn_write_word(conn, pool, cmdname));
va_start(ap, fmt);
- err = vwrite_tuple(conn, pool, fmt, ap);
+ err = vwrite_tuple(conn, pool, fmt, &ap);
va_end(ap);
return err ? svn_error_trace(err) : svn_ra_svn_end_list(conn, pool);
}
+svn_error_t *svn_ra_svn_write_templated_cmd(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ svn_ra_svn_cmd_t cmd, ...)
+{
+ va_list ap;
+ svn_error_t *err;
+
+ SVN_ERR(writebuf_write_short_string(conn, pool,
+ cmd_templates[cmd].start_sequence,
+ cmd_templates[cmd].start_sequence_length));
+ va_start(ap, cmd);
+ err = cmd_templates[cmd].write_ops(conn, pool, &ap);
+ va_end(ap);
+
+ return err ? err : writebuf_write_short_string(conn, pool, ") ) ", 4);
+}
+
svn_error_t *svn_ra_svn_write_cmd_response(svn_ra_svn_conn_t *conn,
apr_pool_t *pool,
const char *fmt, ...)
@@ -1162,7 +1871,7 @@
SVN_ERR(writebuf_write_short_string(conn, pool, "( success ", 10));
va_start(ap, fmt);
- err = vwrite_tuple(conn, pool, fmt, ap);
+ err = vwrite_tuple(conn, pool, fmt, &ap);
va_end(ap);
return err ? svn_error_trace(err) : svn_ra_svn_end_list(conn, pool);
}
diff --git a/subversion/libsvn_ra_svn/protocol b/subversion/libsvn_ra_svn/protocol
index 3839ebe..bc1445c 100644
--- a/subversion/libsvn_ra_svn/protocol
+++ b/subversion/libsvn_ra_svn/protocol
@@ -202,6 +202,10 @@
[S] atomic-revprops If the server presents this capability, it
supports the change-rev-prop2 command.
See section 3.1.1.
+[S] inherited-props If the server presents this capability, it supports the
+ retrieval of inherited properties via the get-dir and
+ get-file commands and also supports the get-iprops
+ command (see section 3.1.1).
3. Commands
-----------
@@ -222,6 +226,7 @@
Here are some miscellaneous prototypes used by the command sets:
proplist: ( ( name:string value:string ) ... )
+ iproplist: ( ( name:string proplist ) ... )
propdelta: ( ( name:string [ value:string ] ) ... )
node-kind: none|file|dir|unknown
bool: true|false
@@ -293,8 +298,10 @@
? ( post-commit-err:string ) )
get-file
- params: ( path:string [ rev:number ] want-props:bool want-contents:bool )
- response: ( [ checksum:string ] rev:number props:proplist )
+ params: ( path:string [ rev:number ] want-props:bool want-contents:bool
+ [ want-iprops:bool ] )
+ response: ( [ checksum:string ] rev:number props:proplist
+ [ inherited-props:iproplist ] )
If want-contents is specified, then after sending response, server
sends file contents as a series of strings, terminated by the empty
string, followed by a second empty command response to indicate
@@ -302,8 +309,9 @@
get-dir
params: ( path:string [ rev:number ] want-props:bool want-contents:bool
- ? ( field:dirent-field ... ) )
- response: ( rev:number props:proplist ( entry:dirent ... ) )]
+ ? ( field:dirent-field ... ) [ want-iprops:bool ] )
+ response: ( rev:number props:proplist ( entry:dirent ... )
+ [ inherited-props:iproplist ] )]
dirent: ( name:string kind:node-kind size:number has-props:bool
created-rev:number [ created-date:string ]
[ last-author:string ] )
@@ -464,6 +472,11 @@
params: ( path:string peg-rev:number end-rev:number )
response: ( deleted-rev:number )
+ get-iprops
+ params: ( path:string [ rev:number ] )
+ response: ( inherited-props:iproplist )
+ New in svn 1.8. If rev is not specified, the youngest revision is used.
+
3.1.2. Editor Command Set
An edit operation produces only one response, at close-edit or
diff --git a/subversion/libsvn_ra_svn/ra_svn.h b/subversion/libsvn_ra_svn/ra_svn.h
index 83813f8..37cde18 100644
--- a/subversion/libsvn_ra_svn/ra_svn.h
+++ b/subversion/libsvn_ra_svn/ra_svn.h
@@ -56,9 +56,13 @@
apr_pool_t *pool,
void *baton);
+/* The default "user agent". */
+#define SVN_RA_SVN__DEFAULT_USERAGENT "SVN/" SVN_VER_NUMBER
+
/* The size of our per-connection read and write buffers. */
-#define SVN_RA_SVN__READBUF_SIZE (4*4096)
-#define SVN_RA_SVN__WRITEBUF_SIZE (4*4096)
+#define SVN_RA_SVN__PAGE_SIZE 4096
+#define SVN_RA_SVN__READBUF_SIZE (4 * SVN_RA_SVN__PAGE_SIZE)
+#define SVN_RA_SVN__WRITEBUF_SIZE (4 * SVN_RA_SVN__PAGE_SIZE)
/* Create forward reference */
typedef struct svn_ra_svn__session_baton_t svn_ra_svn__session_baton_t;
@@ -66,6 +70,14 @@
/* This structure is opaque to the server. The client pokes at the
* first few fields during setup and cleanup. */
struct svn_ra_svn_conn_st {
+
+ /* I/O buffers */
+ char write_buf[SVN_RA_SVN__WRITEBUF_SIZE];
+ char read_buf[SVN_RA_SVN__READBUF_SIZE];
+ char *read_ptr;
+ char *read_end;
+ apr_size_t write_pos;
+
svn_ra_svn__stream_t *stream;
svn_ra_svn__session_baton_t *session;
#ifdef SVN_HAVE_SASL
@@ -75,19 +87,32 @@
apr_socket_t *sock;
svn_boolean_t encrypted;
#endif
- char read_buf[SVN_RA_SVN__READBUF_SIZE];
- char *read_ptr;
- char *read_end;
- char write_buf[SVN_RA_SVN__WRITEBUF_SIZE];
- apr_size_t write_pos;
+
+ /* abortion check control */
+ apr_size_t written_since_error_check;
+ apr_size_t error_check_interval;
+ svn_boolean_t may_check_for_error;
+
+ /* repository info */
const char *uuid;
const char *repos_root;
+
+ /* TX block notification target */
ra_svn_block_handler_t block_handler;
void *block_baton;
+
+ /* server settings */
apr_hash_t *capabilities;
int compression_level;
+ apr_size_t zero_copy_limit;
+
+ /* who's on the other side of the connection? */
char *remote_ip;
+
+ /* EV2 support*/
svn_delta_shim_callbacks_t *shim_callbacks;
+
+ /* our pool */
apr_pool_t *pool;
};
@@ -104,6 +129,7 @@
void *callbacks_baton;
apr_off_t bytes_read, bytes_written; /* apr_off_t's because that's what
the callback interface uses */
+ const char *useragent;
};
/* Set a callback for blocked writes on conn. This handler may
@@ -167,6 +193,13 @@
svn_error_t *svn_ra_svn__stream_read(svn_ra_svn__stream_t *stream,
char *data, apr_size_t *len);
+/* Read the command word from CONN, return it in *COMMAND and skip to the
+ * end of the command. Allocate data in POOL.
+ */
+svn_error_t *svn_ra_svn__read_command_only(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ const char **command);
+
/* Set the timeout for operations on STREAM to INTERVAL. */
void svn_ra_svn__stream_timeout(svn_ra_svn__stream_t *stream,
apr_interval_time_t interval);
diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c
index 17e904a..0384a8b 100644
--- a/subversion/libsvn_repos/authz.c
+++ b/subversion/libsvn_repos/authz.c
@@ -714,14 +714,15 @@
{
struct authz_validate_baton *b = baton;
- /* If the section is the groups definition, use the group checking
- callback. Otherwise, use the rule checking callback. */
- if (strncmp(name, "groups", 6) == 0)
+ /* Use the group checking callback for the "groups" section... */
+ if (strcmp(name, "groups") == 0)
svn_config_enumerate2(b->config, name, authz_validate_group,
baton, pool);
- else if (strncmp(name, "aliases", 7) == 0)
+ /* ...and the alias checking callback for "aliases"... */
+ else if (strcmp(name, "aliases") == 0)
svn_config_enumerate2(b->config, name, authz_validate_alias,
baton, pool);
+ /* ...but for everything else use the rule checking callback. */
else
{
/* Validate the section's name. Skip the optional REPOS_NAME. */
diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c
index 7518a58..6a9f8c6 100644
--- a/subversion/libsvn_repos/commit.c
+++ b/subversion/libsvn_repos/commit.c
@@ -921,8 +921,8 @@
const char *repos_url,
const char *base_path,
apr_hash_t *revprop_table,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_repos_authz_callback_t authz_callback,
void *authz_baton,
apr_pool_t *pool)
@@ -967,8 +967,8 @@
/* Set up the edit baton. */
eb->pool = subpool;
eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool);
- eb->commit_callback = callback;
- eb->commit_callback_baton = callback_baton;
+ eb->commit_callback = commit_callback;
+ eb->commit_callback_baton = commit_baton;
eb->authz_callback = authz_callback;
eb->authz_baton = authz_baton;
eb->base_path = svn_fspath__canonicalize(base_path, subpool);
@@ -1334,12 +1334,8 @@
/* Can the user modify the repository at all? */
/* ### check against AUTHZ. */
- /* Okay... some access is allowed. Let's run the start-commit hook. */
author = apr_hash_get(revprops, SVN_PROP_REVISION_AUTHOR,
APR_HASH_KEY_STRING);
- SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
- repos->client_capabilities,
- scratch_pool));
eb = apr_palloc(result_pool, sizeof(*eb));
eb->repos = repos;
@@ -1357,6 +1353,11 @@
/* The TXN has been created. Go ahead and apply all revision properties. */
SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool));
+ /* Okay... some access is allowed. Let's run the start-commit hook. */
+ SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
+ repos->client_capabilities,
+ eb->txn_name, scratch_pool));
+
/* Wrap the FS editor within our editor. */
SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton,
result_pool, scratch_pool));
diff --git a/subversion/libsvn_repos/delta.c b/subversion/libsvn_repos/delta.c
index cd7d73a..ccd234a 100644
--- a/subversion/libsvn_repos/delta.c
+++ b/subversion/libsvn_repos/delta.c
@@ -629,12 +629,23 @@
if (!*changed_p)
return SVN_NO_ERROR;
- /* From this point on, assume things haven't changed. */
+ /* If the SHA1 checksums match for these things, we'll claim they
+ have the same contents. (We don't give quite as much weight to
+ MD5 checksums.) */
+ SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_sha1,
+ root1, path1, FALSE, pool));
+ SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_sha1,
+ root2, path2, FALSE, pool));
+ if (checksum1 && checksum2)
+ {
+ *changed_p = !svn_checksum_match(checksum1, checksum2);
+ return SVN_NO_ERROR;
+ }
+
+ /* From this point on, our default answer is "Nothing's changed". */
*changed_p = FALSE;
- /* So, things have changed. But we need to know if the two sets of
- file contents are actually different. If they have differing
- sizes, then we know they differ. */
+ /* Different filesizes means the contents are different. */
SVN_ERR(svn_fs_file_length(&size1, root1, path1, pool));
SVN_ERR(svn_fs_file_length(&size2, root2, path2, pool));
if (size1 != size2)
@@ -643,8 +654,7 @@
return SVN_NO_ERROR;
}
- /* Same sizes, huh? Well, if their checksums differ, we know they
- differ. */
+ /* Different MD5 checksums means the contents are different. */
SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_md5, root1, path1,
FALSE, pool));
SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_md5, root2, path2,
@@ -655,13 +665,11 @@
return SVN_NO_ERROR;
}
- /* Same sizes, same checksums. Chances are reallllly good that they
- don't differ, but to be absolute sure, we need to compare bytes. */
+ /* And finally, different contents means the ... uh ... contents are
+ different. */
SVN_ERR(svn_fs_file_contents(&stream1, root1, path1, pool));
SVN_ERR(svn_fs_file_contents(&stream2, root2, path2, pool));
-
SVN_ERR(svn_stream_contents_same2(&same, stream1, stream2, pool));
-
*changed_p = !same;
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_repos/deprecated.c b/subversion/libsvn_repos/deprecated.c
index b76851f..9ae6cfd 100644
--- a/subversion/libsvn_repos/deprecated.c
+++ b/subversion/libsvn_repos/deprecated.c
@@ -48,8 +48,8 @@
const char *base_path,
const char *user,
const char *log_msg,
- svn_commit_callback2_t callback,
- void *callback_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
svn_repos_authz_callback_t authz_callback,
void *authz_baton,
apr_pool_t *pool)
@@ -65,7 +65,7 @@
svn_string_create(log_msg, pool));
return svn_repos_get_commit_editor5(editor, edit_baton, repos, txn,
repos_url, base_path, revprop_table,
- callback, callback_baton,
+ commit_callback, commit_baton,
authz_callback, authz_baton, pool);
}
@@ -254,6 +254,41 @@
}
svn_error_t *
+svn_repos_begin_report2(void **report_baton,
+ svn_revnum_t revnum,
+ svn_repos_t *repos,
+ const char *fs_base,
+ const char *target,
+ const char *tgt_path,
+ svn_boolean_t text_deltas,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_begin_report3(report_baton,
+ revnum,
+ repos,
+ fs_base,
+ target,
+ tgt_path,
+ text_deltas,
+ depth,
+ ignore_ancestry,
+ send_copyfrom_args,
+ editor,
+ edit_baton,
+ authz_read_func,
+ authz_read_baton,
+ 0, /* disable zero-copy code path */
+ pool);
+}
+
+svn_error_t *
svn_repos_set_path2(void *baton, const char *path, svn_revnum_t rev,
svn_boolean_t start_empty, const char *lock_token,
apr_pool_t *pool)
diff --git a/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c
index 9fa0791..2df83d4 100644
--- a/subversion/libsvn_repos/fs-wrap.c
+++ b/subversion/libsvn_repos/fs-wrap.c
@@ -31,10 +31,12 @@
#include "svn_props.h"
#include "svn_repos.h"
#include "svn_time.h"
+#include "svn_sorts.h"
#include "repos.h"
#include "svn_private_config.h"
#include "private/svn_repos_private.h"
#include "private/svn_utf_private.h"
+#include "private/svn_fspath.h"
/*** Commit wrappers ***/
@@ -48,6 +50,9 @@
{
svn_error_t *err, *err2;
const char *txn_name;
+ apr_hash_t *props;
+ apr_pool_t *iterpool;
+ apr_hash_index_t *hi;
*new_rev = SVN_INVALID_REVNUM;
@@ -55,6 +60,24 @@
SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool));
+ /* Remove any ephemeral transaction properties. */
+ SVN_ERR(svn_fs_txn_proplist(&props, txn, pool));
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_hash_this(hi, &key, NULL, NULL);
+
+ svn_pool_clear(iterpool);
+
+ if (strncmp(key, SVN_PROP_TXN_PREFIX,
+ (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
+ {
+ SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
/* Commit. */
err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
if (! SVN_IS_VALID_REVNUM(*new_rev))
@@ -83,23 +106,29 @@
apr_hash_t *revprop_table,
apr_pool_t *pool)
{
- svn_string_t *author = apr_hash_get(revprop_table, SVN_PROP_REVISION_AUTHOR,
- APR_HASH_KEY_STRING);
apr_array_header_t *revprops;
+ const char *txn_name;
+ svn_string_t *author = apr_hash_get(revprop_table,
+ SVN_PROP_REVISION_AUTHOR,
+ APR_HASH_KEY_STRING);
- /* Run start-commit hooks. */
- SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
- repos->client_capabilities, pool));
-
- /* Begin the transaction, ask for the fs to do on-the-fly lock checks. */
+ /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
+ We fetch its name, too, so the start-commit hook can use it. */
SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
SVN_FS_TXN_CHECK_LOCKS, pool));
+ SVN_ERR(svn_fs_txn_name(&txn_name, *txn_p, pool));
/* We pass the revision properties to the filesystem by adding them
as properties on the txn. Later, when we commit the txn, these
properties will be copied into the newly created revision. */
revprops = svn_prop_hash_to_array(revprop_table, pool);
- return svn_repos_fs_change_txn_props(*txn_p, revprops, pool);
+ SVN_ERR(svn_repos_fs_change_txn_props(*txn_p, revprops, pool));
+
+ /* Run start-commit hooks. */
+ SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
+ repos->client_capabilities, txn_name,
+ pool));
+ return SVN_NO_ERROR;
}
@@ -364,9 +393,9 @@
{
/* Only svn:author and svn:date are fetchable. */
if ((strncmp(propname, SVN_PROP_REVISION_AUTHOR,
- strlen(SVN_PROP_REVISION_AUTHOR)) != 0)
+ sizeof(SVN_PROP_REVISION_AUTHOR)-1) != 0)
&& (strncmp(propname, SVN_PROP_REVISION_DATE,
- strlen(SVN_PROP_REVISION_DATE)) != 0))
+ sizeof(SVN_PROP_REVISION_DATE)-1) != 0))
*value_p = NULL;
else
@@ -710,7 +739,54 @@
cancel_func, cancel_baton, pool);
}
+svn_error_t *
+svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *inherited_props;
+ const char *parent_path = path;
+ inherited_props = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ while (!(parent_path[0] == '/' && parent_path[1] == '\0'))
+ {
+ svn_boolean_t allowed = TRUE;
+ apr_hash_t *parent_properties;
+
+ svn_pool_clear(iterpool);
+ parent_path = svn_fspath__dirname(parent_path, scratch_pool);
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, parent_path,
+ authz_read_baton, iterpool));
+ if (allowed)
+ {
+ SVN_ERR(svn_fs_node_proplist(&parent_properties, root,
+ parent_path, result_pool));
+ if (parent_properties && apr_hash_count(parent_properties))
+ {
+ svn_prop_inherited_item_t *i_props =
+ apr_pcalloc(result_pool, sizeof(*i_props));
+ i_props->path_or_url =
+ apr_pstrdup(result_pool, parent_path + 1);
+ i_props->prop_hash = parent_properties;
+ /* Build the output array in depth-first order. */
+ svn_sort__array_insert(&i_props, inherited_props, 0);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *inherited_props_p = inherited_props;
+ return SVN_NO_ERROR;
+}
/*
* vim:ts=4:sw=2:expandtab:tw=80:fo=tcroq
diff --git a/subversion/libsvn_repos/hooks.c b/subversion/libsvn_repos/hooks.c
index 82694dd..53ecfd6 100644
--- a/subversion/libsvn_repos/hooks.c
+++ b/subversion/libsvn_repos/hooks.c
@@ -215,6 +215,7 @@
svn_error_t *err;
apr_proc_t cmd_proc = {0};
apr_pool_t *cmd_pool;
+ apr_hash_t *hook_env = NULL;
if (result)
{
@@ -234,8 +235,19 @@
* destroy in order to clean up the stderr pipe opened for the process. */
cmd_pool = svn_pool_create(pool);
+ /* Check if a custom environment is defined for this hook, or else
+ * whether a default environment is defined. */
+ if (hooks_env)
+ {
+ hook_env = apr_hash_get(hooks_env, name, APR_HASH_KEY_STRING);
+ if (hook_env == NULL)
+ hook_env = apr_hash_get(hooks_env,
+ SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION,
+ APR_HASH_KEY_STRING);
+ }
+
err = svn_io_start_cmd3(&cmd_proc, ".", cmd, args,
- env_from_env_hash(hooks_env, pool, pool),
+ env_from_env_hash(hook_env, pool, pool),
FALSE, FALSE, stdin_handle, result != NULL,
null_handle, TRUE, NULL, cmd_pool);
if (!err)
@@ -357,6 +369,7 @@
svn_repos__hooks_start_commit(svn_repos_t *repos,
const char *user,
const apr_array_header_t *capabilities,
+ const char *txn_name,
apr_pool_t *pool)
{
const char *hook = svn_repos_start_commit_hook(repos, pool);
@@ -368,7 +381,7 @@
}
else if (hook)
{
- const char *args[5];
+ const char *args[6];
char *capabilities_string;
if (capabilities)
@@ -388,7 +401,8 @@
args[1] = svn_dirent_local_style(svn_repos_path(repos, pool), pool);
args[2] = user ? user : "";
args[3] = capabilities_string;
- args[4] = NULL;
+ args[4] = txn_name;
+ args[5] = NULL;
SVN_ERR(run_hook_cmd(NULL, SVN_REPOS__HOOK_START_COMMIT, hook, args,
repos->hooks_env, NULL, pool));
diff --git a/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c
index 50a56c0..1492f43 100644
--- a/subversion/libsvn_repos/log.c
+++ b/subversion/libsvn_repos/log.c
@@ -39,6 +39,7 @@
#include "repos.h"
#include "private/svn_fspath.h"
#include "private/svn_mergeinfo_private.h"
+#include "private/svn_subr_private.h"
@@ -159,6 +160,10 @@
* The CHANGED hash set and its keys and values are allocated in POOL;
* keys are const char * paths and values are svn_log_changed_path_t.
*
+ * To prevent changes from being processed over and over again, the
+ * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the
+ * latter is NULL, we will request the list inside this function.
+ *
* If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
* AUTHZ_READ_BATON and FS) to check whether each changed-path (and
* copyfrom_path) is readable:
@@ -177,18 +182,20 @@
detect_changed(apr_hash_t **changed,
svn_fs_root_t *root,
svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
apr_pool_t *pool)
{
- apr_hash_t *changes;
+ apr_hash_t *changes = prefetched_changes;
apr_hash_index_t *hi;
apr_pool_t *subpool;
svn_boolean_t found_readable = FALSE;
svn_boolean_t found_unreadable = FALSE;
- *changed = apr_hash_make(pool);
- SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
+ *changed = svn_hash__make(pool);
+ if (changes == NULL)
+ SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
if (apr_hash_count(changes) == 0)
/* No paths changed in this revision? Uh, sure, I guess the
@@ -551,26 +558,28 @@
/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
catalogs describing how mergeinfo values on paths (which are the
- keys of those catalogs) were changed in REV. */
+ keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES
+ already contains the changed paths for REV, use that. Otherwise,
+ request that data and return it in *PREFETCHED_CHANGES. */
/* ### TODO: This would make a *great*, useful public function,
### svn_repos_fs_mergeinfo_changed()! -- cmpilato */
static svn_error_t *
fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
+ apr_hash_t **prefetched_changes,
svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_hash_t *changes;
svn_fs_root_t *root;
apr_pool_t *iterpool;
apr_hash_index_t *hi;
/* Initialize return variables. */
- *deleted_mergeinfo_catalog = apr_hash_make(result_pool);
- *added_mergeinfo_catalog = apr_hash_make(result_pool);
+ *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
+ *added_mergeinfo_catalog = svn_hash__make(result_pool);
/* Revision 0 has no mergeinfo and no mergeinfo changes. */
if (rev == 0)
@@ -579,17 +588,20 @@
/* We're going to use the changed-paths information for REV to
narrow down our search. */
SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
- SVN_ERR(svn_fs_paths_changed2(&changes, root, scratch_pool));
+ if (*prefetched_changes == NULL)
+ SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
/* No changed paths? We're done. */
- if (apr_hash_count(changes) == 0)
+ if (apr_hash_count(*prefetched_changes) == 0)
return SVN_NO_ERROR;
/* Loop over changes, looking for anything that might carry an
svn:mergeinfo change and is one of our paths of interest, or a
child or [grand]parent directory thereof. */
iterpool = svn_pool_create(scratch_pool);
- for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
+ hi;
+ hi = apr_hash_next(hi))
{
const void *key;
void *val;
@@ -769,10 +781,14 @@
/* Determine what (if any) mergeinfo for PATHS was modified in
revision REV, returning the differences for added mergeinfo in
*ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
+ If *PREFETCHED_CAHNGES already contains the changed paths for
+ REV, use that. Otherwise, request that data and return it in
+ *PREFETCHED_CHANGES.
Use POOL for all allocations. */
static svn_error_t *
get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
svn_mergeinfo_t *deleted_mergeinfo,
+ apr_hash_t **prefetched_changes,
svn_fs_t *fs,
const apr_array_header_t *paths,
svn_revnum_t rev,
@@ -787,8 +803,8 @@
svn_error_t *err;
/* Initialize return value. */
- *added_mergeinfo = apr_hash_make(result_pool);
- *deleted_mergeinfo = apr_hash_make(result_pool);
+ *added_mergeinfo = svn_hash__make(result_pool);
+ *deleted_mergeinfo = svn_hash__make(result_pool);
/* If we're asking about revision 0, there's no mergeinfo to be found. */
if (rev == 0)
@@ -804,6 +820,7 @@
/* Fetch the mergeinfo changes for REV. */
err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
&added_mergeinfo_catalog,
+ prefetched_changes,
fs, rev, scratch_pool, scratch_pool);
if (err)
{
@@ -829,6 +846,7 @@
{
const char *path = APR_ARRAY_IDX(paths, i, const char *);
const char *prev_path;
+ apr_ssize_t klen;
svn_revnum_t appeared_rev, prev_rev;
svn_fs_root_t *prev_root;
svn_mergeinfo_catalog_t catalog, inherited_catalog;
@@ -899,8 +917,9 @@
FALSE, /* adjust_inherited_mergeinfo */
iterpool, iterpool));
- prev_mergeinfo = apr_hash_get(catalog, prev_path, APR_HASH_KEY_STRING);
- prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, APR_HASH_KEY_STRING);
+ klen = strlen(prev_path);
+ prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
+ prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
/* Fetch the current mergeinfo (as of REV, and including
inherited stuff) for this path. */
@@ -915,8 +934,9 @@
FALSE, /* adjust_inherited_mergeinfo */
iterpool, iterpool));
- mergeinfo = apr_hash_get(catalog, path, APR_HASH_KEY_STRING);
- inherited_mergeinfo = apr_hash_get(inherited_catalog, path, APR_HASH_KEY_STRING);
+ klen = strlen(path);
+ mergeinfo = apr_hash_get(catalog, path, klen);
+ inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
if (!prev_mergeinfo && !mergeinfo)
continue;
@@ -935,6 +955,16 @@
if (inherits_same_mergeinfo)
continue;
}
+ else
+ {
+ svn_boolean_t same_mergeinfo;
+ SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
+ prev_inherited_mergeinfo,
+ FALSE,
+ TRUE, iterpool));
+ if (same_mergeinfo)
+ continue;
+ }
/* Compare, constrast, and combine the results. */
SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
@@ -989,6 +1019,7 @@
fill_log_entry(svn_log_entry_t *log_entry,
svn_revnum_t rev,
svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
svn_boolean_t discover_changed_paths,
const apr_array_header_t *revprops,
svn_repos_authz_func_t authz_read_func,
@@ -1008,7 +1039,7 @@
SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
patherr = detect_changed(&changed_paths,
- newroot, fs,
+ newroot, fs, prefetched_changes,
authz_read_func, authz_read_baton,
pool);
@@ -1048,7 +1079,7 @@
if (censor_revprops)
{
/* ... but we can only return author/date. */
- log_entry->revprops = apr_hash_make(pool);
+ log_entry->revprops = svn_hash__make(pool);
apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
APR_HASH_KEY_STRING,
apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
@@ -1077,7 +1108,7 @@
/* ... but we can only return author/date. */
continue;
if (log_entry->revprops == NULL)
- log_entry->revprops = apr_hash_make(pool);
+ log_entry->revprops = svn_hash__make(pool);
apr_hash_set(log_entry->revprops, name,
APR_HASH_KEY_STRING, value);
}
@@ -1119,6 +1150,7 @@
static svn_error_t *
send_log(svn_revnum_t rev,
svn_fs_t *fs,
+ apr_hash_t *prefetched_changes,
svn_mergeinfo_t log_target_history_as_mergeinfo,
apr_hash_t *nested_merges,
svn_boolean_t discover_changed_paths,
@@ -1137,7 +1169,7 @@
svn_boolean_t found_rev_of_interest = TRUE;
log_entry = svn_log_entry_create(pool);
- SVN_ERR(fill_log_entry(log_entry, rev, fs,
+ SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
discover_changed_paths || handling_merged_revision,
revprops, authz_read_func, authz_read_baton,
pool));
@@ -1800,7 +1832,7 @@
singe revisions where HIST_START is equal to HIST_END. */
svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
- svn_mergeinfo_t mergeinfo = apr_hash_make(scratch_pool);
+ svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
apr_pool_t *processed_pool = apr_hash_pool_get(processed);
int i;
@@ -1941,6 +1973,7 @@
svn_mergeinfo_t added_mergeinfo = NULL;
svn_mergeinfo_t deleted_mergeinfo = NULL;
svn_boolean_t has_children = FALSE;
+ apr_hash_t *changes = NULL;
/* If we're including merged revisions, we need to calculate
the mergeinfo deltas committed in this revision to our
@@ -1961,6 +1994,7 @@
}
SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
&deleted_mergeinfo,
+ &changes,
fs, cur_paths,
current, iterpool,
iterpool));
@@ -1973,7 +2007,7 @@
in anyway). */
if (descending_order)
{
- SVN_ERR(send_log(current, fs,
+ SVN_ERR(send_log(current, fs, changes,
log_target_history_as_mergeinfo, nested_merges,
discover_changed_paths,
subtractive_merge, handling_merged_revisions,
@@ -1989,8 +2023,8 @@
single hash to be shared across all of the merged
recursions so we can track and squelch duplicates. */
subpool = svn_pool_create(pool);
- nested_merges = apr_hash_make(subpool);
- processed = apr_hash_make(subpool);
+ nested_merges = svn_hash__make(subpool);
+ processed = svn_hash__make(subpool);
}
SVN_ERR(handle_merged_revisions(
@@ -2035,7 +2069,7 @@
*cur_rev = current;
if (! rev_mergeinfo)
- rev_mergeinfo = apr_hash_make(pool);
+ rev_mergeinfo = svn_hash__make(pool);
apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
add_and_del_mergeinfo);
}
@@ -2078,8 +2112,8 @@
|| apr_hash_count(deleted_mergeinfo) > 0);
}
- SVN_ERR(send_log(current, fs, log_target_history_as_mergeinfo,
- nested_merges,
+ SVN_ERR(send_log(current, fs, NULL,
+ log_target_history_as_mergeinfo, nested_merges,
discover_changed_paths, subtractive_merge,
handling_merged_revisions, revprops, has_children,
receiver, receiver_baton, authz_read_func,
@@ -2089,7 +2123,7 @@
if (!nested_merges)
{
subpool = svn_pool_create(pool);
- nested_merges = apr_hash_make(subpool);
+ nested_merges = svn_hash__make(subpool);
}
SVN_ERR(handle_merged_revisions(current, fs,
@@ -2169,7 +2203,7 @@
end_rev = tmp_rev;
}
- *paths_history_mergeinfo = apr_hash_make(result_pool);
+ *paths_history_mergeinfo = svn_hash__make(result_pool);
for (i = 0; i < paths->nelts; i++)
{
@@ -2302,7 +2336,8 @@
rev = end - i;
else
rev = start + i;
- SVN_ERR(send_log(rev, fs, NULL, NULL, discover_changed_paths, FALSE,
+ SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
+ discover_changed_paths, FALSE,
FALSE, revprops, FALSE, receiver,
receiver_baton, authz_read_func,
authz_read_baton, iterpool));
diff --git a/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c
index 2a05011..306075b 100644
--- a/subversion/libsvn_repos/replay.c
+++ b/subversion/libsvn_repos/replay.c
@@ -150,7 +150,6 @@
apr_pool_t *pool;
};
-#ifndef USE_EV2_IMPL
/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
the appropriate editor calls to add it and its children without any
history. This is meant to be used when either a subset of the tree
@@ -342,7 +341,6 @@
return SVN_NO_ERROR;
}
-#endif
/* Given PATH deleted under ROOT, return in READABLE whether the path was
readable prior to the deletion. Consult COPIES (a stack of 'struct
@@ -459,7 +457,6 @@
return SVN_NO_ERROR;
}
-#ifndef USE_EV2_IMPL
static svn_error_t *
path_driver_cb_func(void **dir_baton,
void *parent_baton,
@@ -784,8 +781,7 @@
return SVN_NO_ERROR;
}
-#else
-
+#ifdef USE_EV2_IMPL
static svn_error_t *
fetch_kind_func(svn_kind_t *kind,
void *baton,
@@ -795,8 +791,14 @@
{
svn_fs_root_t *root = baton;
svn_node_kind_t node_kind;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
- SVN_ERR(svn_fs_check_path(&node_kind, root, path, scratch_pool));
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_check_path(&node_kind, prev_root, path, scratch_pool));
*kind = svn__kind_from_node_kind(node_kind, FALSE);
return SVN_NO_ERROR;
@@ -814,15 +816,14 @@
svn_fs_root_t *prev_root;
svn_fs_t *fs = svn_fs_root_fs(root);
- SVN_ERR(svn_fs_revision_root(&prev_root, fs,
- svn_fs_revision_root_revision(root) - 1,
- scratch_pool));
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
return SVN_NO_ERROR;
}
-
#endif
@@ -947,9 +948,9 @@
}
/* Call the path-based editor driver. */
- return svn_delta_path_driver(editor, edit_baton,
- SVN_INVALID_REVNUM, paths,
- path_driver_cb_func, &cb_baton, pool);
+ return svn_delta_path_driver2(editor, edit_baton,
+ paths, TRUE,
+ path_driver_cb_func, &cb_baton, pool);
#else
svn_editor_t *editorv2;
struct svn_delta__extra_baton *exb;
@@ -1009,7 +1010,6 @@
* Ev2 Implementation *
*****************************************************************/
-#ifdef USE_EV2_IMPL
/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
the appropriate editor calls to add it and its children without any
history. This is meant to be used when either a subset of the tree
@@ -1018,16 +1018,16 @@
unavailable because of authz and we need to use it as the source of
a copy. */
static svn_error_t *
-add_subdir(svn_fs_root_t *source_root,
- svn_fs_root_t *target_root,
- svn_editor_t *editor,
- const char *repos_relpath,
- const char *source_fspath,
- svn_repos_authz_func_t authz_read_func,
- void *authz_read_baton,
- apr_hash_t *changed_paths,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+add_subdir_ev2(svn_fs_root_t *source_root,
+ svn_fs_root_t *target_root,
+ svn_editor_t *editor,
+ const char *repos_relpath,
+ const char *source_fspath,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_hash_t *changed_paths,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_hash_index_t *hi;
@@ -1131,11 +1131,11 @@
}
else
{
- SVN_ERR(add_subdir(new_source_root, target_root,
- editor, child_relpath,
- new_source_fspath,
- authz_read_func, authz_read_baton,
- changed_paths, result_pool, iterpool));
+ SVN_ERR(add_subdir_ev2(new_source_root, target_root,
+ editor, child_relpath,
+ new_source_fspath,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool, iterpool));
}
}
else if (dent->kind == svn_node_file)
@@ -1164,7 +1164,6 @@
return SVN_NO_ERROR;
}
-#endif
static svn_error_t *
replay_node(svn_fs_root_t *root,
@@ -1178,11 +1177,6 @@
void *authz_read_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
-#ifndef USE_EV2_IMPL
-{
- SVN__NOT_IMPLEMENTED();
-}
-#else
{
svn_fs_path_change2_t *change;
svn_boolean_t do_add = FALSE;
@@ -1297,10 +1291,11 @@
contents. */
if (change->copyfrom_path && ! copyfrom_path)
{
- SVN_ERR(add_subdir(copyfrom_root, root, editor,
- repos_relpath, change->copyfrom_path,
- authz_read_func, authz_read_baton,
- changed_paths, result_pool, scratch_pool));
+ SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
+ repos_relpath, change->copyfrom_path,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool,
+ scratch_pool));
}
else
{
@@ -1437,15 +1432,18 @@
if (change->node_kind == svn_node_file
&& (change->text_mod || change->prop_mod || downgraded_copy))
{
- svn_checksum_t *checksum;
- svn_stream_t *contents;
+ svn_checksum_t *checksum = NULL;
+ svn_stream_t *contents = NULL;
- SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
- root, repos_relpath, TRUE,
- scratch_pool));
+ if (change->text_mod)
+ {
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ root, repos_relpath, TRUE,
+ scratch_pool));
- SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
- scratch_pool));
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+ }
SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
SVN_INVALID_REVNUM, props, checksum,
@@ -1465,7 +1463,6 @@
return SVN_NO_ERROR;
}
-#endif
svn_error_t *
svn_repos__replay_ev2(svn_fs_root_t *root,
diff --git a/subversion/libsvn_repos/reporter.c b/subversion/libsvn_repos/reporter.c
index e488e32..cad2ff5 100644
--- a/subversion/libsvn_repos/reporter.c
+++ b/subversion/libsvn_repos/reporter.c
@@ -36,6 +36,7 @@
#include "private/svn_dep_compat.h"
#include "private/svn_fspath.h"
#include "private/svn_subr_private.h"
+#include "private/svn_string_private.h"
#define NUM_CACHED_SOURCE_ROOTS 4
@@ -102,13 +103,15 @@
driven by the client as it describes its working copy revisions. */
typedef struct report_baton_t
{
- /* Parameters remembered from svn_repos_begin_report2 */
+ /* Parameters remembered from svn_repos_begin_report3 */
svn_repos_t *repos;
const char *fs_base; /* fspath corresponding to wc anchor */
const char *s_operand; /* anchor-relative wc target (may be empty) */
svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */
const char *t_path; /* FS path the edit will bring the wc to */
svn_boolean_t text_deltas; /* Whether to report text deltas */
+ apr_size_t zero_copy_limit; /* Max item size that will be sent using
+ the zero-copy code path. */
/* If the client requested a specific depth, record it here; if the
client did not, then this is svn_depth_unknown, and the depth of
@@ -135,8 +138,10 @@
/* Cache for revision properties. This is used to eliminate redundant
revprop fetching. */
- apr_hash_t* revision_infos;
+ apr_hash_t *revision_infos;
+ /* This will not change. So, fetch it once and reuse it. */
+ svn_string_t *repos_uuid;
apr_pool_t *pool;
} report_baton_t;
@@ -513,25 +518,29 @@
void *object, apr_pool_t *pool)
{
svn_fs_root_t *s_root;
- apr_hash_t *s_props, *t_props;
+ apr_hash_t *s_props = NULL, *t_props;
apr_array_header_t *prop_diffs;
int i;
svn_revnum_t crev;
- const char *uuid;
- svn_string_t *cr_str;
- revision_info_t* revision_info;
+ revision_info_t *revision_info;
svn_boolean_t changed;
const svn_prop_t *pc;
svn_lock_t *lock;
+ apr_hash_index_t *hi;
/* Fetch the created-rev and send entry props. */
SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
if (SVN_IS_VALID_REVNUM(crev))
{
+ /* convert committed-rev to string */
+ char buf[SVN_INT64_BUFFER_SIZE];
+ svn_string_t cr_str;
+ cr_str.data = buf;
+ cr_str.len = svn__i64toa(buf, crev);
+
/* Transmit the committed-rev. */
- cr_str = svn_string_createf(pool, "%ld", crev);
SVN_ERR(change_fn(b, object,
- SVN_PROP_ENTRY_COMMITTED_REV, cr_str, pool));
+ SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool));
SVN_ERR(get_revision_info(b, crev, &revision_info, pool));
@@ -546,9 +555,8 @@
revision_info->author, pool));
/* Transmit the UUID. */
- SVN_ERR(svn_fs_get_uuid(b->repos->fs, &uuid, pool));
SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID,
- svn_string_create(uuid, pool), pool));
+ b->repos_uuid, pool));
}
/* Update lock properties. */
@@ -575,23 +583,83 @@
/* If so, go ahead and get the source path's properties. */
SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool));
}
- else
- s_props = apr_hash_make(pool);
/* Get the target path's properties */
SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool));
- /* Now transmit the differences. */
- SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
- for (i = 0; i < prop_diffs->nelts; i++)
+ if (s_props && apr_hash_count(s_props))
{
- pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
- SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
+ /* Now transmit the differences. */
+ SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
+ for (i = 0; i < prop_diffs->nelts; i++)
+ {
+ pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+ SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
+ }
+ }
+ else if (apr_hash_count(t_props))
+ {
+ /* So source, i.e. all new. Transmit all target props. */
+ for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(change_fn(b, object, key, val, pool));
+ }
}
return SVN_NO_ERROR;
}
+/* Baton type to be passed into send_zero_copy_delta.
+ */
+typedef struct zero_copy_baton_t
+{
+ /* don't process data larger than this limit */
+ apr_size_t zero_copy_limit;
+
+ /* window handler and baton to send the data to */
+ svn_txdelta_window_handler_t dhandler;
+ void *dbaton;
+
+ /* return value: will be set to TRUE, if the data was processed. */
+ svn_boolean_t zero_copy_succeeded;
+} zero_copy_baton_t;
+
+/* Implement svn_fs_process_contents_func_t. If LEN is smaller than the
+ * limit given in *BATON, send the CONTENTS as an delta windows to the
+ * handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that
+ * BATON. Otherwise, reset it to FALSE.
+ * Use POOL for temporary allocations.
+ */
+static svn_error_t *
+send_zero_copy_delta(const unsigned char *contents,
+ apr_size_t len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ zero_copy_baton_t *zero_copy_baton = baton;
+
+ /* if the item is too large, the caller must revert to traditional
+ streaming code. */
+ if (len > zero_copy_baton->zero_copy_limit)
+ {
+ zero_copy_baton->zero_copy_succeeded = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_txdelta_send_contents(contents, len,
+ zero_copy_baton->dhandler,
+ zero_copy_baton->dbaton, pool));
+
+ /* all fine now */
+ zero_copy_baton->zero_copy_succeeded = TRUE;
+ return SVN_NO_ERROR;
+}
+
+
/* Make the appropriate edits on FILE_BATON to change its contents and
properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
possibly using LOCK_TOKEN to determine if the client's lock on the file
@@ -645,6 +713,26 @@
{
if (b->text_deltas)
{
+ /* if we send deltas against empty streams, we may use our
+ zero-copy code. */
+ if (b->zero_copy_limit > 0 && s_path == NULL)
+ {
+ zero_copy_baton_t baton = { b->zero_copy_limit
+ , dhandler
+ , dbaton
+ , FALSE};
+ svn_boolean_t called = FALSE;
+ SVN_ERR(svn_fs_try_process_file_contents(&called,
+ b->t_root, t_path,
+ send_zero_copy_delta,
+ &baton, pool));
+
+ /* data has been available and small enough,
+ i.e. been processed? */
+ if (called && baton.zero_copy_succeeded)
+ return SVN_NO_ERROR;
+ }
+
SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
b->t_root, t_path, pool));
SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool));
@@ -1463,7 +1551,7 @@
svn_error_t *
-svn_repos_begin_report2(void **report_baton,
+svn_repos_begin_report3(void **report_baton,
svn_revnum_t revnum,
svn_repos_t *repos,
const char *fs_base,
@@ -1477,14 +1565,18 @@
void *edit_baton,
svn_repos_authz_func_t authz_read_func,
void *authz_read_baton,
+ apr_size_t zero_copy_limit,
apr_pool_t *pool)
{
report_baton_t *b;
+ const char *uuid;
if (depth == svn_depth_exclude)
return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
_("Request depth 'exclude' not supported"));
+ SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool));
+
/* Build a reporter baton. Copy strings in case the caller doesn't
keep track of them. */
b = apr_palloc(pool, sizeof(*b));
@@ -1495,6 +1587,7 @@
b->t_path = switch_path ? svn_fspath__canonicalize(switch_path, pool)
: svn_fspath__join(b->fs_base, s_operand, pool);
b->text_deltas = text_deltas;
+ b->zero_copy_limit = zero_copy_limit;
b->requested_depth = depth;
b->ignore_ancestry = ignore_ancestry;
b->send_copyfrom_args = send_copyfrom_args;
@@ -1508,6 +1601,7 @@
b->reader = svn_spillbuf__reader_create(1000 /* blocksize */,
1000000 /* maxsize */,
pool);
+ b->repos_uuid = svn_string_create(uuid, pool);
/* Hand reporter back to client. */
*report_baton = b;
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
index c6a8f8a..ff1755e 100644
--- a/subversion/libsvn_repos/repos.c
+++ b/subversion/libsvn_repos/repos.c
@@ -34,6 +34,7 @@
#include "svn_repos.h"
#include "svn_hash.h"
#include "svn_version.h"
+#include "svn_config.h"
#include "private/svn_repos_private.h"
#include "private/svn_subr_private.h"
@@ -166,13 +167,6 @@
pool);
}
-void
-svn_repos_hooks_setenv(svn_repos_t *repos,
- apr_hash_t *hooks_env)
-{
- repos->hooks_env = hooks_env;
-}
-
static svn_error_t *
create_repos_dir(const char *path, apr_pool_t *pool)
{
@@ -311,16 +305,17 @@
"" NL
"# START-COMMIT HOOK" NL
"#" NL
-"# The start-commit hook is invoked before a Subversion txn is created" NL
-"# in the process of doing a commit. Subversion runs this hook" NL
-"# by invoking a program (script, executable, binary, etc.) named" NL
-"# '"SCRIPT_NAME"' (for which this file is a template)" NL
-"# with the following ordered arguments:" NL
+"# The start-commit hook is invoked immediately after a Subversion txn is" NL
+"# created and populated with initial revprops in the process of doing a" NL
+"# commit. Subversion runs this hook by invoking a program (script, " NL
+"# executable, binary, etc.) named '"SCRIPT_NAME"' (for which this file" NL
+"# is a template) with the following ordered arguments:" NL
"#" NL
"# [1] REPOS-PATH (the path to this repository)" NL
"# [2] USER (the authenticated user attempting to commit)" NL
"# [3] CAPABILITIES (a colon-separated list of capabilities reported" NL
"# by the client; see note below)" NL
+"# [4] TXN-NAME (the name of the commit txn just created)" NL
"#" NL
"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5" NL
"# clients will typically report at least the \"" \
@@ -329,6 +324,11 @@
"# e.g.: \"" SVN_RA_CAPABILITY_MERGEINFO ":some-other-capability\" " \
"(the order is undefined)." NL
"#" NL
+"# Note: The TXN-NAME parameter is new in Subversion 1.8. Prior to version" NL
+"# 1.8, the start-commit hook was invoked before the commit txn was even" NL
+"# created, so the ability to inspect the commit txn and its metadata from" NL
+"# within the start-commit hook was not possible." NL
+"# " NL
"# The list is self-reported by the client. Therefore, you should not" NL
"# make security assumptions based on the capabilities list, nor should" NL
"# you assume that clients reliably report every capability they have." NL
@@ -1035,6 +1035,13 @@
"### \"none\" (to compare usernames as-is without case conversion, which" NL
"### is the default behavior)." NL
"# force-username-case = none" NL
+"### The hooks-env options specifies a path to the hook script environment " NL
+"### configuration file. This option overrides the per-repository default" NL
+"### and can be used to configure the hook script environment for multiple " NL
+"### repositories in a single file, if an absolute path is specified." NL
+"### Unless you specify an absolute path, the file's location is relative" NL
+"### to the directory containing this file." NL
+"# hooks-env = " SVN_REPOS__CONF_HOOKS_ENV NL
"" NL
"[sasl]" NL
"### This option specifies whether you want to use the Cyrus SASL" NL
@@ -1049,17 +1056,7 @@
"### to the effective key length for encryption (e.g. 128 means 128-bit" NL
"### encryption). The values below are the defaults." NL
"# min-encryption = 0" NL
-"# max-encryption = 256" NL
-"" NL
-"[hooks-env]" NL
-"### Options in this section define environment variables for use by" NL
-"### hook scripts run by svnserve." NL
-#ifdef WIN32
-"# PATH = C:\\Program Files\\Subversion\\bin" NL
-#else
-"# PATH = /bin:/sbin:/usr/bin:/usr/sbin" NL
-#endif
-"# LC_CTYPE = en_US.UTF-8" NL;
+"# max-encryption = 256" NL;
SVN_ERR_W(svn_io_file_create(svn_repos_svnserve_conf(repos, pool),
svnserve_conf_contents, pool),
@@ -1126,6 +1123,129 @@
_("Creating authz file"));
}
+ {
+ static const char * const hooks_env_contents =
+"### This file is an example hook script environment configuration file." NL
+"### Hook scripts run in an empty environment by default." NL
+"### As shown below each section defines environment variables for a" NL
+"### particular hook script. The [default] section defines environment" NL
+"### variables for all hook scripts, unless overridden by a hook-specific" NL
+"### section." NL
+"" NL
+"### This example configures a UTF-8 locale for all hook scripts, so that " NL
+"### special characters, such as umlauts, may be printed to stderr." NL
+"### If UTF-8 is used with a mod_dav_svn server, the SVNUseUTF8 option must" NL
+"### also be set to 'yes' in httpd.conf." NL
+"### With svnserve, the LANG environment variable of the svnserve process" NL
+"### must be set to the same value as given here." NL
+"[default]" NL
+"# LANG = en_US.UTF-8" NL
+"" NL
+"### This sets the PATH environment variable for the pre-commit hook." NL
+"# [pre-commit]" NL
+"# PATH = /usr/local/bin:/usr/bin:/usr/sbin" NL;
+
+ SVN_ERR_W(svn_io_file_create(svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV,
+ pool),
+ hooks_env_contents, pool),
+ _("Creating hooks-env file"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for parse_hooks_env_option. */
+struct parse_hooks_env_option_baton {
+ /* The name of the section being parsed. If not the default section,
+ * the section name should match the name of a hook to which the
+ * options apply. */
+ const char *section;
+ apr_hash_t *hooks_env;
+} parse_hooks_env_option_baton;
+
+/* An implementation of svn_config_enumerator2_t.
+ * Set environment variable NAME to value VALUE in the environment for
+ * all hooks (in case the current section is the default section),
+ * or the hook with the name corresponding to the current section's name. */
+static svn_boolean_t
+parse_hooks_env_option(const char *name, const char *value,
+ void *baton, apr_pool_t *pool)
+{
+ struct parse_hooks_env_option_baton *bo = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(bo->hooks_env);
+ apr_hash_t *hook_env;
+
+ hook_env = apr_hash_get(bo->hooks_env, bo->section, APR_HASH_KEY_STRING);
+ if (hook_env == NULL)
+ {
+ hook_env = apr_hash_make(result_pool);
+ apr_hash_set(bo->hooks_env, apr_pstrdup(result_pool, bo->section),
+ APR_HASH_KEY_STRING, hook_env);
+ }
+ apr_hash_set(hook_env, apr_pstrdup(result_pool, name),
+ APR_HASH_KEY_STRING, apr_pstrdup(result_pool, value));
+
+ return TRUE;
+}
+
+struct parse_hooks_env_section_baton {
+ svn_config_t *cfg;
+ apr_hash_t *hooks_env;
+} parse_hooks_env_section_baton;
+
+/* An implementation of svn_config_section_enumerator2_t. */
+static svn_boolean_t
+parse_hooks_env_section(const char *name, void *baton, apr_pool_t *pool)
+{
+ struct parse_hooks_env_section_baton *b = baton;
+ struct parse_hooks_env_option_baton bo;
+
+ bo.section = name;
+ bo.hooks_env = b->hooks_env;
+
+ (void)svn_config_enumerate2(b->cfg, name, parse_hooks_env_option, &bo, pool);
+
+ return TRUE;
+}
+
+/* Parse the hooks env file for this repository. */
+static svn_error_t *
+parse_hooks_env(svn_repos_t *repos,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_config_t *cfg;
+ int n;
+ struct parse_hooks_env_section_baton b;
+
+ SVN_ERR(svn_config_read2(&cfg, local_abspath, FALSE, TRUE, scratch_pool));
+ b.cfg = cfg;
+ b.hooks_env = apr_hash_make(result_pool);
+ n = svn_config_enumerate_sections2(cfg, parse_hooks_env_section, &b,
+ scratch_pool);
+ if (n > 0)
+ repos->hooks_env = b.hooks_env;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_hooks_setenv(svn_repos_t *repos,
+ const char *hooks_env_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (hooks_env_path == NULL)
+ hooks_env_path = svn_dirent_join(repos->conf_path,
+ SVN_REPOS__CONF_HOOKS_ENV, scratch_pool);
+ else if (!svn_dirent_is_absolute(hooks_env_path))
+ hooks_env_path = svn_dirent_join(repos->conf_path, hooks_env_path,
+ scratch_pool);
+
+ SVN_ERR(parse_hooks_env(repos, hooks_env_path, result_pool, scratch_pool));
+
return SVN_NO_ERROR;
}
@@ -1145,6 +1265,7 @@
repos->hook_path = svn_dirent_join(path, SVN_REPOS__HOOK_DIR, pool);
repos->lock_path = svn_dirent_join(path, SVN_REPOS__LOCK_DIR, pool);
repos->repository_capabilities = apr_hash_make(pool);
+ repos->hooks_env = NULL;
return repos;
}
@@ -1685,6 +1806,47 @@
return SVN_NO_ERROR;
}
+/* For BDB we fall back on BDB's repos layer lock which means that the
+ repository is unreadable while frozen.
+
+ For FSFS we delegate to the FS layer which uses the FSFS write-lock
+ and an SQLite reserved lock which means the repository is readable
+ while frozen. */
+svn_error_t *
+svn_repos_freeze(const char *path,
+ svn_error_t *(*freeze_body)(void *, apr_pool_t *),
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+
+ /* Using a subpool as the only way to unlock the repos lock used by
+ BDB is to clear the pool used to take the lock. */
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(get_repos(&repos, path,
+ TRUE /* exclusive */,
+ FALSE /* non-blocking */,
+ FALSE /* open-fs */,
+ NULL, subpool));
+
+ if (strcmp(repos->fs_type, SVN_FS_TYPE_BDB) == 0)
+ {
+ svn_error_t *err = freeze_body(baton, subpool);
+ svn_pool_destroy(subpool);
+ return err;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_open(&repos->fs, repos->db_path, NULL, subpool));
+ SVN_ERR(svn_fs_freeze(svn_repos_fs(repos), freeze_body, baton, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *svn_repos_db_logfiles(apr_array_header_t **logfiles,
const char *path,
svn_boolean_t only_unused,
diff --git a/subversion/libsvn_repos/repos.h b/subversion/libsvn_repos/repos.h
index 2bc4de7..061fca6 100644
--- a/subversion/libsvn_repos/repos.h
+++ b/subversion/libsvn_repos/repos.h
@@ -85,6 +85,11 @@
/* The extension added to the names of example hook scripts. */
#define SVN_REPOS__HOOK_DESC_EXT ".tmpl"
+/* The file which contains a custom set of environment variables
+ * passed inherited to hook scripts, in the repository conf directory. */
+#define SVN_REPOS__CONF_HOOKS_ENV "hooks-env"
+/* The name of the default section in the hooks-env config file. */
+#define SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION "default"
/* The configuration file for svnserve, in the repository conf directory. */
#define SVN_REPOS__CONF_SVNSERVE_CONF "svnserve.conf"
@@ -142,7 +147,15 @@
apr_hash_t *repository_capabilities;
/* The environment inherited to hook scripts. If NULL, hooks run
- * in an empty environment. */
+ * in an empty environment.
+ *
+ * This is a nested hash table.
+ * The entry with name SVN_REPOS__HOOKS_ENV_DEFAULT_SECTION contains the
+ * default environment for all hooks in form of an apr_hash_t with keys
+ * and values describing the names and values of environment variables.
+ * Defaults can be overridden by an entry matching the name of a hook.
+ * E.g. an entry with the name SVN_REPOS__HOOK_PRE_COMMIT provides the
+ * environment specific to the pre-commit hook. */
apr_hash_t *hooks_env;
};
@@ -153,14 +166,19 @@
allocations. If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
USER is the authenticated name of the user starting the commit.
+
CAPABILITIES is a list of 'const char *' capability names (using
SVN_RA_CAPABILITY_*) that the client has self-reported. Note that
there is no guarantee the client is telling the truth: the hook
- should not make security assumptions based on the capabilities. */
+ should not make security assumptions based on the capabilities.
+
+ TXN_NAME is the name of the commit transaction that's just been
+ created. */
svn_error_t *
svn_repos__hooks_start_commit(svn_repos_t *repos,
const char *user,
const apr_array_header_t *capabilities,
+ const char *txn_name,
apr_pool_t *pool);
/* Run the pre-commit hook for REPOS. Use POOL for any temporary
diff --git a/subversion/libsvn_subr/auth.c b/subversion/libsvn_subr/auth.c
index 3ba0688..94ac013 100644
--- a/subversion/libsvn_subr/auth.c
+++ b/subversion/libsvn_subr/auth.c
@@ -523,34 +523,29 @@
apr_array_header_t *password_stores;
int i;
+#define SVN__MAYBE_ADD_PROVIDER(list, p) \
+ { if (p) APR_ARRAY_PUSH(list, svn_auth_provider_object_t *) = p; }
+
#define SVN__DEFAULT_AUTH_PROVIDER_LIST \
"gnome-keyring,kwallet,keychain,gpg-agent,windows-cryptoapi"
- if (config)
- {
- svn_config_get
- (config,
- &password_stores_config_option,
- SVN_CONFIG_SECTION_AUTH,
- SVN_CONFIG_OPTION_PASSWORD_STORES,
- SVN__DEFAULT_AUTH_PROVIDER_LIST);
- }
- else
- {
- password_stores_config_option = SVN__DEFAULT_AUTH_PROVIDER_LIST;
- }
-
*providers = apr_array_make(pool, 12, sizeof(svn_auth_provider_object_t *));
- password_stores
- = svn_cstring_split(password_stores_config_option, " ,", TRUE, pool);
+ /* Fetch the configured list of password stores, and split them into
+ an array. */
+ svn_config_get(config,
+ &password_stores_config_option,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_PASSWORD_STORES,
+ SVN__DEFAULT_AUTH_PROVIDER_LIST);
+ password_stores = svn_cstring_split(password_stores_config_option,
+ " ,", TRUE, pool);
for (i = 0; i < password_stores->nelts; i++)
{
const char *password_store = APR_ARRAY_IDX(password_stores, i,
const char *);
-
/* GNOME Keyring */
if (apr_strnatcmp(password_store, "gnome-keyring") == 0)
{
@@ -558,104 +553,68 @@
"gnome_keyring",
"simple",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"gnome_keyring",
"ssl_client_cert_pw",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
-
- continue;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
-
/* GPG-AGENT */
- if (apr_strnatcmp(password_store, "gpg-agent") == 0)
+ else if (apr_strnatcmp(password_store, "gpg-agent") == 0)
{
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"gpg_agent",
"simple",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
-
- continue;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
-
/* KWallet */
- if (apr_strnatcmp(password_store, "kwallet") == 0)
+ else if (apr_strnatcmp(password_store, "kwallet") == 0)
{
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"kwallet",
"simple",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"kwallet",
"ssl_client_cert_pw",
pool));
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
-
- continue;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
-
/* Keychain */
- if (apr_strnatcmp(password_store, "keychain") == 0)
+ else if (apr_strnatcmp(password_store, "keychain") == 0)
{
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"keychain",
"simple",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"keychain",
"ssl_client_cert_pw",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
-
- continue;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
-
/* Windows */
- if (apr_strnatcmp(password_store, "windows-cryptoapi") == 0)
+ else if (apr_strnatcmp(password_store, "windows-cryptoapi") == 0)
{
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"windows",
"simple",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"windows",
"ssl_client_cert_pw",
pool));
-
- if (provider)
- APR_ARRAY_PUSH(*providers, svn_auth_provider_object_t *) = provider;
-
- continue;
+ SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
-
- return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
- _("Invalid config: unknown password store "
- "'%s'"),
- password_store);
}
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_subr/cache-membuffer.c b/subversion/libsvn_subr/cache-membuffer.c
index c89fd49..93fde6d 100644
--- a/subversion/libsvn_subr/cache-membuffer.c
+++ b/subversion/libsvn_subr/cache-membuffer.c
@@ -33,11 +33,12 @@
#include "svn_string.h"
#include "private/svn_dep_compat.h"
#include "private/svn_mutex.h"
+#include "private/svn_pseudo_md5.h"
/*
* This svn_cache__t implementation actually consists of two parts:
* a shared (per-process) singleton membuffer cache instance and shallow
- * svn_cache__t frontend instances that each use different key spaces.
+ * svn_cache__t front-end instances that each use different key spaces.
* For data management, they all forward to the singleton membuffer cache.
*
* A membuffer cache consists of two parts:
@@ -65,7 +66,7 @@
*
* Insertion can occur at only one, sliding position. It is marked by its
* offset in the data buffer plus the index of the first used entry at or
- * behind that position. If this gap is too small to accomodate the new
+ * behind that position. If this gap is too small to accommodate the new
* item, the insertion window is extended as described below. The new entry
* will always be inserted at the bottom end of the window and since the
* next used entry is known, properly sorted insertion is possible.
@@ -79,7 +80,7 @@
* get evicted, it is moved to the begin of that window and the window is
* moved.
*
- * Moreover, the entry's hits get halfed to make that entry more likely to
+ * Moreover, the entry's hits get halved to make that entry more likely to
* be removed the next time the sliding insertion / removal window comes by.
* As a result, frequently used entries are likely not to be dropped until
* they get not used for a while. Also, even a cache thrashing situation
@@ -100,33 +101,46 @@
* on their hash key.
*/
-/* A 4-way associative cache seems to be the best compromise between
+/* A 16-way associative cache seems to be a good compromise between
* performance (worst-case lookups) and efficiency-loss due to collisions.
*
* This value may be changed to any positive integer.
*/
-#define GROUP_SIZE 4
+#define GROUP_SIZE 16
-/* We use MD5 for digest size and speed (SHA1 is >2x slower, for instance).
- */
-#define KEY_SIZE APR_MD5_DIGESTSIZE
-
-/* For more efficient copy operations, let'a align all data items properly.
+/* For more efficient copy operations, let's align all data items properly.
* Must be a power of 2.
*/
#define ITEM_ALIGNMENT 16
-/* Don't create cache segments smaller than this value unless the total
- * cache size itself is smaller.
+/* By default, don't create cache segments smaller than this value unless
+ * the total cache size itself is smaller.
*/
-#define MIN_SEGMENT_SIZE 0x2000000ull
+#define DEFAULT_MIN_SEGMENT_SIZE 0x2000000ull
+
+/* The minimum segment size we will allow for multi-segmented caches
+ */
+#define MIN_SEGMENT_SIZE 0x10000ull
+
+/* The maximum number of segments allowed. Larger numbers reduce the size
+ * of each segment, in turn reducing the max size of a cachable item.
+ * Also, each segment gets its own lock object. The actual number supported
+ * by the OS may therefore be lower and svn_cache__membuffer_cache_create
+ * may return an error.
+ */
+#define MAX_SEGMENT_COUNT 0x10000
+
+/* As of today, APR won't allocate chunks of 4GB or more. So, limit the
+ * segment size to slightly below that.
+ */
+#define MAX_SEGMENT_SIZE 0xffff0000ull
/* We don't mark the initialization status for every group but initialize
* a number of groups at once. That will allow for a very small init flags
* vector that is likely to fit into the CPU caches even for fairly large
- * caches. For instance, the default of 32 means 8x32 groups per byte, i.e.
- * 8 flags/byte x 32 groups/flag x 4 entries/group x 40 index bytes/entry
- * x 16 cache bytes/index byte = 1kB init vector / 640MB cache.
+ * membuffer caches. For instance, the default of 32 means 8x32 groups per
+ * byte, i.e. 8 flags/byte x 32 groups/flag x 8 entries/group x 40 index
+ * bytes/entry x 8 cache bytes/index byte = 1kB init vector / 640MB cache.
*/
#define GROUP_INIT_GRANULARITY 32
@@ -134,10 +148,6 @@
*/
#define NO_INDEX APR_UINT32_MAX
-/* Invalid buffer offset reference value. Equivalent to APR_UINT32_T(-1)
- */
-#define NO_OFFSET APR_UINT64_MAX
-
/* To save space in our group structure, we only use 32 bit size values
* and, therefore, limit the size of each entry to just below 4GB.
* Supporting larger items is not a good idea as the data transfer
@@ -145,6 +155,12 @@
*/
#define MAX_ITEM_SIZE ((apr_uint32_t)(0 - ITEM_ALIGNMENT))
+/* A 16 byte key type. We use that to identify cache entries.
+ * The notation as just two integer values will cause many compilers
+ * to create better code.
+ */
+typedef apr_uint64_t entry_key_t[2];
+
/* Debugging / corruption detection support.
* If you define this macro, the getter functions will performed expensive
* checks on the item data, requested keys and entry types. If there is
@@ -205,7 +221,7 @@
/* Initialize all members of TAG except for the content hash.
*/
static svn_error_t *store_key_part(entry_tag_t *tag,
- unsigned char *prefix_hash,
+ entry_key_t prefix_hash,
char *prefix_tail,
const void *key,
apr_size_t key_len,
@@ -265,16 +281,17 @@
return SVN_NO_ERROR;
}
-/* Reoccuring code snippets.
+/* Reoccurring code snippets.
*/
#define DEBUG_CACHE_MEMBUFFER_TAG_ARG entry_tag_t *tag,
-#define DEBUG_CACHE_MEMBUFFER_TAG &tag,
+#define DEBUG_CACHE_MEMBUFFER_TAG tag,
#define DEBUG_CACHE_MEMBUFFER_INIT_TAG \
- entry_tag_t tag; \
- SVN_ERR(store_key_part(&tag, \
+ entry_tag_t _tag; \
+ entry_tag_t *tag = &_tag; \
+ SVN_ERR(store_key_part(tag, \
cache->prefix, \
cache->prefix_tail, \
key, \
@@ -296,17 +313,15 @@
/* A single dictionary entry. Since all entries will be allocated once
* during cache creation, those entries might be either used or unused.
* An entry is used if and only if it is contained in the doubly-linked
- * list of used entries. An entry is unused if and only if its OFFSET
- * member is NO_OFFSET.
+ * list of used entries.
*/
typedef struct entry_t
{
/* Identifying the data item. Only valid for used entries.
*/
- unsigned char key [KEY_SIZE];
+ entry_key_t key;
- /* If NO_OFFSET, the entry is not in used. Otherwise, it is the offset
- * of the cached item's serialized data within the data buffer.
+ /* The offset of the cached item's serialized data within the data buffer.
*/
apr_uint64_t offset;
@@ -342,9 +357,16 @@
#endif
} entry_t;
-/* We group dictionary entries to make this GROUP-SIZE-way assicative.
+/* We group dictionary entries to make this GROUP-SIZE-way associative.
*/
-typedef entry_t entry_group_t[GROUP_SIZE];
+typedef struct entry_group_t
+{
+ /* number of entries used [0 .. USED-1] */
+ apr_size_t used;
+
+ /* the actual entries */
+ entry_t entries[GROUP_SIZE];
+} entry_group_t;
/* The cache header structure.
*/
@@ -444,6 +466,12 @@
* thread-safe.
*/
apr_thread_rwlock_t *lock;
+
+ /* If set, write access will wait until they get exclusive access.
+ * Otherwise, they will become no-ops if the segment is currently
+ * read-locked.
+ */
+ svn_boolean_t allow_blocking_writes;
#endif
};
@@ -455,7 +483,7 @@
*/
#define ALIGN_POINTER(pointer) ((void*)ALIGN_VALUE((apr_size_t)(char*)(pointer)))
-/* If locking is supported for CACHE, aquire a read lock for it.
+/* If locking is supported for CACHE, acquire a read lock for it.
*/
static svn_error_t *
read_lock_cache(svn_membuffer_t *cache)
@@ -471,18 +499,48 @@
return SVN_NO_ERROR;
}
-/* If locking is supported for CACHE, aquire a write lock for it.
+/* If locking is supported for CACHE, acquire a write lock for it.
*/
static svn_error_t *
-write_lock_cache(svn_membuffer_t *cache)
+write_lock_cache(svn_membuffer_t *cache, svn_boolean_t *success)
{
#if APR_HAS_THREADS
if (cache->lock)
- {
- apr_status_t status = apr_thread_rwlock_wrlock(cache->lock);
- if (status)
- return svn_error_wrap_apr(status, _("Can't write-lock cache mutex"));
- }
+ {
+ apr_status_t status;
+ if (cache->allow_blocking_writes)
+ {
+ status = apr_thread_rwlock_wrlock(cache->lock);
+ }
+ else
+ {
+ status = apr_thread_rwlock_trywrlock(cache->lock);
+ if (SVN_LOCK_IS_BUSY(status))
+ {
+ *success = FALSE;
+ status = APR_SUCCESS;
+ }
+ }
+
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't write-lock cache mutex"));
+ }
+#endif
+ return SVN_NO_ERROR;
+}
+
+/* If locking is supported for CACHE, acquire an unconditional write lock
+ * for it.
+ */
+static svn_error_t *
+force_write_lock_cache(svn_membuffer_t *cache)
+{
+#if APR_HAS_THREADS
+ apr_status_t status = apr_thread_rwlock_wrlock(cache->lock);
+ if (status)
+ return svn_error_wrap_apr(status,
+ _("Can't write-lock cache mutex"));
#endif
return SVN_NO_ERROR;
}
@@ -508,7 +566,7 @@
}
/* If supported, guard the execution of EXPR with a read lock to cache.
- * Macro has been modelled after SVN_MUTEX__WITH_LOCK.
+ * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
*/
#define WITH_READ_LOCK(cache, expr) \
do { \
@@ -517,12 +575,29 @@
} while (0)
/* If supported, guard the execution of EXPR with a write lock to cache.
- * Macro has been modelled after SVN_MUTEX__WITH_LOCK.
+ * Macro has been modeled after SVN_MUTEX__WITH_LOCK.
+ *
+ * The write lock process is complicated if we don't allow to wait for
+ * the lock: If we didn't get the lock, we may still need to remove an
+ * existing entry for the given key because that content is now stale.
+ * Once we discovered such an entry, we unconditionally do a blocking
+ * wait for the write lock. In case no old content could be found, a
+ * failing lock attempt is simply a no-op and we exit the macro.
*/
-#define WITH_WRITE_LOCK(cache, expr) \
-do { \
- SVN_ERR(write_lock_cache(cache)); \
- SVN_ERR(unlock_cache(cache, (expr))); \
+#define WITH_WRITE_LOCK(cache, expr) \
+do { \
+ svn_boolean_t got_lock = TRUE; \
+ SVN_ERR(write_lock_cache(cache, &got_lock)); \
+ if (!got_lock) \
+ { \
+ svn_boolean_t exists; \
+ SVN_ERR(entry_exists(cache, group_index, key, &exists)); \
+ if (exists) \
+ SVN_ERR(force_write_lock_cache(cache)); \
+ else \
+ break; \
+ } \
+ SVN_ERR(unlock_cache(cache, (expr))); \
} while (0)
/* Resolve a dictionary entry reference, i.e. return the entry
@@ -531,7 +606,7 @@
static APR_INLINE entry_t *
get_entry(svn_membuffer_t *cache, apr_uint32_t idx)
{
- return &cache->directory[idx / GROUP_SIZE][idx % GROUP_SIZE];
+ return &cache->directory[idx / GROUP_SIZE].entries[idx % GROUP_SIZE];
}
/* Get the entry references for the given ENTRY.
@@ -539,7 +614,11 @@
static APR_INLINE apr_uint32_t
get_index(svn_membuffer_t *cache, entry_t *entry)
{
- return (apr_uint32_t)(entry - (entry_t *)cache->directory);
+ apr_uint32_t group_index
+ = ((char *)entry - (char *)cache->directory) / sizeof(entry_group_t);
+
+ return group_index * GROUP_SIZE
+ + (apr_uint32_t)(entry - cache->directory[group_index].entries);
}
/* Remove the used ENTRY from the CACHE, i.e. make it "unused".
@@ -548,11 +627,16 @@
static void
drop_entry(svn_membuffer_t *cache, entry_t *entry)
{
+ /* the group that ENTRY belongs to plus a number of useful index values
+ */
apr_uint32_t idx = get_index(cache, entry);
+ apr_uint32_t group_index = idx / GROUP_SIZE;
+ entry_group_t *group = &cache->directory[group_index];
+ apr_uint32_t last_in_group = group_index * GROUP_SIZE + group->used - 1;
/* Only valid to be called for used entries.
*/
- assert(entry->offset != NO_OFFSET);
+ assert(idx <= last_in_group);
/* update global cache usage counters
*/
@@ -574,7 +658,7 @@
/* remove the first entry -> insertion may start at pos 0, now */
cache->current_data = 0;
}
- else
+ else
{
/* insertion may start right behind the previous entry */
entry_t *previous = get_entry(cache, entry->previous);
@@ -595,9 +679,35 @@
else
get_entry(cache, entry->next)->previous = entry->previous;
- /* Mark the entry as unused.
+ /* Move last entry into hole (if the removed one is not the last used).
+ * We need to do this since all used entries are at the beginning of
+ * the group's entries array.
*/
- entry->offset = NO_OFFSET;
+ if (idx < last_in_group)
+ {
+ /* copy the last used entry to the removed entry's index
+ */
+ *entry = group->entries[group->used-1];
+
+ /* update foreign links to new index
+ */
+ if (last_in_group == cache->next)
+ cache->next = idx;
+
+ if (entry->previous == NO_INDEX)
+ cache->first = idx;
+ else
+ get_entry(cache, entry->previous)->next = idx;
+
+ if (entry->next == NO_INDEX)
+ cache->last = idx;
+ else
+ get_entry(cache, entry->next)->previous = idx;
+ }
+
+ /* Update the number of used entries.
+ */
+ group->used--;
}
/* Insert ENTRY into the chain of used dictionary entries. The entry's
@@ -607,21 +717,28 @@
static void
insert_entry(svn_membuffer_t *cache, entry_t *entry)
{
+ /* the group that ENTRY belongs to plus a number of useful index values
+ */
apr_uint32_t idx = get_index(cache, entry);
+ apr_uint32_t group_index = idx / GROUP_SIZE;
+ entry_group_t *group = &cache->directory[group_index];
entry_t *next = cache->next == NO_INDEX
? NULL
: get_entry(cache, cache->next);
/* The entry must start at the beginning of the insertion window.
+ * It must also be the first unused entry in the group.
*/
assert(entry->offset == cache->current_data);
+ assert(idx == group_index * GROUP_SIZE + group->used);
cache->current_data = ALIGN_VALUE(entry->offset + entry->size);
- /* update global cache usage counters
+ /* update usage counters
*/
cache->used_entries++;
cache->data_used += entry->size;
entry->hit_count = 0;
+ group->used++;
/* update entry chain
*/
@@ -656,6 +773,11 @@
else
cache->first = idx;
}
+
+ /* The current insertion position must never point outside our
+ * data buffer.
+ */
+ assert(cache->current_data <= cache->data_size);
}
/* Map a KEY of 16 bytes to the CACHE and group that shall contain the
@@ -663,25 +785,16 @@
*/
static apr_uint32_t
get_group_index(svn_membuffer_t **cache,
- const apr_uint32_t *key)
+ entry_key_t key)
{
- apr_uint32_t hash;
-
- /* Get the group that *must* contain the entry. Fold the hash value
- * just to be sure (it should not be necessary for perfect hashes).
- */
- hash = key[0];
- hash = key[1] ^ ((hash >> 19) || (hash << 13));
- hash = key[2] ^ ((hash >> 19) || (hash << 13));
- hash = key[3] ^ ((hash >> 19) || (hash << 13));
-
- /* select the cache segment to use */
- *cache = &(*cache)[key[0] & ((*cache)->segment_count -1)];
-
- return hash % (*cache)->group_count;
+ svn_membuffer_t *segment0 = *cache;
+
+ /* select the cache segment to use. they have all the same group_count */
+ *cache = &segment0[key[0] & (segment0->segment_count -1)];
+ return key[1] % segment0->group_count;
}
-/* Reduce the hit count of ENTRY and update the accumunated hit info
+/* Reduce the hit count of ENTRY and update the accumulated hit info
* in CACHE accordingly.
*/
static APR_INLINE void
@@ -693,8 +806,8 @@
entry->hit_count -= hits_removed;
}
-/* Returns 0 if the entry group idenified by GROUP_INDEX in CACHE has not
- * been intialized, yet. In that case, this group can not data. Otherwise,
+/* Returns 0 if the entry group identified by GROUP_INDEX in CACHE has not
+ * been initialized, yet. In that case, this group can not data. Otherwise,
* a non-zero value is returned.
*/
static APR_INLINE unsigned char
@@ -709,12 +822,12 @@
}
/* Initializes the section of the directory in CACHE that contains
- * the entry group indentified by GROUP_INDEX. */
+ * the entry group identified by GROUP_INDEX. */
static void
initialize_group(svn_membuffer_t *cache, apr_uint32_t group_index)
{
unsigned char bit_mask;
- apr_uint32_t i, j;
+ apr_uint32_t i;
/* range of groups to initialize due to GROUP_INIT_GRANULARITY */
apr_uint32_t first_index =
@@ -724,8 +837,7 @@
last_index = cache->group_count;
for (i = first_index; i < last_index; ++i)
- for (j = 0; j < GROUP_SIZE; j++)
- cache->directory[i][j].offset = NO_OFFSET;
+ cache->directory[i].used = 0;
/* set the "initialized" bit for these groups */
bit_mask
@@ -749,16 +861,16 @@
static entry_t *
find_entry(svn_membuffer_t *cache,
apr_uint32_t group_index,
- const unsigned char *to_find,
+ const apr_uint64_t to_find[2],
svn_boolean_t find_empty)
{
- entry_t *group;
+ entry_group_t *group;
entry_t *entry = NULL;
- int i;
+ apr_size_t i;
/* get the group that *must* contain the entry
*/
- group = &cache->directory[group_index][0];
+ group = &cache->directory[group_index];
/* If the entry group has not been initialized, yet, there is no data.
*/
@@ -767,10 +879,11 @@
if (find_empty)
{
initialize_group(cache, group_index);
- entry = group;
+ entry = &group->entries[0];
/* initialize entry for the new key */
- memcpy(entry->key, to_find, KEY_SIZE);
+ entry->key[0] = to_find[0];
+ entry->key[1] = to_find[1];
}
return entry;
@@ -778,54 +891,53 @@
/* try to find the matching entry
*/
- for (i = 0; i < GROUP_SIZE; ++i)
- if (group[i].offset != NO_OFFSET &&
- !memcmp(to_find, group[i].key, KEY_SIZE))
+ for (i = 0; i < group->used; ++i)
+ if ( to_find[0] == group->entries[i].key[0]
+ && to_find[1] == group->entries[i].key[1])
{
/* found it
*/
- entry = &group[i];
+ entry = &group->entries[i];
if (find_empty)
drop_entry(cache, entry);
-
- return entry;
+ else
+ return entry;
}
/* None found. Are we looking for a free entry?
*/
if (find_empty)
{
- /* look for an empty entry and return that ...
+ /* if there is no empty entry, delete the oldest entry
*/
- for (i = 0; i < GROUP_SIZE; ++i)
- if (group[i].offset == NO_OFFSET)
- {
- entry = &group[i];
- break;
- }
-
- /* ... or, if none is empty, delete the oldest entry
- */
- if (entry == NULL)
+ if (group->used == GROUP_SIZE)
{
- entry = &group[0];
+ /* every entry gets the same chance of being removed.
+ * Otherwise, we free the first entry, fill it and
+ * remove it again on the next occasion without considering
+ * the other entries in this group.
+ */
+ entry = &group->entries[rand() % GROUP_SIZE];
for (i = 1; i < GROUP_SIZE; ++i)
- if (entry->hit_count > group[i].hit_count)
- entry = &group[i];
+ if (entry->hit_count > group->entries[i].hit_count)
+ entry = &group->entries[i];
/* for the entries that don't have been removed,
- * reduce their hitcounts to put them at a relative
+ * reduce their hit counts to put them at a relative
* disadvantage the next time.
*/
for (i = 0; i < GROUP_SIZE; ++i)
- if (entry != &group[i])
+ if (entry != &group->entries[i])
let_entry_age(cache, entry);
drop_entry(cache, entry);
}
- /* initialize entry for the new key */
- memcpy(entry->key, to_find, KEY_SIZE);
+ /* initialize entry for the new key
+ */
+ entry = &group->entries[group->used];
+ entry->key[0] = to_find[0];
+ entry->key[1] = to_find[1];
}
return entry;
@@ -847,7 +959,7 @@
/* Move the entry to the start of the empty / insertion section
* (if it isn't there already). Size-aligned moves are legal
- * since all offsets and block sizes share this same aligment.
+ * since all offsets and block sizes share this same alignment.
* Size-aligned moves tend to be faster than non-aligned ones
* because no "odd" bytes at the end need to special treatment.
*/
@@ -863,6 +975,11 @@
*/
cache->current_data = entry->offset + size;
cache->next = entry->next;
+
+ /* The current insertion position must never point outside our
+ * data buffer.
+ */
+ assert(cache->current_data <= cache->data_size);
}
/* If necessary, enlarge the insertion window until it is at least
@@ -904,7 +1021,7 @@
/* leave function as soon as the insertion window is large enough
*/
- if (end - cache->current_data >= size)
+ if (end >= size + cache->current_data)
return TRUE;
/* Don't be too eager to cache data. Smaller items will fit into
@@ -946,22 +1063,38 @@
}
else
{
- /* Roll the dice and determine a threshold somewhere from 0 up
- * to 2 times the average hit count.
- */
- average_hit_value = cache->hit_count / cache->used_entries;
- threshold = (average_hit_value+1) * (rand() % 4096) / 2048;
+ svn_boolean_t keep;
- /* Drop the entry from the end of the insertion window, if it
- * has been hit less than the threshold. Otherwise, keep it and
- * move the insertion window one entry further.
- */
- if (entry->hit_count >= threshold)
+ if (cache->hit_count > cache->used_entries)
+ {
+ /* Roll the dice and determine a threshold somewhere from 0 up
+ * to 2 times the average hit count.
+ */
+ average_hit_value = cache->hit_count / cache->used_entries;
+ threshold = (average_hit_value+1) * (rand() % 4096) / 2048;
+
+ keep = entry->hit_count >= threshold;
+ }
+ else
+ {
+ /* general hit count is low. Keep everything that got hit
+ * at all and assign some 50% survival chance to everything
+ * else.
+ */
+ keep = (entry->hit_count > 0) || (rand() & 1);
+ }
+
+ /* keepers or destroyers? */
+ if (keep)
{
move_entry(cache, entry);
}
else
{
+ /* Drop the entry from the end of the insertion window, if it
+ * has been hit less than the threshold. Otherwise, keep it and
+ * move the insertion window one entry further.
+ */
drop_size += entry->size;
drop_entry(cache, entry);
}
@@ -995,56 +1128,82 @@
return memory;
}
-/* Create a new membuffer cache instance. If the TOTAL_SIZE of the
- * memory i too small to accomodate the DICTIONARY_SIZE, the latte
- * will be resized automatically. Also, a minumum size is assured
- * for the DICTIONARY_SIZE. THREAD_SAFE may be FALSE, if there will
- * be no concurrent acccess to the CACHE returned.
- *
- * All allocations, in particular the data buffer and dictionary will
- * be made from POOL.
- */
svn_error_t *
svn_cache__membuffer_cache_create(svn_membuffer_t **cache,
apr_size_t total_size,
apr_size_t directory_size,
+ apr_size_t segment_count,
svn_boolean_t thread_safe,
+ svn_boolean_t allow_blocking_writes,
apr_pool_t *pool)
{
svn_membuffer_t *c;
- apr_uint32_t segment_count_shift = 0;
- apr_uint32_t segment_count = 1;
-
apr_uint32_t seg;
apr_uint32_t group_count;
apr_uint32_t group_init_size;
apr_uint64_t data_size;
apr_uint64_t max_entry_size;
- /* Determine a reasonable number of cache segments. Segmentation is
- * only useful for multi-threaded / multi-core servers as it reduces
- * lock contention on these systems.
- *
- * But on these systems, we can assume that ample memory has been
- * allocated to this cache. Smaller caches should not be segmented
- * as this severely limites the maximum size of cachable items.
- *
- * Segments should not be smaller than 32MB and max. cachable item
- * size should grow as fast as segmentation.
+ /* Limit the total size (only relevant if we can address > 4GB)
*/
- while (((2 * MIN_SEGMENT_SIZE) << (2 * segment_count_shift)) < total_size)
- ++segment_count_shift;
+#if APR_SIZEOF_VOIDP > 4
+ if (total_size > MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT)
+ total_size = MAX_SEGMENT_SIZE * MAX_SEGMENT_COUNT;
+#endif
- segment_count = 1 << segment_count_shift;
+ /* Limit the segment count
+ */
+ if (segment_count > MAX_SEGMENT_COUNT)
+ segment_count = MAX_SEGMENT_COUNT;
+ if (segment_count * MIN_SEGMENT_SIZE > total_size)
+ segment_count = total_size / MIN_SEGMENT_SIZE;
+
+ /* The segment count must be a power of two. Round it down as necessary.
+ */
+ while ((segment_count & (segment_count-1)) != 0)
+ segment_count &= segment_count-1;
+
+ /* if the caller hasn't provided a reasonable segment count or the above
+ * limitations set it to 0, derive one from the absolute cache size
+ */
+ if (segment_count < 1)
+ {
+ /* Determine a reasonable number of cache segments. Segmentation is
+ * only useful for multi-threaded / multi-core servers as it reduces
+ * lock contention on these systems.
+ *
+ * But on these systems, we can assume that ample memory has been
+ * allocated to this cache. Smaller caches should not be segmented
+ * as this severely limits the maximum size of cachable items.
+ *
+ * Segments should not be smaller than 32MB and max. cachable item
+ * size should grow as fast as segmentation.
+ */
+
+ apr_uint32_t segment_count_shift = 0;
+ while (((2 * DEFAULT_MIN_SEGMENT_SIZE) << (2 * segment_count_shift))
+ < total_size)
+ ++segment_count_shift;
+
+ segment_count = 1 << segment_count_shift;
+ }
+
+ /* If we have an extremely large cache (>512 GB), the default segment
+ * size may exceed the amount allocatable as one chunk. In that case,
+ * increase segmentation until we are under the threshold.
+ */
+ while ( total_size / segment_count > MAX_SEGMENT_SIZE
+ && segment_count < MAX_SEGMENT_COUNT)
+ segment_count *= 2;
/* allocate cache as an array of segments / cache objects */
c = apr_palloc(pool, segment_count * sizeof(*c));
/* Split total cache size into segments of equal size
*/
- total_size >>= segment_count_shift;
- directory_size >>= segment_count_shift;
+ total_size /= segment_count;
+ directory_size /= segment_count;
/* prevent pathological conditions: ensure a certain minimum cache size
*/
@@ -1061,8 +1220,10 @@
/* limit the data size to what we can address.
* Note that this cannot overflow since all values are of size_t.
+ * Also, make it a multiple of the item placement granularity to
+ * prevent subtle overflows.
*/
- data_size = total_size - directory_size;
+ data_size = ALIGN_VALUE(total_size - directory_size + 1) - ITEM_ALIGNMENT;
/* For cache sizes > 4TB, individual cache segments will be larger
* than 16GB allowing for >4GB entries. But caching chunks larger
@@ -1072,7 +1233,7 @@
? MAX_ITEM_SIZE
: data_size / 4;
- /* to keep the entries small, we use 32 bit indices only
+ /* to keep the entries small, we use 32 bit indexes only
* -> we need to ensure that no more then 4G entries exist.
*
* Note, that this limit could only be exceeded in a very
@@ -1137,6 +1298,10 @@
if (status)
return svn_error_wrap_apr(status, _("Can't create cache mutex"));
}
+
+ /* Select the behavior of write operations.
+ */
+ c[seg].allow_blocking_writes = allow_blocking_writes;
#endif
}
@@ -1146,6 +1311,40 @@
return SVN_NO_ERROR;
}
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND and set *FOUND accordingly.
+ *
+ * Note: This function requires the caller to serialize access.
+ * Don't call it directly, call entry_exists instead.
+ */
+static svn_error_t *
+entry_exists_internal(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ svn_boolean_t *found)
+{
+ *found = find_entry(cache, group_index, to_find, FALSE) != NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Look for the cache entry in group GROUP_INDEX of CACHE, identified
+ * by the hash value TO_FIND and set *FOUND accordingly.
+ */
+static svn_error_t *
+entry_exists(svn_membuffer_t *cache,
+ apr_uint32_t group_index,
+ entry_key_t to_find,
+ svn_boolean_t *found)
+{
+ WITH_READ_LOCK(cache,
+ entry_exists_internal(cache,
+ group_index,
+ to_find,
+ found));
+
+ return SVN_NO_ERROR;
+}
+
/* Try to insert the serialized item given in BUFFER with SIZE into
* the group GROUP_INDEX of CACHE and uniquely identify it by hash
@@ -1161,13 +1360,39 @@
*/
static svn_error_t *
membuffer_cache_set_internal(svn_membuffer_t *cache,
- const unsigned char *to_find,
+ entry_key_t to_find,
apr_uint32_t group_index,
char *buffer,
apr_size_t size,
DEBUG_CACHE_MEMBUFFER_TAG_ARG
apr_pool_t *scratch_pool)
{
+ /* first, look for a previous entry for the given key */
+ entry_t *entry = find_entry(cache, group_index, to_find, FALSE);
+
+ /* if there is an old version of that entry and the new data fits into
+ * the old spot, just re-use that space. */
+ if (buffer && entry && entry->size >= size)
+ {
+ cache->data_used += size - entry->size;
+ entry->size = size;
+
+#ifdef SVN_DEBUG_CACHE_MEMBUFFER
+
+ /* Remember original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, buffer, size, scratch_pool));
+ memcpy(&entry->tag, tag, sizeof(*tag));
+
+#endif
+
+ if (size)
+ memcpy(cache->data + entry->offset, buffer, size);
+
+ cache->total_writes++;
+ return SVN_NO_ERROR;
+ }
+
/* if necessary, enlarge the insertion window.
*/
if ( buffer != NULL
@@ -1176,9 +1401,9 @@
{
/* Remove old data for this key, if that exists.
* Get an unused entry for the key and and initialize it with
- * the serialized item's (future) posion within data buffer.
+ * the serialized item's (future) position within data buffer.
*/
- entry_t *entry = find_entry(cache, group_index, to_find, TRUE);
+ entry = find_entry(cache, group_index, to_find, TRUE);
entry->size = size;
entry->offset = cache->current_data;
@@ -1191,26 +1416,29 @@
#endif
+ /* Link the entry properly.
+ */
+ insert_entry(cache, entry);
+
/* Copy the serialized item data into the cache.
*/
if (size)
memcpy(cache->data + entry->offset, buffer, size);
- /* Link the entry properly.
- */
- insert_entry(cache, entry);
cache->total_writes++;
}
else
{
/* if there is already an entry for this key, drop it.
*/
- find_entry(cache, group_index, to_find, TRUE);
+ if (entry)
+ drop_entry(cache, entry);
}
+
return SVN_NO_ERROR;
}
-/* Try to insert the ITEM and use the KEY to unqiuely identify it.
+/* Try to insert the ITEM and use the KEY to uniquely identify it.
* However, there is no guarantee that it will actually be put into
* the cache. If there is already some data associated to the KEY,
* it will be removed from the cache even if the new data cannot
@@ -1221,7 +1449,7 @@
*/
static svn_error_t *
membuffer_cache_set(svn_membuffer_t *cache,
- const void *key,
+ entry_key_t key,
void *item,
svn_cache__serialize_func_t serializer,
DEBUG_CACHE_MEMBUFFER_TAG_ARG
@@ -1265,7 +1493,7 @@
static svn_error_t *
membuffer_cache_get_internal(svn_membuffer_t *cache,
apr_uint32_t group_index,
- const unsigned char *to_find,
+ entry_key_t to_find,
char **buffer,
apr_size_t *item_size,
DEBUG_CACHE_MEMBUFFER_TAG_ARG
@@ -1302,7 +1530,7 @@
/* Compare original content, type and key (hashes)
*/
- SVN_ERR(store_content_part(tag, buffer, entry->size, result_pool));
+ SVN_ERR(store_content_part(tag, *buffer, entry->size, result_pool));
SVN_ERR(assert_equal_tags(&entry->tag, tag));
#endif
@@ -1325,7 +1553,7 @@
*/
static svn_error_t *
membuffer_cache_get(svn_membuffer_t *cache,
- const void *key,
+ entry_key_t key,
void **item,
svn_cache__deserialize_func_t deserializer,
DEBUG_CACHE_MEMBUFFER_TAG_ARG
@@ -1372,7 +1600,7 @@
static svn_error_t *
membuffer_cache_get_partial_internal(svn_membuffer_t *cache,
apr_uint32_t group_index,
- const unsigned char *to_find,
+ entry_key_t to_find,
void **item,
svn_boolean_t *found,
svn_cache__partial_getter_func_t deserializer,
@@ -1431,7 +1659,7 @@
*/
static svn_error_t *
membuffer_cache_get_partial(svn_membuffer_t *cache,
- const void *key,
+ entry_key_t key,
void **item,
svn_boolean_t *found,
svn_cache__partial_getter_func_t deserializer,
@@ -1463,7 +1691,7 @@
static svn_error_t *
membuffer_cache_set_partial_internal(svn_membuffer_t *cache,
apr_uint32_t group_index,
- const unsigned char *to_find,
+ entry_key_t to_find,
svn_cache__partial_setter_func_t func,
void *baton,
DEBUG_CACHE_MEMBUFFER_TAG_ARG
@@ -1504,7 +1732,7 @@
#endif
- /* modify it, preferrably in-situ.
+ /* modify it, preferably in-situ.
*/
err = func((void **)&data, &size, baton, scratch_pool);
@@ -1531,6 +1759,7 @@
{
/* Write the new entry.
*/
+ entry = find_entry(cache, group_index, to_find, TRUE);
entry->size = size;
entry->offset = cache->current_data;
if (size)
@@ -1539,17 +1768,17 @@
/* Link the entry properly.
*/
insert_entry(cache, entry);
+ }
+ }
#ifdef SVN_DEBUG_CACHE_MEMBUFFER
- /* Remember original content, type and key (hashes)
- */
- SVN_ERR(store_content_part(tag, data, size, scratch_pool));
- memcpy(&entry->tag, tag, sizeof(*tag));
+ /* Remember original content, type and key (hashes)
+ */
+ SVN_ERR(store_content_part(tag, data, size, scratch_pool));
+ memcpy(&entry->tag, tag, sizeof(*tag));
#endif
- }
- }
}
}
@@ -1563,7 +1792,7 @@
*/
static svn_error_t *
membuffer_cache_set_partial(svn_membuffer_t *cache,
- const void *key,
+ entry_key_t key,
svn_cache__partial_setter_func_t func,
void *baton,
DEBUG_CACHE_MEMBUFFER_TAG_ARG
@@ -1575,7 +1804,7 @@
WITH_WRITE_LOCK(cache,
membuffer_cache_set_partial_internal
(cache, group_index, key, func, baton,
- DEBUG_CACHE_MEMBUFFER_TAG_ARG
+ DEBUG_CACHE_MEMBUFFER_TAG
scratch_pool));
/* done here -> unlock the cache
@@ -1588,11 +1817,11 @@
* Because membuffer caches tend to be very large, there will be rather few
* of them (usually only one). Thus, the same instance shall be used as the
* backend to many application-visible svn_cache__t instances. This should
- * also achive global resource usage fairness.
+ * also achieve global resource usage fairness.
*
- * To accomodate items from multiple resources, the individual keys must be
- * unique over all sources. This is achived by simply adding a prefix key
- * that unambigously identifies the item's context (e.g. path to the
+ * To accommodate items from multiple resources, the individual keys must be
+ * unique over all sources. This is achieved by simply adding a prefix key
+ * that unambiguously identifies the item's context (e.g. path to the
* respective repository). The prefix will be set upon construction of the
* svn_cache__t instance.
*/
@@ -1619,7 +1848,7 @@
* This makes (very likely) our keys different from all keys used
* by other svn_membuffer_cache_t instances.
*/
- apr_uint64_t prefix [APR_MD5_DIGESTSIZE / sizeof(apr_uint64_t)];
+ entry_key_t prefix;
/* A copy of the unmodified prefix. It is being used as a user-visible
* ID for this cache instance.
@@ -1633,7 +1862,7 @@
/* Temporary buffer containing the hash key for the current access
*/
- apr_uint64_t combined_key [APR_MD5_DIGESTSIZE / sizeof(apr_uint64_t)];
+ entry_key_t combined_key;
/* a pool for temporary allocations during get() and set()
*/
@@ -1673,7 +1902,31 @@
if (key_len == APR_HASH_KEY_STRING)
key_len = strlen((const char *) key);
- apr_md5((unsigned char*)cache->combined_key, key, key_len);
+ if (key_len < 16)
+ {
+ apr_uint32_t data[4] = { 0 };
+ memcpy(data, key, key_len);
+
+ svn__pseudo_md5_15((apr_uint32_t *)cache->combined_key, data);
+ }
+ else if (key_len < 32)
+ {
+ apr_uint32_t data[8] = { 0 };
+ memcpy(data, key, key_len);
+
+ svn__pseudo_md5_31((apr_uint32_t *)cache->combined_key, data);
+ }
+ else if (key_len < 64)
+ {
+ apr_uint32_t data[16] = { 0 };
+ memcpy(data, key, key_len);
+
+ svn__pseudo_md5_63((apr_uint32_t *)cache->combined_key, data);
+ }
+ else
+ {
+ apr_md5((unsigned char*)cache->combined_key, key, key_len);
+ }
cache->combined_key[0] ^= cache->prefix[0];
cache->combined_key[1] ^= cache->prefix[1];
@@ -1895,11 +2148,11 @@
svn_membuffer_cache_t *cache = cache_void;
apr_uint32_t i;
- /* cache frontend specific data */
+ /* cache front-end specific data */
info->id = apr_pstrdup(result_pool, cache->full_prefix);
- /* collect info from shared cache backend */
+ /* collect info from shared cache back-end */
info->data_size = 0;
info->used_size = 0;
diff --git a/subversion/libsvn_subr/cache-memcache.c b/subversion/libsvn_subr/cache-memcache.c
index b9c138c..5332d04 100644
--- a/subversion/libsvn_subr/cache-memcache.c
+++ b/subversion/libsvn_subr/cache-memcache.c
@@ -470,7 +470,8 @@
0, /* min connections */
5, /* soft max connections */
10, /* hard max connections */
- 50, /* connection time to live (secs) */
+ /* time to live (in microseconds) */
+ apr_time_from_sec(50),
&server);
if (apr_err != APR_SUCCESS)
{
diff --git a/subversion/libsvn_subr/cache_config.c b/subversion/libsvn_subr/cache_config.c
index adce479..6b3edae 100644
--- a/subversion/libsvn_subr/cache_config.c
+++ b/subversion/libsvn_subr/cache_config.c
@@ -52,7 +52,7 @@
* has little impact on performance and a more modest
* value (< 100) may be more suitable.
*/
-#ifdef APR_HAS_THREADS
+#if APR_HAS_THREADS
FALSE /* assume multi-threaded operation.
* Because this simply activates proper synchronization
* between threads, it is a safe default.
@@ -118,8 +118,10 @@
err = svn_cache__membuffer_cache_create(
&new_cache,
(apr_size_t)cache_size,
- (apr_size_t)(cache_size / 16),
+ (apr_size_t)(cache_size / 10),
+ 0,
! svn_cache_config_get()->single_threaded,
+ FALSE,
pool);
/* Some error occured. Most likely it's an OOM error but we don't
diff --git a/subversion/libsvn_subr/cmdline.c b/subversion/libsvn_subr/cmdline.c
index da697f4..18a2807 100644
--- a/subversion/libsvn_subr/cmdline.c
+++ b/subversion/libsvn_subr/cmdline.c
@@ -526,6 +526,13 @@
if (non_interactive == FALSE)
{
+ svn_boolean_t ssl_client_cert_file_prompt;
+
+ SVN_ERR(svn_config_get_bool(cfg, &ssl_client_cert_file_prompt,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_SSL_CLIENT_CERT_FILE_PROMPT,
+ FALSE));
+
/* Two basic prompt providers: username/password, and just username. */
svn_auth_get_simple_prompt_provider(&provider,
svn_cmdline_auth_simple_prompt,
@@ -539,19 +546,23 @@
2, /* retry limit */ pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
- /* Three ssl prompt providers, for server-certs, client-certs,
- and client-cert-passphrases. */
+ /* SSL prompt providers: server-certs and client-cert-passphrases. */
svn_auth_get_ssl_server_trust_prompt_provider
(&provider, svn_cmdline_auth_ssl_server_trust_prompt, pb, pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
- svn_auth_get_ssl_client_cert_prompt_provider
- (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
- APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
-
svn_auth_get_ssl_client_cert_pw_prompt_provider
(&provider, svn_cmdline_auth_ssl_client_cert_pw_prompt, pb, 2, pool);
APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+
+ /* If configuration allows, add a provider for client-cert path
+ prompting, too. */
+ if (ssl_client_cert_file_prompt)
+ {
+ svn_auth_get_ssl_client_cert_prompt_provider
+ (&provider, svn_cmdline_auth_ssl_client_cert_prompt, pb, 2, pool);
+ APR_ARRAY_PUSH(providers, svn_auth_provider_object_t *) = provider;
+ }
}
else if (trust_server_cert)
{
@@ -631,6 +642,7 @@
svn_cmdline__print_xml_prop(svn_stringbuf_t **outstr,
const char* propname,
svn_string_t *propval,
+ svn_boolean_t inherited_prop,
apr_pool_t *pool)
{
const char *xml_safe;
@@ -654,16 +666,22 @@
}
if (encoding)
- svn_xml_make_open_tag(outstr, pool, svn_xml_protect_pcdata,
- "property", "name", propname,
- "encoding", encoding, NULL);
+ svn_xml_make_open_tag(
+ outstr, pool, svn_xml_protect_pcdata,
+ inherited_prop ? "inherited_property" : "property",
+ "name", propname,
+ "encoding", encoding, NULL);
else
- svn_xml_make_open_tag(outstr, pool, svn_xml_protect_pcdata,
- "property", "name", propname, NULL);
+ svn_xml_make_open_tag(
+ outstr, pool, svn_xml_protect_pcdata,
+ inherited_prop ? "inherited_property" : "property",
+ "name", propname, NULL);
svn_stringbuf_appendcstr(*outstr, xml_safe);
- svn_xml_make_close_tag(outstr, pool, "property");
+ svn_xml_make_close_tag(
+ outstr, pool,
+ inherited_prop ? "inherited_property" : "property");
return;
}
diff --git a/subversion/libsvn_subr/config.c b/subversion/libsvn_subr/config.c
index 58bfc1f..4410a69 100644
--- a/subversion/libsvn_subr/config.c
+++ b/subversion/libsvn_subr/config.c
@@ -555,6 +555,43 @@
*opt_x_valuep = NULL;
}
+static void
+svn_config_addsection(svn_config_t *cfg,
+ const char *section,
+ cfg_section_t **sec)
+{
+ cfg_section_t *s;
+
+ s = apr_palloc(cfg->pool, sizeof(cfg_section_t));
+ s->name = apr_pstrdup(cfg->pool, section);
+ if(cfg->section_names_case_sensitive)
+ s->hash_key = s->name;
+ else
+ s->hash_key = make_hash_key(apr_pstrdup(cfg->pool, section));
+ s->options = apr_hash_make(cfg->pool);
+ apr_hash_set(cfg->sections, s->hash_key, APR_HASH_KEY_STRING, s);
+
+ *sec = s;
+}
+
+static void
+svn_config_create_option(cfg_option_t **opt,
+ const char *option,
+ const char *value,
+ apr_pool_t *pool)
+{
+ cfg_option_t *o;
+
+ o = apr_palloc(pool, sizeof(cfg_option_t));
+ o->name = apr_pstrdup(pool, option);
+ o->hash_key = make_hash_key(apr_pstrdup(pool, option));
+
+ o->value = apr_pstrdup(pool, value);
+ o->x_value = NULL;
+ o->expanded = FALSE;
+
+ *opt = o;
+}
void
@@ -612,25 +649,12 @@
}
/* Create a new option */
- opt = apr_palloc(cfg->pool, sizeof(*opt));
- opt->name = apr_pstrdup(cfg->pool, option);
- opt->hash_key = make_hash_key(apr_pstrdup(cfg->pool, option));
-
- opt->value = apr_pstrdup(cfg->pool, value);
- opt->x_value = NULL;
- opt->expanded = FALSE;
+ svn_config_create_option(&opt, option, value, cfg->pool);
if (sec == NULL)
{
/* Even the section doesn't exist. Create it. */
- sec = apr_palloc(cfg->pool, sizeof(*sec));
- sec->name = apr_pstrdup(cfg->pool, section);
- if(cfg->section_names_case_sensitive)
- sec->hash_key = sec->name;
- else
- sec->hash_key = make_hash_key(apr_pstrdup(cfg->pool, section));
- sec->options = apr_hash_make(cfg->pool);
- apr_hash_set(cfg->sections, sec->hash_key, APR_HASH_KEY_STRING, sec);
+ svn_config_addsection(cfg, section, &sec);
}
apr_hash_set(sec->options, opt->hash_key, APR_HASH_KEY_STRING, opt);
@@ -951,6 +975,94 @@
return retval;
}
+
+svn_error_t *
+svn_config_dup(svn_config_t **cfgp,
+ svn_config_t *src,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *sectidx;
+ apr_hash_index_t *optidx;
+
+ *cfgp = 0;
+ SVN_ERR(svn_config_create(cfgp, FALSE, pool));
+
+ (*cfgp)->x_values = src->x_values;
+ (*cfgp)->section_names_case_sensitive = src->section_names_case_sensitive;
+
+ for (sectidx = apr_hash_first(pool, src->sections);
+ sectidx != NULL;
+ sectidx = apr_hash_next(sectidx))
+ {
+ const void *sectkey;
+ void *sectval;
+ apr_ssize_t sectkeyLength;
+ cfg_section_t * srcsect;
+ cfg_section_t * destsec;
+
+ apr_hash_this(sectidx, §key, §keyLength, §val);
+ srcsect = sectval;
+
+ svn_config_addsection(*cfgp, srcsect->name, &destsec);
+
+ for (optidx = apr_hash_first(pool, srcsect->options);
+ optidx != NULL;
+ optidx = apr_hash_next(optidx))
+ {
+ const void *optkey;
+ void *optval;
+ apr_ssize_t optkeyLength;
+ cfg_option_t *srcopt;
+ cfg_option_t *destopt;
+
+ apr_hash_this(optidx, &optkey, &optkeyLength, &optval);
+ srcopt = optval;
+
+ svn_config_create_option(&destopt, srcopt->name, srcopt->value, pool);
+
+ destopt->value = apr_pstrdup(pool, srcopt->value);
+ destopt->x_value = apr_pstrdup(pool, srcopt->x_value);
+ destopt->expanded = srcopt->expanded;
+ apr_hash_set(destsec->options,
+ apr_pstrdup(pool, (const char*)optkey),
+ optkeyLength, destopt);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_config_copy_config(apr_hash_t **cfg_hash,
+ apr_hash_t *src_hash,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *cidx;
+
+ *cfg_hash = apr_hash_make(pool);
+ for (cidx = apr_hash_first(pool, src_hash);
+ cidx != NULL;
+ cidx = apr_hash_next(cidx))
+ {
+ const void *ckey;
+ void *cval;
+ apr_ssize_t ckeyLength;
+ svn_config_t * srcconfig;
+ svn_config_t * destconfig;
+
+ apr_hash_this(cidx, &ckey, &ckeyLength, &cval);
+ srcconfig = cval;
+
+ SVN_ERR(svn_config_dup(&destconfig, srcconfig, pool));
+
+ apr_hash_set(*cfg_hash,
+ apr_pstrdup(pool, (const char*)ckey),
+ ckeyLength, destconfig);
+ }
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t*
svn_config_get_server_setting_int(svn_config_t *cfg,
const char *server_group,
diff --git a/subversion/libsvn_subr/config_file.c b/subversion/libsvn_subr/config_file.c
index 4a39849..12b0a1f 100644
--- a/subversion/libsvn_subr/config_file.c
+++ b/subversion/libsvn_subr/config_file.c
@@ -1034,6 +1034,13 @@
"# kwallet-svn-application-name-with-pid = yes" NL
#endif
"###" NL
+ "### Set ssl-client-cert-file-prompt to 'yes' to cause the client" NL
+ "### to prompt for a path to a client cert file when the server" NL
+ "### requests a client cert but no client cert file is found in the" NL
+ "### expected place (see the 'ssl-client-cert-file' option in the" NL
+ "### 'servers' configuration file). Defaults to 'no'." NL
+ "# ssl-client-cert-file-prompt = no" NL
+ "###" NL
"### The rest of the [auth] section in this file has been deprecated."
NL
"### Both 'store-passwords' and 'store-auth-creds' can now be" NL
@@ -1153,7 +1160,12 @@
"# *.png = svn:mime-type=image/png" NL
"# *.jpg = svn:mime-type=image/jpeg" NL
"# Makefile = svn:eol-style=native" NL
- "" NL;
+ "" NL
+ "### Section for configuring working copies." NL
+ "[working-copy]" NL
+ "### Set to true to enable exclusive SQLite locking. Some clients" NL
+ "### may not support exclusive locking." NL
+ "# exclusive-locking = false" NL;
err = svn_io_file_open(&f, path,
(APR_WRITE | APR_CREATE | APR_EXCL),
diff --git a/subversion/libsvn_subr/deprecated.c b/subversion/libsvn_subr/deprecated.c
index 7da0568..abb98ff 100644
--- a/subversion/libsvn_subr/deprecated.c
+++ b/subversion/libsvn_subr/deprecated.c
@@ -36,6 +36,7 @@
#include "svn_path.h"
#include "svn_opt.h"
#include "svn_cmdline.h"
+#include "svn_version.h"
#include "svn_pools.h"
#include "svn_dso.h"
#include "svn_mergeinfo.h"
@@ -604,8 +605,11 @@
}
}
else if (print_version) /* just --version */
- SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
- quiet, FALSE, pool));
+ {
+ SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
+ svn_version_extended(FALSE, pool),
+ quiet, FALSE, pool));
+ }
else if (os && !targets->nelts) /* `-h', `--help', or `help' */
svn_opt_print_generic_help(header,
cmd_table,
diff --git a/subversion/libsvn_subr/dirent_uri.c b/subversion/libsvn_subr/dirent_uri.c
index ac9d096..a35d00b 100644
--- a/subversion/libsvn_subr/dirent_uri.c
+++ b/subversion/libsvn_subr/dirent_uri.c
@@ -1401,7 +1401,7 @@
apr_size_t len = strlen(parent_dirent);
apr_size_t root_len;
- if (0 != memcmp(parent_dirent, child_dirent, len))
+ if (0 != strncmp(parent_dirent, child_dirent, len))
return NULL; /* parent_dirent is no ancestor of child_dirent */
if (child_dirent[len] == 0)
@@ -1459,7 +1459,7 @@
if (len == 0)
return child_relpath;
- if (0 != memcmp(parent_relpath, child_relpath, len))
+ if (0 != strncmp(parent_relpath, child_relpath, len))
return NULL; /* parent_relpath is no ancestor of child_relpath */
if (child_relpath[len] == 0)
@@ -1482,7 +1482,7 @@
assert(svn_uri_is_canonical(parent_uri, NULL));
assert(svn_uri_is_canonical(child_uri, NULL));
- if (0 != memcmp(parent_uri, child_uri, len))
+ if (0 != strncmp(parent_uri, child_uri, len))
return NULL; /* parent_uri is no ancestor of child_uri */
if (child_uri[len] == 0)
diff --git a/subversion/libsvn_subr/eol.c b/subversion/libsvn_subr/eol.c
index 180010f..88a6a37 100644
--- a/subversion/libsvn_subr/eol.c
+++ b/subversion/libsvn_subr/eol.c
@@ -32,18 +32,6 @@
/* Machine-word-sized masks used in svn_eol__find_eol_start.
*/
-#if APR_SIZEOF_VOIDP == 8
-# define LOWER_7BITS_SET 0x7f7f7f7f7f7f7f7f
-# define BIT_7_SET 0x8080808080808080
-# define R_MASK 0x0a0a0a0a0a0a0a0a
-# define N_MASK 0x0d0d0d0d0d0d0d0d
-#else
-# define LOWER_7BITS_SET 0x7f7f7f7f
-# define BIT_7_SET 0x80808080
-# define R_MASK 0x0a0a0a0a
-# define N_MASK 0x0d0d0d0d
-#endif
-
char *
svn_eol__find_eol_start(char *buf, apr_size_t len)
{
@@ -69,19 +57,19 @@
/* This is a variant of the well-known strlen test: */
apr_uintptr_t chunk = *(const apr_uintptr_t *)buf;
- /* A byte in R_TEST is \0, iff it was \r in *BUF.
- * Similarly, N_TEST is an indicator for \n. */
- apr_uintptr_t r_test = chunk ^ R_MASK;
- apr_uintptr_t n_test = chunk ^ N_MASK;
+ /* A byte in SVN__R_TEST is \0, iff it was \r in *BUF.
+ * Similarly, SVN__N_TEST is an indicator for \n. */
+ apr_uintptr_t r_test = chunk ^ SVN__R_MASK;
+ apr_uintptr_t n_test = chunk ^ SVN__N_MASK;
- /* A byte in R_TEST can by < 0x80, iff it has been \0 before
- * (i.e. \r in *BUF). Dito for N_TEST. */
- r_test |= (r_test & LOWER_7BITS_SET) + LOWER_7BITS_SET;
- n_test |= (n_test & LOWER_7BITS_SET) + LOWER_7BITS_SET;
+ /* A byte in SVN__R_TEST can by < 0x80, iff it has been \0 before
+ * (i.e. \r in *BUF). Dito for SVN__N_TEST. */
+ r_test |= (r_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+ n_test |= (n_test & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
/* Check whether at least one of the words contains a byte <0x80
* (if one is detected, there was a \r or \n in CHUNK). */
- if ((r_test & n_test & BIT_7_SET) != BIT_7_SET)
+ if ((r_test & n_test & SVN__BIT_7_SET) != SVN__BIT_7_SET)
break;
}
diff --git a/subversion/libsvn_subr/error.c b/subversion/libsvn_subr/error.c
index 11fa2c5..53ab2b8 100644
--- a/subversion/libsvn_subr/error.c
+++ b/subversion/libsvn_subr/error.c
@@ -196,9 +196,14 @@
va_start(ap, fmt);
msg = apr_pvsprintf(err->pool, fmt, ap);
va_end(ap);
- err->message = apr_psprintf(err->pool, "%s%s%s", msg,
- (msg_apr) ? ": " : "",
- (msg_apr) ? msg_apr : "");
+ if (msg_apr)
+ {
+ err->message = apr_pstrcat(err->pool, msg, ": ", msg_apr, NULL);
+ }
+ else
+ {
+ err->message = msg;
+ }
}
return err;
diff --git a/subversion/libsvn_subr/gpg_agent.c b/subversion/libsvn_subr/gpg_agent.c
index fb65114..f0395c0 100644
--- a/subversion/libsvn_subr/gpg_agent.c
+++ b/subversion/libsvn_subr/gpg_agent.c
@@ -107,9 +107,13 @@
receive_from_gpg_agent(int sd, char *buf, size_t n)
{
int i = 0;
- int recvd;
+ size_t recvd;
char c;
+ /* Clear existing buffer content before reading response. */
+ if (n > 0)
+ *buf = '\0';
+
/* Require the message to fit into the buffer and be terminated
* with a newline. */
while (i < n)
@@ -322,7 +326,6 @@
display = getenv("DISPLAY");
if (display != NULL)
{
- request = apr_psprintf(pool, "OPTION display=%s\n", display);
if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool))
{
close(sd);
@@ -332,8 +335,8 @@
/* Create the CACHE_ID which will be generated based on REALMSTRING similar
to other password caching mechanisms. */
- svn_checksum(&digest, svn_checksum_md5, realmstring, strlen(realmstring),
- pool);
+ SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
+ strlen(realmstring), pool));
cache_id = svn_checksum_to_cstring(digest, pool);
password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c
index d0f51a5..8604160 100644
--- a/subversion/libsvn_subr/io.c
+++ b/subversion/libsvn_subr/io.c
@@ -138,6 +138,14 @@
#endif
#endif
+/* Forward declaration */
+static apr_status_t
+dir_is_empty(const char *dir, apr_pool_t *pool);
+static APR_INLINE svn_error_t *
+do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
+ const char *msg, const char *msg_no_name,
+ apr_pool_t *pool);
+
/* Local wrapper of svn_path_cstring_to_utf8() that does no copying on
* operating systems where APR always uses utf-8 as native path format */
static svn_error_t *
@@ -2058,8 +2066,12 @@
if (locktype == APR_FLOCK_EXCLUSIVE)
flags |= APR_WRITE;
+ /* locktype is never read after this block, so we don't need to bother
+ setting it. If that were to ever change, uncomment the following
+ block.
if (nonblocking)
locktype |= APR_FLOCK_NONBLOCK;
+ */
SVN_ERR(svn_io_file_open(&lockfile_handle, lock_file, flags,
APR_OS_DEFAULT,
@@ -2073,11 +2085,6 @@
/* Data consistency/coherency operations. */
-static APR_INLINE svn_error_t *
-do_io_file_wrapper_cleanup(apr_file_t *file, apr_status_t status,
- const char *msg, const char *msg_no_name,
- apr_pool_t *pool);
-
svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file,
apr_pool_t *pool)
{
@@ -3427,30 +3434,60 @@
svn_io_read_length_line(apr_file_t *file, char *buf, apr_size_t *limit,
apr_pool_t *pool)
{
+ /* variables */
+ apr_size_t total_read = 0;
+ svn_boolean_t eof = FALSE;
const char *name;
svn_error_t *err;
- apr_size_t i;
- char c;
+ apr_size_t buf_size = *limit;
- for (i = 0; i < *limit; i++)
+ while (buf_size > 0)
{
- SVN_ERR(svn_io_file_getc(&c, file, pool));
- /* Note: this error could be APR_EOF, which
- is totally fine. The caller should be aware of
- this. */
+ /* read a fair chunk of data at once. But don't get too ambitious
+ * as that would result in too much waste. Also make sure we can
+ * put a NUL after the last byte read.
+ */
+ apr_size_t to_read = buf_size < 129 ? buf_size - 1 : 128;
+ apr_size_t bytes_read = 0;
+ char *eol;
- if (c == '\n')
+ /* read data block (or just a part of it) */
+ SVN_ERR(svn_io_file_read_full2(file, buf, to_read,
+ &bytes_read, &eof, pool));
+
+ /* look or a newline char */
+ buf[bytes_read] = 0;
+ eol = strchr(buf, '\n');
+ if (eol)
{
- buf[i] = '\0';
- *limit = i;
+ apr_off_t offset = (eol + 1 - buf) - (apr_off_t)bytes_read;
+
+ *eol = 0;
+ *limit = total_read + (eol - buf);
+
+ /* correct the file pointer:
+ * appear as though we just had read the newline char
+ */
+ SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
+
return SVN_NO_ERROR;
}
- else
+ else if (eof)
{
- buf[i] = c;
+ /* no EOL found but we hit the end of the file.
+ * Generate a nice EOF error object and return it.
+ */
+ char dummy;
+ SVN_ERR(svn_io_file_getc(&dummy, file, pool));
}
+
+ /* next data chunk */
+ buf_size -= bytes_read;
+ buf += bytes_read;
+ total_read += bytes_read;
}
+ /* buffer limit has been exceeded without finding the EOL */
err = svn_io_file_name_get(&name, file, pool);
if (err)
name = NULL;
@@ -3689,11 +3726,6 @@
return SVN_NO_ERROR;
}
-/* Forward declaration */
-static apr_status_t
-dir_is_empty(const char *dir, apr_pool_t *pool);
-
-
svn_error_t *
svn_io_dir_remove_nonrecursive(const char *dirname, apr_pool_t *pool)
{
diff --git a/subversion/libsvn_subr/log.c b/subversion/libsvn_subr/log.c
index 8f4a058..9e0b22a 100644
--- a/subversion/libsvn_subr/log.c
+++ b/subversion/libsvn_subr/log.c
@@ -380,3 +380,17 @@
log_path = "/";
return apr_psprintf(pool, "replay %s r%ld", log_path, rev);
}
+
+const char *
+svn_log__get_inherited_props(const char *path,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ const char *log_path;
+
+ if (path && path[0] != '\0')
+ log_path = svn_path_uri_encode(path, pool);
+ else
+ log_path = "/";
+ return apr_psprintf(pool, "get-inherited-props %s r%ld", log_path, rev);
+}
diff --git a/subversion/libsvn_subr/mergeinfo.c b/subversion/libsvn_subr/mergeinfo.c
index e5fcf35..caeacbf 100644
--- a/subversion/libsvn_subr/mergeinfo.c
+++ b/subversion/libsvn_subr/mergeinfo.c
@@ -35,6 +35,7 @@
#include "private/svn_fspath.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
#include "svn_private_config.h"
#include "svn_hash.h"
#include "private/svn_dep_compat.h"
@@ -616,6 +617,7 @@
apr_pool_t *scratch_pool)
{
const char *pathname = "";
+ apr_ssize_t klen;
svn_rangelist_t *existing_rangelist;
svn_rangelist_t *rangelist = apr_array_make(scratch_pool, 1,
sizeof(svn_merge_range_t *));
@@ -700,14 +702,14 @@
leading slash, e.g. "trunk:4033\n/trunk:4039-4995". In the event
we encounter this we merge the rangelists together under a single
absolute path key. */
- existing_rangelist = apr_hash_get(hash, pathname, APR_HASH_KEY_STRING);
+ klen = strlen(pathname);
+ existing_rangelist = apr_hash_get(hash, pathname, klen);
if (existing_rangelist)
SVN_ERR(svn_rangelist_merge2(rangelist, existing_rangelist,
scratch_pool, scratch_pool));
- apr_hash_set(hash, apr_pstrdup(apr_hash_pool_get(hash), pathname),
- APR_HASH_KEY_STRING,
- svn_rangelist_dup(rangelist, apr_hash_pool_get(hash)));
+ apr_hash_set(hash, apr_pstrmemdup(apr_hash_pool_get(hash), pathname, klen),
+ klen, svn_rangelist_dup(rangelist, apr_hash_pool_get(hash)));
return SVN_NO_ERROR;
}
@@ -736,7 +738,7 @@
{
svn_error_t *err;
- *mergeinfo = apr_hash_make(pool);
+ *mergeinfo = svn_hash__make(pool);
err = parse_top(&input, input + strlen(input), *mergeinfo, pool);
/* Always return SVN_ERR_MERGEINFO_PARSE_ERROR as the topmost error. */
@@ -1559,29 +1561,28 @@
{
/* Record any deltas (additions or deletions). */
svn_rangelist_t *deleted_rangelist, *added_rangelist;
- from_rangelist = apr_hash_get(cb->from, path, APR_HASH_KEY_STRING);
- to_rangelist = apr_hash_get(cb->to, path, APR_HASH_KEY_STRING);
+ from_rangelist = apr_hash_get(cb->from, path, klen);
+ to_rangelist = apr_hash_get(cb->to, path, klen);
SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
from_rangelist, to_rangelist,
cb->consider_inheritance, cb->pool));
if (cb->deleted && deleted_rangelist->nelts > 0)
- apr_hash_set(cb->deleted, apr_pstrdup(cb->pool, path),
- APR_HASH_KEY_STRING, deleted_rangelist);
+ apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen),
+ klen, deleted_rangelist);
if (cb->added && added_rangelist->nelts > 0)
- apr_hash_set(cb->added, apr_pstrdup(cb->pool, path),
- APR_HASH_KEY_STRING, added_rangelist);
+ apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen),
+ klen, added_rangelist);
}
else if ((status == svn_hash_diff_key_a) && cb->deleted)
{
- from_rangelist = apr_hash_get(cb->from, path, APR_HASH_KEY_STRING);
- apr_hash_set(cb->deleted, apr_pstrdup(cb->pool, path),
- APR_HASH_KEY_STRING,
+ from_rangelist = apr_hash_get(cb->from, path, klen);
+ apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen), klen,
svn_rangelist_dup(from_rangelist, cb->pool));
}
else if ((status == svn_hash_diff_key_b) && cb->added)
{
- to_rangelist = apr_hash_get(cb->to, path, APR_HASH_KEY_STRING);
- apr_hash_set(cb->added, apr_pstrdup(cb->pool, path), APR_HASH_KEY_STRING,
+ to_rangelist = apr_hash_get(cb->to, path, klen);
+ apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen), klen,
svn_rangelist_dup(to_rangelist, cb->pool));
}
return SVN_NO_ERROR;
@@ -1618,17 +1619,17 @@
if (from && to == NULL)
{
*deleted = svn_mergeinfo_dup(from, result_pool);
- *added = apr_hash_make(result_pool);
+ *added = svn_hash__make(result_pool);
}
else if (from == NULL && to)
{
- *deleted = apr_hash_make(result_pool);
+ *deleted = svn_hash__make(result_pool);
*added = svn_mergeinfo_dup(to, result_pool);
}
else
{
- *deleted = apr_hash_make(result_pool);
- *added = apr_hash_make(result_pool);
+ *deleted = svn_hash__make(result_pool);
+ *added = svn_hash__make(result_pool);
if (from && to)
{
@@ -1648,17 +1649,77 @@
svn_boolean_t consider_inheritance,
apr_pool_t *pool)
{
- if (apr_hash_count(info1) == apr_hash_count(info2))
+ apr_hash_index_t *hi;
+
+ *is_equal = FALSE;
+
+ /* special cases: at least one side has no merge info */
+ if (info1 == NULL && info2 == NULL)
{
- svn_mergeinfo_t deleted, added;
- SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, info1, info2,
- consider_inheritance, pool, pool));
- *is_equal = apr_hash_count(deleted) == 0 && apr_hash_count(added) == 0;
+ *is_equal = TRUE;
+ return SVN_NO_ERROR;
}
- else
+
+ if (info1 == NULL || info2 == NULL)
+ return SVN_NO_ERROR;
+
+ /* trivial case: different number of paths -> unequal */
+ if (apr_hash_count(info1) != apr_hash_count(info2))
+ return SVN_NO_ERROR;
+
+ /* compare range lists for all paths */
+ for (hi = apr_hash_first(pool, info1); hi; hi = apr_hash_next(hi))
{
- *is_equal = FALSE;
+ const char *key;
+ apr_ssize_t key_length;
+ svn_rangelist_t *lhs, *rhs;
+ int i;
+ svn_rangelist_t *deleted, *added;
+
+ /* get both path lists */
+ apr_hash_this(hi, (const void**)&key, &key_length, (void **)&lhs);
+ rhs = apr_hash_get(info2, key, key_length);
+
+ /* missing on one side? */
+ if (rhs == NULL)
+ return SVN_NO_ERROR;
+
+ /* quick compare: the range lists will often be a perfect match */
+ if (lhs->nelts == rhs->nelts)
+ {
+ for (i = 0; i < lhs->nelts; ++i)
+ {
+ svn_merge_range_t *lrange
+ = APR_ARRAY_IDX(lhs, i, svn_merge_range_t *);
+ svn_merge_range_t *rrange
+ = APR_ARRAY_IDX(rhs, i, svn_merge_range_t *);
+
+ /* range mismatch? -> needs detailed comparison */
+ if ( lrange->start != rrange->start
+ || lrange->end != rrange->end)
+ break;
+
+ /* inheritance mismatch? -> merge info differs */
+ if ( consider_inheritance
+ && lrange->inheritable != rrange->inheritable)
+ return SVN_NO_ERROR;
+ }
+
+ /* all ranges found to match -> next path */
+ if (i == lhs->nelts)
+ continue;
+ }
+
+ /* range lists differ but there are many ways to sort and aggregate
+ revisions into ranges. Do a full diff on them. */
+ SVN_ERR(svn_rangelist_diff(&deleted, &added, lhs, rhs,
+ consider_inheritance, pool));
+ if (deleted->nelts || added->nelts)
+ return SVN_NO_ERROR;
}
+
+ /* no mismatch found */
+ *is_equal = TRUE;
return SVN_NO_ERROR;
}
@@ -1668,62 +1729,37 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_array_header_t *sorted1, *sorted2;
- int i, j;
+ apr_hash_index_t *hi;
apr_pool_t *iterpool;
if (!apr_hash_count(changes))
return SVN_NO_ERROR;
- sorted1 = svn_sort__hash(mergeinfo, svn_sort_compare_items_as_paths,
- scratch_pool);
- sorted2 = svn_sort__hash(changes, svn_sort_compare_items_as_paths,
- scratch_pool);
-
- i = 0;
- j = 0;
iterpool = svn_pool_create(scratch_pool);
- while (i < sorted1->nelts && j < sorted2->nelts)
+ for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
{
- svn_sort__item_t elt1, elt2;
- int res;
+ const char *key;
+ apr_ssize_t klen;
+ svn_rangelist_t *to_insert;
+ svn_rangelist_t *target;
- svn_pool_clear(iterpool);
+ /* get ranges to insert and the target ranges list of that insertion */
+ apr_hash_this(hi, (const void**)&key, &klen, (void*)&to_insert);
+ target = apr_hash_get(mergeinfo, key, klen);
- elt1 = APR_ARRAY_IDX(sorted1, i, svn_sort__item_t);
- elt2 = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
- res = svn_sort_compare_items_as_paths(&elt1, &elt2);
-
- if (res == 0)
+ /* if range list exists, just expand on it.
+ * Otherwise, add new hash entry. */
+ if (target)
{
- svn_rangelist_t *rl1, *rl2;
-
- rl1 = elt1.value;
- rl2 = elt2.value;
-
- SVN_ERR(svn_rangelist_merge2(rl1, rl2, result_pool, iterpool));
- apr_hash_set(mergeinfo, elt1.key, elt1.klen, rl1);
- i++;
- j++;
- }
- else if (res < 0)
- {
- i++;
+ SVN_ERR(svn_rangelist_merge2(target, to_insert, result_pool,
+ iterpool));
+ apr_pool_clear(iterpool);
}
else
- {
- apr_hash_set(mergeinfo, elt2.key, elt2.klen, elt2.value);
- j++;
- }
+ apr_hash_set(mergeinfo, key, klen, to_insert);
}
- svn_pool_destroy(iterpool);
- /* Copy back any remaining elements from the second hash. */
- for (; j < sorted2->nelts; j++)
- {
- svn_sort__item_t elt = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
- apr_hash_set(mergeinfo, elt.key, elt.klen, elt.value);
- }
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -1970,7 +2006,7 @@
svn_mergeinfo_t
svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool)
{
- svn_mergeinfo_t new_mergeinfo = apr_hash_make(pool);
+ svn_mergeinfo_t new_mergeinfo = svn_hash__make(pool);
apr_hash_index_t *hi;
for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
@@ -2161,36 +2197,6 @@
}
svn_error_t *
-svn_mergeinfo__relpaths_to_urls(apr_hash_t **out_mergeinfo,
- svn_mergeinfo_t mergeinfo,
- const char *repos_root_url,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- *out_mergeinfo = NULL;
- if (mergeinfo)
- {
- apr_hash_index_t *hi;
- apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool);
-
- for (hi = apr_hash_first(scratch_pool, mergeinfo);
- hi; hi = apr_hash_next(hi))
- {
- const char *key = svn__apr_hash_index_key(hi);
- void *val = svn__apr_hash_index_val(hi);
-
- apr_hash_set(full_path_mergeinfo,
- svn_path_url_add_component2(repos_root_url, key + 1,
- result_pool),
- APR_HASH_KEY_STRING, val);
- }
- *out_mergeinfo = full_path_mergeinfo;
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
svn_mergeinfo__add_suffix_to_mergeinfo(svn_mergeinfo_t *out_mergeinfo,
svn_mergeinfo_t mergeinfo,
const char *suffix_relpath,
@@ -2224,13 +2230,18 @@
{
svn_rangelist_t *new_rl = apr_array_make(pool, rangelist->nelts,
sizeof(svn_merge_range_t *));
+
+ /* allocate target range buffer with a single operation */
+ svn_merge_range_t *copy = apr_palloc(pool, sizeof(*copy) * rangelist->nelts);
int i;
+ /* fill it iteratively and link it into the range list */
for (i = 0; i < rangelist->nelts; i++)
{
- APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) =
- svn_merge_range_dup(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *),
- pool);
+ memcpy(copy + i,
+ APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *),
+ sizeof(*copy));
+ APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) = copy + i;
}
return new_rl;
@@ -2318,38 +2329,6 @@
}
svn_error_t *
-svn_mergeinfo__to_formatted_string(svn_string_t **output,
- svn_mergeinfo_t mergeinfo,
- const char *prefix,
- apr_pool_t *pool)
-{
- svn_stringbuf_t *output_buf = NULL;
-
- if (mergeinfo && apr_hash_count(mergeinfo))
- {
- SVN_ERR(mergeinfo_to_stringbuf(&output_buf, mergeinfo,
- prefix ? prefix : "", pool));
- svn_stringbuf_appendcstr(output_buf, "\n");
- }
-#if SVN_DEBUG
- else if (!mergeinfo)
- {
- output_buf = svn_stringbuf_create(prefix ? prefix : "", pool);
- svn_stringbuf_appendcstr(output_buf, _("NULL mergeinfo\n"));
- }
- else if (apr_hash_count(mergeinfo) == 0)
- {
- output_buf = svn_stringbuf_create(prefix ? prefix : "", pool);
- svn_stringbuf_appendcstr(output_buf, _("empty mergeinfo\n"));
- }
-#endif
-
- *output = output_buf ? svn_stringbuf__morph_into_string(output_buf)
- : svn_string_create_empty(pool);
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
svn_mergeinfo__get_range_endpoints(svn_revnum_t *youngest_rev,
svn_revnum_t *oldest_rev,
svn_mergeinfo_t mergeinfo,
diff --git a/subversion/libsvn_subr/named_atomic.c b/subversion/libsvn_subr/named_atomic.c
index fb52eff..d486b57 100644
--- a/subversion/libsvn_subr/named_atomic.c
+++ b/subversion/libsvn_subr/named_atomic.c
@@ -24,7 +24,7 @@
#include "private/svn_named_atomic.h"
#include <apr_global_mutex.h>
-#include <apr_shm.h>
+#include <apr_mmap.h>
#include "svn_private_config.h"
#include "private/svn_atomic.h"
@@ -35,13 +35,16 @@
/* Implementation aspects.
*
- * We use a single shared memory block that will be created by the first
- * user and merely mapped by all subsequent ones. The memory block contains
- * an short header followed by a fixed-capacity array of named atomics. The
- * number of entries currently in use is stored in the header part.
+ * We use a single shared memory block (memory mapped file) that will be
+ * created by the first user and merely mapped by all subsequent ones.
+ * The memory block contains an short header followed by a fixed-capacity
+ * array of named atomics. The number of entries currently in use is stored
+ * in the header part.
*
- * Finding / creating the SHM object as well as adding new array entries
- * is being guarded by an APR global mutex.
+ * Finding / creating the MMAP object as well as adding new array entries
+ * is being guarded by an APR global mutex. Since releasing the MMAP
+ * structure and closing the underlying does not affect other users of the
+ * same, cleanup will not be synchronized.
*
* The array is append-only. Once a process mapped the block into its
* address space, it may freely access any of the used entries. However,
@@ -182,8 +185,8 @@
*/
struct shared_data_t
{
- volatile apr_int32_t count;
- char padding [sizeof(struct named_atomic_data_t) - sizeof(apr_int32_t)];
+ volatile apr_uint32_t count;
+ char padding [sizeof(struct named_atomic_data_t) - sizeof(apr_uint32_t)];
struct named_atomic_data_t atomics[MAX_ATOMIC_COUNT];
};
@@ -382,8 +385,13 @@
apr_pool_t *result_pool)
{
apr_status_t apr_err;
+ svn_error_t *err;
+ apr_file_t *file;
+ apr_mmap_t *mmap;
const char *shm_name, *lock_name;
- apr_shm_t *shared_mem;
+ apr_finfo_t finfo;
+
+ apr_pool_t *subpool = svn_pool_create(result_pool);
/* allocate the namespace data structure
*/
@@ -391,15 +399,13 @@
/* construct the names of the system objects that we need
*/
- shm_name = apr_pstrcat(result_pool, name, SHM_NAME_SUFFIX, NULL);
- lock_name = apr_pstrcat(result_pool, name, MUTEX_NAME_SUFFIX, NULL);
+ shm_name = apr_pstrcat(subpool, name, SHM_NAME_SUFFIX, NULL);
+ lock_name = apr_pstrcat(subpool, name, MUTEX_NAME_SUFFIX, NULL);
/* initialize the lock objects
*/
- svn_atomic__init_once(&mutex_initialized,
- init_thread_mutex,
- NULL,
- result_pool);
+ SVN_ERR(svn_atomic__init_once(&mutex_initialized, init_thread_mutex, NULL,
+ result_pool));
new_ns->mutex.pool = result_pool;
SVN_ERR(svn_io_file_open(&new_ns->mutex.lock_file, lock_name,
@@ -417,42 +423,80 @@
*/
SVN_ERR(lock(&new_ns->mutex));
- /* First, look for an existing shared memory object. If it doesn't
- * exist, create one.
+ /* First, make sure that the underlying file exists. If it doesn't
+ * exist, create one and initialize its content.
*/
- apr_err = apr_shm_attach(&shared_mem, shm_name, result_pool);
- if (apr_err)
+ err = svn_io_file_open(&file, shm_name,
+ APR_READ | APR_WRITE | APR_CREATE,
+ APR_OS_DEFAULT,
+ result_pool);
+ if (!err)
{
- apr_err = apr_shm_create(&shared_mem,
- sizeof(*new_ns->data),
- shm_name,
- result_pool);
- if (apr_err)
- return unlock(&new_ns->mutex,
- svn_error_wrap_apr(apr_err,
- _("Can't get shared memory for named atomics")));
-
- new_ns->data = apr_shm_baseaddr_get(shared_mem);
-
- /* Zero all counters, values and names.
- */
- memset(new_ns->data, 0, sizeof(*new_ns->data));
+ err = svn_io_stat(&finfo, shm_name, APR_FINFO_SIZE, subpool);
+ if (!err && finfo.size < sizeof(struct shared_data_t))
+ {
+ /* Zero all counters, values and names.
+ */
+ struct shared_data_t initial_data;
+ memset(&initial_data, 0, sizeof(initial_data));
+ err = svn_io_file_write_full(file, &initial_data,
+ sizeof(initial_data), NULL,
+ subpool);
+ }
}
- else
- new_ns->data = apr_shm_baseaddr_get(shared_mem);
- /* Cache the number of existing, complete entries. There can't be
- * incomplete ones from other processes because we hold the mutex.
- * Our process will also not access this information since we are
- * wither being called from within svn_atomic__init_once or by
- * svn_atomic_namespace__create for a new object.
+ /* Now, map it into memory.
*/
- new_ns->min_used = new_ns->data->count;
+ if (!err)
+ {
+ apr_err = apr_mmap_create(&mmap, file, 0, sizeof(*new_ns->data),
+ APR_MMAP_READ | APR_MMAP_WRITE , result_pool);
+ if (!apr_err)
+ new_ns->data = mmap->mm;
+ else
+ err = svn_error_createf(apr_err, NULL,
+ _("MMAP failed for file '%s'"), shm_name);
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (!err && new_ns->data)
+ {
+ /* Sanitize (in case of data corruption)
+ */
+ if (new_ns->data->count > MAX_ATOMIC_COUNT)
+ new_ns->data->count = MAX_ATOMIC_COUNT;
+
+ /* Cache the number of existing, complete entries. There can't be
+ * incomplete ones from other processes because we hold the mutex.
+ * Our process will also not access this information since we are
+ * either being called from within svn_atomic__init_once or by
+ * svn_atomic_namespace__create for a new object.
+ */
+ new_ns->min_used = new_ns->data->count;
+ *ns = new_ns;
+ }
/* Unlock to allow other processes may access the shared memory as well.
*/
- *ns = new_ns;
- return unlock(&new_ns->mutex, SVN_NO_ERROR);
+ return unlock(&new_ns->mutex, err);
+}
+
+svn_error_t *
+svn_atomic_namespace__cleanup(const char *name,
+ apr_pool_t *pool)
+{
+ const char *shm_name, *lock_name;
+
+ /* file names used for the specified namespace */
+ shm_name = apr_pstrcat(pool, name, SHM_NAME_SUFFIX, NULL);
+ lock_name = apr_pstrcat(pool, name, MUTEX_NAME_SUFFIX, NULL);
+
+ /* remove these files if they exist */
+ SVN_ERR(svn_io_remove_file2(shm_name, TRUE, pool));
+ SVN_ERR(svn_io_remove_file2(lock_name, TRUE, pool));
+
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -461,7 +505,7 @@
const char *name,
svn_boolean_t auto_create)
{
- apr_int32_t i, count;
+ apr_uint32_t i, count;
svn_error_t *error = SVN_NO_ERROR;
apr_size_t len = strlen(name);
@@ -499,7 +543,7 @@
/* We only need to check for new entries.
*/
for (i = count; i < ns->data->count; ++i)
- if (strcmp(ns->data->atomics[i].name, name) == 0)
+ if (strncmp(ns->data->atomics[i].name, name, len + 1) == 0)
{
return_atomic(atomic, ns, i);
diff --git a/subversion/libsvn_subr/nls.c b/subversion/libsvn_subr/nls.c
index 9bc96f8..dd06c6d 100644
--- a/subversion/libsvn_subr/nls.c
+++ b/subversion/libsvn_subr/nls.c
@@ -121,10 +121,12 @@
#else /* ! WIN32 */
bindtextdomain(PACKAGE_NAME, SVN_LOCALE_DIR);
}
+#endif /* WIN32 */
+
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
bind_textdomain_codeset(PACKAGE_NAME, "UTF-8");
#endif /* HAVE_BIND_TEXTDOMAIN_CODESET */
-#endif /* WIN32 */
+
#endif /* ENABLE_NLS */
return err;
diff --git a/subversion/libsvn_subr/opt.c b/subversion/libsvn_subr/opt.c
index f617340..1fd69f5 100644
--- a/subversion/libsvn_subr/opt.c
+++ b/subversion/libsvn_subr/opt.c
@@ -49,7 +49,6 @@
#include "private/svn_opt_private.h"
#include "opt.h"
-#include "sysinfo.h"
#include "svn_private_config.h"
@@ -1102,9 +1101,11 @@
return SVN_NO_ERROR;
}
+
svn_error_t *
svn_opt__print_version_info(const char *pgm_name,
const char *footer,
+ const svn_version_extended_t *info,
svn_boolean_t quiet,
svn_boolean_t verbose,
apr_pool_t *pool)
@@ -1114,16 +1115,11 @@
SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
" compiled %s, %s on %s\n\n"),
- pgm_name, SVN_VERSION, __DATE__, __TIME__,
- SVN_BUILD_HOST));
- SVN_ERR(svn_cmdline_fputs(
- _("Copyright (C) 2012 The Apache Software Foundation.\n"
- "This software consists of contributions made by many "
- "people; see the NOTICE\n"
- "file for more information.\n"
- "Subversion is open source software, see "
- "http://subversion.apache.org/\n\n"),
- stdout, pool));
+ pgm_name, SVN_VERSION,
+ svn_version_ext_build_date(info),
+ svn_version_ext_build_time(info),
+ svn_version_ext_build_host(info)));
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
if (footer)
{
@@ -1132,30 +1128,66 @@
if (verbose)
{
- const char *const host = svn_sysinfo__canonical_host(pool);
- const char *const relname = svn_sysinfo__release_name(pool);
- const char *const dlibs = svn_sysinfo__loaded_libs(pool);
+ const apr_array_header_t *libs;
SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
- if (relname)
- SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"
- " - %s\n"),
- host, relname));
- else
- SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), host));
-
- if (dlibs)
+ SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
+ svn_version_ext_runtime_host(info)));
+ if (svn_version_ext_runtime_osname(info))
{
+ SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"),
+ svn_version_ext_runtime_osname(info)));
+ }
+
+ libs = svn_version_ext_linked_libs(info);
+ if (libs && libs->nelts)
+ {
+ const svn_version_ext_linked_lib_t *lib;
+ int i;
+
+ SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
+ stdout, pool));
+ for (i = 0; i < libs->nelts; ++i)
+ {
+ lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
+ if (lib->runtime_version)
+ SVN_ERR(svn_cmdline_printf(pool,
+ " - %s %s (compiled with %s)\n",
+ lib->name,
+ lib->runtime_version,
+ lib->compiled_version));
+ else
+ SVN_ERR(svn_cmdline_printf(pool,
+ " - %s %s (static)\n",
+ lib->name,
+ lib->compiled_version));
+ }
+ }
+
+ libs = svn_version_ext_loaded_libs(info);
+ if (libs && libs->nelts)
+ {
+ const svn_version_ext_loaded_lib_t *lib;
+ int i;
+
SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
stdout, pool));
- SVN_ERR(svn_cmdline_fputs(dlibs, stdout, pool));
+ for (i = 0; i < libs->nelts; ++i)
+ {
+ lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
+ if (lib->version)
+ SVN_ERR(svn_cmdline_printf(pool,
+ " - %s (%s)\n",
+ lib->name, lib->version));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name));
+ }
}
}
return SVN_NO_ERROR;
}
-
svn_error_t *
svn_opt_print_help4(apr_getopt_t *os,
const char *pgm_name,
@@ -1187,8 +1219,11 @@
}
}
else if (print_version) /* just --version */
- SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
- quiet, verbose, pool));
+ {
+ SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
+ svn_version_extended(verbose, pool),
+ quiet, verbose, pool));
+ }
else if (os && !targets->nelts) /* `-h', `--help', or `help' */
svn_opt_print_generic_help2(header,
cmd_table,
diff --git a/subversion/libsvn_subr/opt.h b/subversion/libsvn_subr/opt.h
index 54d4820..ddf3984 100644
--- a/subversion/libsvn_subr/opt.h
+++ b/subversion/libsvn_subr/opt.h
@@ -24,15 +24,17 @@
#ifndef SVN_LIBSVN_SUBR_OPT_H
#define SVN_LIBSVN_SUBR_OPT_H
+#include "svn_version.h"
#include "svn_opt.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
-/* Print version info for PGM_NAME. If QUIET is true, print in
- * brief. Else if QUIET is not true, print the version more
- * verbosely, and if FOOTER is non-null, print it following the
+
+/* Print version version info for PGM_NAME to the console. If QUIET is
+ * true, print in brief. Else if QUIET is not true, print the version
+ * more verbosely, and if FOOTER is non-null, print it following the
* version information. If VERBOSE is true, print running system info.
*
* Use POOL for temporary allocations.
@@ -40,6 +42,7 @@
svn_error_t *
svn_opt__print_version_info(const char *pgm_name,
const char *footer,
+ const svn_version_extended_t *info,
svn_boolean_t quiet,
svn_boolean_t verbose,
apr_pool_t *pool);
diff --git a/subversion/libsvn_subr/path.c b/subversion/libsvn_subr/path.c
index e27191c..5147090 100644
--- a/subversion/libsvn_subr/path.c
+++ b/subversion/libsvn_subr/path.c
@@ -1082,7 +1082,7 @@
/* = svn_path_uri_encode() but without always copying */
component = uri_escape(component, svn_uri__char_validity, pool);
- return svn_path_join(url, component, pool);
+ return svn_path_join_internal(url, component, pool);
}
svn_error_t *
@@ -1100,7 +1100,7 @@
}
-
+#if !defined(WIN32) && !defined(DARWIN)
/** Get APR's internal path encoding. */
static svn_error_t *
get_path_encoding(svn_boolean_t *path_is_utf8, apr_pool_t *pool)
@@ -1119,6 +1119,7 @@
*path_is_utf8 = (encoding_style == APR_FILEPATH_ENCODING_UTF8);
return SVN_NO_ERROR;
}
+#endif
svn_error_t *
diff --git a/subversion/libsvn_subr/pseudo_md5.c b/subversion/libsvn_subr/pseudo_md5.c
new file mode 100644
index 0000000..329b71b
--- /dev/null
+++ b/subversion/libsvn_subr/pseudo_md5.c
@@ -0,0 +1,422 @@
+/*
+ * This is work is derived from material Copyright RSA Data Security, Inc.
+ *
+ * The RSA copyright statement and Licence for that original material is
+ * included below. This is followed by the Apache copyright statement and
+ * licence for the modifications made to that material.
+ */
+
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
+ * MD5 crypt() function, which is licenced as follows:
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * pseudo_md5.c: md5-esque hash sum calculation for short data blocks.
+ * Code taken and adapted from the APR (see licenses above).
+ */
+#include "private/svn_pseudo_md5.h"
+
+/* Constants for MD5 calculation.
+ */
+
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+/* F, G, H and I are basic MD5 functions.
+ */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits.
+ */
+#if defined(_MSC_VER) && _MSC_VER >= 1310
+#pragma intrinsic(_rotl)
+#define ROTATE_LEFT(x, n) (_rotl(x,n))
+#else
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+#endif
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ * Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (apr_uint32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* The idea of the functions below is as follows:
+ *
+ * - The core MD5 algorithm does not assume that the "important" data
+ * is at the begin of the encryption block, followed by e.g. 0.
+ * Instead, all bits are equally relevant.
+ *
+ * - If some bytes in the input are known to be 0, we may hard-code them.
+ * With the previous property, it is safe to move them to the upper end
+ * of the encryption block to maximize the number of steps that can be
+ * pre-calculated.
+ *
+ * - Variable-length streams will use the upper 8 byte of the last
+ * encryption block to store the stream length in bits (to make 0, 00,
+ * 000, ... etc. produce different hash sums).
+ *
+ * - We will hash at most 63 bytes, i.e. 504 bits. In the standard stream
+ * implementation, the upper 6 bytes of the last encryption block would
+ * be 0. We will put at least one non-NULL value in the last 4 bytes.
+ * Therefore, our input will always be different to a standard MD5 stream
+ * implementation in either block count, content or both.
+ *
+ * - Our length indicator also varies with the number bytes in the input.
+ * Hence, different pseudo-MD5 input length produces different output
+ * (with "cryptographic probability") even if the content is all 0 or
+ * otherwise identical.
+ *
+ * - Collisions between pseudo-MD5 and pseudo-MD5 as well as pseudo-MD5
+ * and standard MD5 are as likely as any other MD5 collision.
+ */
+
+void svn__pseudo_md5_15(apr_uint32_t digest[4],
+ const apr_uint32_t x[4])
+{
+ apr_uint32_t a = 0x67452301;
+ apr_uint32_t b = 0xefcdab89;
+ apr_uint32_t c = 0x98badcfe;
+ apr_uint32_t d = 0x10325476;
+
+ /* make sure byte 63 gets the marker independently of BE / LE */
+ apr_uint32_t x3n = x[3] ^ 0xffffffff;
+
+ /* Round 1 */
+ FF(a, b, c, d, 0, S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, 0, S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, 0, S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, 0, S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, 0, S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, 0, S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, 0, S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, 0, S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, 0, S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, 0, S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, 0, S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, 0, S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[0], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[1], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[2], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x3n, S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, 0, S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, 0, S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, 0, S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, 0, S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, 0, S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, 0, S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x3n, S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, 0, S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, 0, S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[2], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, 0, S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, 0, S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[1], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, 0, S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, 0, S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[0], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, 0, S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, 0, S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, 0, S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[2], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, 0, S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, 0, S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, 0, S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, 0, S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[1], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, 0, S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, 0, S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, 0, S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, 0, S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[0], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x3n, S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, 0, S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, 0, S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, 0, S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[2], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, 0, S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[0], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, 0, S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, 0, S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, 0, S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, 0, S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x3n, S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, 0, S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[1], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, 0, S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, 0, S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, 0, S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, 0, S44, 0xeb86d391); /* 64 */
+
+ digest[0] = a;
+ digest[1] = b;
+ digest[2] = c;
+ digest[3] = d;
+}
+
+void svn__pseudo_md5_31(apr_uint32_t digest[4],
+ const apr_uint32_t x[8])
+{
+ apr_uint32_t a = 0x67452301;
+ apr_uint32_t b = 0xefcdab89;
+ apr_uint32_t c = 0x98badcfe;
+ apr_uint32_t d = 0x10325476;
+
+ /* make sure byte 63 gets the marker independently of BE / LE */
+ apr_uint32_t x7n = x[7] ^ 0xfefefefe;
+
+ /* Round 1 */
+ FF(a, b, c, d, 0, S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, 0, S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, 0, S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, 0, S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, 0, S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, 0, S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, 0, S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, 0, S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[0], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[1], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[2], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[3], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[4], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[5], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[6], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x7n, S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, 0, S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, 0, S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[3], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, 0, S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, 0, S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[2], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x7n, S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, 0, S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[1], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[6], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, 0, S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[0], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[5], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, 0, S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, 0, S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[4], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, 0, S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[0], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[3], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[6], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, 0, S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, 0, S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, 0, S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[2], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[5], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, 0, S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, 0, S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, 0, S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[1], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[4], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x7n, S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, 0, S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, 0, S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, 0, S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[6], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, 0, S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[4], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, 0, S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[2], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, 0, S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[0], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x7n, S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, 0, S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[5], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, 0, S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[3], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, 0, S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[1], S44, 0xeb86d391); /* 64 */
+
+ digest[0] = a;
+ digest[1] = b;
+ digest[2] = c;
+ digest[3] = d;
+}
+
+void svn__pseudo_md5_63(apr_uint32_t digest[4],
+ const apr_uint32_t x[16])
+{
+ apr_uint32_t a = 0x67452301;
+ apr_uint32_t b = 0xefcdab89;
+ apr_uint32_t c = 0x98badcfe;
+ apr_uint32_t d = 0x10325476;
+
+ /* make sure byte 63 gets the marker independently of BE / LE */
+ apr_uint32_t x15n = x[15] ^ 0xfcfcfcfc;
+
+ /* Round 1 */
+ FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x15n, S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x15n, S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x15n, S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x15n, S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
+
+ digest[0] = a;
+ digest[1] = b;
+ digest[2] = c;
+ digest[3] = d;
+}
diff --git a/subversion/libsvn_subr/simple_providers.c b/subversion/libsvn_subr/simple_providers.c
index 13a30e2..e39fd23 100644
--- a/subversion/libsvn_subr/simple_providers.c
+++ b/subversion/libsvn_subr/simple_providers.c
@@ -475,6 +475,10 @@
/* Save credentials to disk. */
err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_SIMPLE,
realmstring, config_dir, pool);
+ if (err)
+ *saved = FALSE;
+
+ /* ### return error? */
svn_error_clear(err);
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_subr/skel.c b/subversion/libsvn_subr/skel.c
index 74c27c9..5d0a3ec 100644
--- a/subversion/libsvn_subr/skel.c
+++ b/subversion/libsvn_subr/skel.c
@@ -23,6 +23,8 @@
#include <string.h>
#include "svn_string.h"
#include "svn_error.h"
+#include "svn_props.h"
+#include "svn_pools.h"
#include "private/svn_skel.h"
#include "private/svn_string_private.h"
@@ -167,6 +169,35 @@
return FALSE;
}
+static svn_boolean_t
+is_valid_iproplist_skel(const svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+
+ if ((len >= 0) && (len & 1) == 0)
+ {
+ svn_skel_t *elt;
+
+ for (elt = skel->children; elt; elt = elt->next)
+ {
+ if (!elt->is_atom)
+ return FALSE;
+
+ if (elt->next == NULL)
+ return FALSE;
+
+ elt = elt->next;
+
+ if (! is_valid_proplist_skel(elt))
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static svn_skel_t *parse(const char *data, apr_size_t len,
apr_pool_t *pool);
@@ -689,6 +720,34 @@
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_skel__parse_iprops(apr_array_header_t **iprops,
+ const svn_skel_t *skel,
+ apr_pool_t *result_pool)
+{
+ svn_skel_t *elt;
+
+ /* Validate the skel. */
+ if (! is_valid_iproplist_skel(skel))
+ return skel_err("iprops");
+
+ /* Create the returned structure */
+ *iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+
+ for (elt = skel->children; elt; elt = elt->next->next)
+ {
+ svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool,
+ sizeof(*new_iprop));
+ svn_string_t *repos_parent = svn_string_ncreate(elt->data, elt->len,
+ result_pool);
+ SVN_ERR(svn_skel__parse_proplist(&(new_iprop->prop_hash), elt->next,
+ result_pool));
+ new_iprop->path_or_url = repos_parent->data;
+ APR_ARRAY_PUSH(*iprops, svn_prop_inherited_item_t *) = new_iprop;
+ }
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_skel__parse_prop(svn_string_t **propval,
@@ -760,3 +819,65 @@
*skel_p = skel;
return SVN_NO_ERROR;
}
+
+svn_error_t *
+svn_skel__unparse_iproplist(svn_skel_t **skel_p,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *result_pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(result_pool);
+
+ /* Create the skel. */
+ if (inherited_props)
+ {
+ int i;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(result_pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_skel_t *skel_list = svn_skel__make_empty_list(result_pool);
+ svn_skel_t *skel_atom;
+
+ svn_pool_clear(subpool);
+
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(subpool, iprop->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ svn_string_t *value;
+
+ apr_hash_this(hi, &key, &klen, &val);
+ value = val;
+
+ /* VALUE */
+ svn_skel__prepend(svn_skel__mem_atom(value->data, value->len,
+ result_pool), skel_list);
+
+ /* NAME */
+ svn_skel__prepend(svn_skel__mem_atom(key, klen, result_pool),
+ skel_list);
+ }
+
+ skel_atom = svn_skel__str_atom(
+ apr_pstrdup(result_pool, iprop->path_or_url), result_pool);
+ svn_skel__append(skel, skel_atom);
+ svn_skel__append(skel, skel_list);
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Validate and return the skel. */
+ if (! is_valid_iproplist_skel(skel))
+ return skel_err("iproplist");
+
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_subr/sqlite.c b/subversion/libsvn_subr/sqlite.c
index 9cf31e9..3e7bef1 100644
--- a/subversion/libsvn_subr/sqlite.c
+++ b/subversion/libsvn_subr/sqlite.c
@@ -45,6 +45,9 @@
#ifdef SVN_SQLITE_INLINE
/* Include sqlite3 inline, making all symbols private. */
#define SQLITE_API static
+ #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+ #pragma GCC diagnostic ignored "-Wunused-function"
+ #endif
#include <sqlite3.c>
#else
#include <sqlite3.h>
@@ -54,6 +57,20 @@
#error SQLite is too old -- version 3.7.12 is the minimum required version
#endif
+const char *
+svn_sqlite__compiled_version(void)
+{
+ static const char sqlite_version[] = SQLITE_VERSION;
+ return sqlite_version;
+}
+
+const char *
+svn_sqlite__runtime_version(void)
+{
+ return sqlite3_libversion();
+}
+
+
INTERNAL_STATEMENTS_SQL_DECLARE_STATEMENTS(internal_statements);
@@ -462,6 +479,27 @@
}
svn_error_t *
+svn_sqlite__bind_iprops(svn_sqlite__stmt_t *stmt,
+ int slot,
+ const apr_array_header_t *inherited_props,
+ apr_pool_t *scratch_pool)
+{
+ svn_skel_t *skel;
+ svn_stringbuf_t *properties;
+
+ if (inherited_props == NULL)
+ return svn_error_trace(svn_sqlite__bind_blob(stmt, slot, NULL, 0));
+
+ SVN_ERR(svn_skel__unparse_iproplist(&skel, inherited_props,
+ scratch_pool));
+ properties = svn_skel__unparse(skel, scratch_pool);
+ return svn_error_trace(svn_sqlite__bind_blob(stmt,
+ slot,
+ properties->data,
+ properties->len));
+}
+
+svn_error_t *
svn_sqlite__bind_checksum(svn_sqlite__stmt_t *stmt,
int slot,
const svn_checksum_t *checksum,
@@ -567,6 +605,31 @@
}
svn_error_t *
+svn_sqlite__column_iprops(apr_array_header_t **iprops,
+ svn_sqlite__stmt_t *stmt,
+ int column,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_size_t len;
+ const void *val;
+
+ /* svn_skel__parse_iprops copies everything needed to result_pool */
+ val = svn_sqlite__column_blob(stmt, column, &len, NULL);
+ if (val == NULL)
+ {
+ *iprops = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_skel__parse_iprops(iprops,
+ svn_skel__parse(val, len, scratch_pool),
+ result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_sqlite__column_checksum(const svn_checksum_t **checksum,
svn_sqlite__stmt_t *stmt, int column,
apr_pool_t *result_pool)
diff --git a/subversion/libsvn_subr/ssl_client_cert_pw_providers.c b/subversion/libsvn_subr/ssl_client_cert_pw_providers.c
index 398ad8c..2dec3f1 100644
--- a/subversion/libsvn_subr/ssl_client_cert_pw_providers.c
+++ b/subversion/libsvn_subr/ssl_client_cert_pw_providers.c
@@ -82,7 +82,7 @@
return SVN_NO_ERROR;
}
*done = FALSE;
- return FALSE;
+ return SVN_NO_ERROR;
}
/* This implements the svn_auth__password_set_t interface.
diff --git a/subversion/libsvn_subr/string.c b/subversion/libsvn_subr/string.c
index 7f9c715..fad5f9f 100644
--- a/subversion/libsvn_subr/string.c
+++ b/subversion/libsvn_subr/string.c
@@ -578,6 +578,80 @@
svn_stringbuf_appendbytes(targetstr, cstr, strlen(cstr));
}
+void
+svn_stringbuf_insert(svn_stringbuf_t *str,
+ apr_size_t pos,
+ const char *bytes,
+ apr_size_t count)
+{
+ if (bytes + count > str->data && bytes < str->data + str->blocksize)
+ {
+ /* special case: BYTES overlaps with this string -> copy the source */
+ const char *temp = apr_pstrndup(str->pool, bytes, count);
+ svn_stringbuf_insert(str, pos, temp, count);
+ }
+ else
+ {
+ if (pos > str->len)
+ pos = str->len;
+
+ svn_stringbuf_ensure(str, str->len + count);
+ memmove(str->data + pos + count, str->data + pos, str->len - pos + 1);
+ memcpy(str->data + pos, bytes, count);
+
+ str->len += count;
+ }
+}
+
+void
+svn_stringbuf_remove(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t count)
+{
+ if (pos > str->len)
+ pos = str->len;
+ if (pos + count > str->len)
+ count = str->len - pos;
+
+ memmove(str->data + pos, str->data + pos + count, str->len - pos - count + 1);
+ str->len -= count;
+}
+
+void
+svn_stringbuf_replace(svn_stringbuf_t *str,
+ apr_size_t pos,
+ apr_size_t old_count,
+ const char *bytes,
+ apr_size_t new_count)
+{
+ if (bytes + new_count > str->data && bytes < str->data + str->blocksize)
+ {
+ /* special case: BYTES overlaps with this string -> copy the source */
+ const char *temp = apr_pstrndup(str->pool, bytes, new_count);
+ svn_stringbuf_replace(str, pos, old_count, temp, new_count);
+ }
+ else
+ {
+ if (pos > str->len)
+ pos = str->len;
+ if (pos + old_count > str->len)
+ old_count = str->len - pos;
+
+ if (old_count < new_count)
+ {
+ apr_size_t delta = new_count - old_count;
+ svn_stringbuf_ensure(str, str->len + delta);
+ }
+
+ if (old_count != new_count)
+ memmove(str->data + pos + new_count, str->data + pos + old_count,
+ str->len - pos - old_count + 1);
+
+ memcpy(str->data + pos, bytes, new_count);
+ str->len += new_count - old_count;
+ }
+}
+
svn_stringbuf_t *
svn_stringbuf_dup(const svn_stringbuf_t *original_string, apr_pool_t *pool)
@@ -989,7 +1063,7 @@
target -= 8;
}
- /* Now, the number fits into 32 bits, but is larger than 1 */
+ /* Now, the number fits into 32 bits, but may still be larger than 99 */
reduced = (apr_uint32_t)(number);
while (reduced >= 100)
{
@@ -1019,3 +1093,44 @@
*dest = '-';
return svn__ui64toa(dest + 1, (apr_uint64_t)(0-number)) + 1;
}
+
+static void
+ui64toa_sep(apr_uint64_t number, char seperator, char *buffer)
+{
+ apr_size_t length = svn__ui64toa(buffer, number);
+ apr_size_t i;
+
+ for (i = length; i > 3; i -= 3)
+ {
+ memmove(&buffer[i - 2], &buffer[i - 3], length - i + 3);
+ buffer[i-3] = seperator;
+ length++;
+ }
+
+ buffer[length] = 0;
+}
+
+char *
+svn__ui64toa_sep(apr_uint64_t number, char seperator, apr_pool_t *pool)
+{
+ char buffer[2 * SVN_INT64_BUFFER_SIZE];
+ ui64toa_sep(number, seperator, buffer);
+
+ return apr_pstrdup(pool, buffer);
+}
+
+char *
+svn__i64toa_sep(apr_int64_t number, char seperator, apr_pool_t *pool)
+{
+ char buffer[2 * SVN_INT64_BUFFER_SIZE];
+ if (number < 0)
+ {
+ buffer[0] = '-';
+ ui64toa_sep((apr_uint64_t)(-number), seperator, &buffer[1]);
+ }
+ else
+ ui64toa_sep((apr_uint64_t)(number), seperator, buffer);
+
+ return apr_pstrdup(pool, buffer);
+}
+
diff --git a/subversion/libsvn_subr/subst.c b/subversion/libsvn_subr/subst.c
index 5620435..65f829d 100644
--- a/subversion/libsvn_subr/subst.c
+++ b/subversion/libsvn_subr/subst.c
@@ -1613,7 +1613,7 @@
}
if (! strncmp(identifier, SVN_SUBST__SPECIAL_LINK_STR " ",
- strlen(SVN_SUBST__SPECIAL_LINK_STR " ")))
+ sizeof(SVN_SUBST__SPECIAL_LINK_STR " ")-1))
{
/* For symlinks, the type specific data is just a filesystem
path that the symlink should reference. */
diff --git a/subversion/libsvn_subr/sysinfo.c b/subversion/libsvn_subr/sysinfo.c
index c454e88..39be28c 100644
--- a/subversion/libsvn_subr/sysinfo.c
+++ b/subversion/libsvn_subr/sysinfo.c
@@ -1,655 +1,1131 @@
-/*
- * sysinfo.c : information about the running system
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- */
-
-
-
-#ifdef WIN32
-#define WIN32_LEAN_AND_MEAN
-#define PSAPI_VERSION 1
-#include <windows.h>
-#include <psapi.h>
-#endif
-
-#define APR_WANT_STRFUNC
-#include <apr_want.h>
-
-#include <apr_lib.h>
-#include <apr_pools.h>
-#include <apr_file_info.h>
-#include <apr_strings.h>
-
-#include "svn_ctype.h"
-#include "svn_error.h"
-#include "svn_utf.h"
-
-#include "sysinfo.h"
-#include "svn_private_config.h"
-
-#if HAVE_SYS_UTSNAME_H
-#include <sys/utsname.h>
-#endif
-
-#ifdef SVN_HAVE_MACOS_PLIST
-#include <CoreFoundation/CoreFoundation.h>
-#endif
-
-#if HAVE_UNAME
-static const char* canonical_host_from_uname(apr_pool_t *pool);
-#endif
-
-#ifdef WIN32
-static const char * win32_canonical_host(apr_pool_t *pool);
-static const char * win32_release_name(apr_pool_t *pool);
-static const char * win32_shared_libs(apr_pool_t *pool);
-#endif /* WIN32 */
-
-#ifdef SVN_HAVE_MACOS_PLIST
-static const char *macos_release_name(apr_pool_t *pool);
-#endif /* SVN_HAVE_MACOS_PLIST */
-
-
-const char *
-svn_sysinfo__canonical_host(apr_pool_t *pool)
-{
-#if HAVE_UNAME
- return canonical_host_from_uname(pool);
-#elif defined(WIN32)
- return win32_canonical_host(pool);
-#else
- return "unknown-unknown-unknown";
-#endif
-}
-
-
-const char *
-svn_sysinfo__release_name(apr_pool_t *pool)
-{
-#ifdef WIN32
- return win32_release_name(pool);
-#elif defined(SVN_HAVE_MACOS_PLIST)
- return macos_release_name(pool);
-#else
- return NULL;
-#endif
-}
-
-
-const char *
-svn_sysinfo__loaded_libs(apr_pool_t *pool)
-{
-#ifdef WIN32
- return win32_shared_libs(pool);
-#else
- return NULL;
-#endif
-}
-
-
-#if HAVE_UNAME
-static const char*
-canonical_host_from_uname(apr_pool_t *pool)
-{
- const char *machine = "unknown";
- const char *vendor = "unknown";
- const char *sysname = "unknown";
- const char *sysver = "";
- struct utsname info;
-
- if (0 <= uname(&info))
- {
- svn_error_t *err;
- const char *tmp;
-
- err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
- if (err)
- svn_error_clear(err);
- else
- machine = tmp;
-
- err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
- if (err)
- svn_error_clear(err);
- else
- {
- char *lwr = apr_pstrdup(pool, tmp);
- char *it = lwr;
- while (*it)
- {
- if (svn_ctype_isupper(*it))
- *it = apr_tolower(*it);
- ++it;
- }
- sysname = lwr;
- }
-
- if (0 == strcmp(sysname, "darwin"))
- vendor = "apple";
-
- err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
- if (err)
- svn_error_clear(err);
- else
- sysver = tmp;
- }
-
- return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
-}
-#endif /* HAVE_UNAME */
-
-
-#ifdef WIN32
-typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
-typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE, DWORD, LPDWORD);
-
-/* Get system and version info, and try to tell the difference
- between the native system type and the runtime environment of the
- current process. Populate results in SYSINFO, LOCAL_SYSINFO
- (optional) and OSINFO. */
-static BOOL
-system_info(SYSTEM_INFO *sysinfo,
- SYSTEM_INFO *local_sysinfo,
- OSVERSIONINFOEXW *osinfo)
-{
- FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
- GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo");
-
- ZeroMemory(sysinfo, sizeof *sysinfo);
- if (local_sysinfo)
- {
- ZeroMemory(local_sysinfo, sizeof *local_sysinfo);
- GetSystemInfo(local_sysinfo);
- if (GetNativeSystemInfo_)
- GetNativeSystemInfo_(sysinfo);
- else
- memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
- }
- else
- GetSystemInfo(sysinfo);
-
- ZeroMemory(osinfo, sizeof *osinfo);
- osinfo->dwOSVersionInfoSize = sizeof *osinfo;
- if (!GetVersionExW((LPVOID)osinfo))
- return FALSE;
-
- return TRUE;
-}
-
-/* Map the proccessor type from SYSINFO to a string. */
-static const char *
-processor_name(SYSTEM_INFO *sysinfo)
-{
- switch (sysinfo->wProcessorArchitecture)
- {
- case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64";
- case PROCESSOR_ARCHITECTURE_IA64: return "ia64";
- case PROCESSOR_ARCHITECTURE_INTEL: return "x86";
- case PROCESSOR_ARCHITECTURE_MIPS: return "mips";
- case PROCESSOR_ARCHITECTURE_ALPHA: return "alpha32";
- case PROCESSOR_ARCHITECTURE_PPC: return "powerpc";
- case PROCESSOR_ARCHITECTURE_SHX: return "shx";
- case PROCESSOR_ARCHITECTURE_ARM: return "arm";
- case PROCESSOR_ARCHITECTURE_ALPHA64: return "alpha";
- case PROCESSOR_ARCHITECTURE_MSIL: return "msil";
- case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
- default: return "unknown";
- }
-}
-
-/* Return the Windows-specific canonical host name. */
-static const char *
-win32_canonical_host(apr_pool_t *pool)
-{
- SYSTEM_INFO sysinfo;
- SYSTEM_INFO local_sysinfo;
- OSVERSIONINFOEXW osinfo;
-
- if (system_info(&sysinfo, &local_sysinfo, &osinfo))
- {
- const char *arch = processor_name(&local_sysinfo);
- const char *machine = processor_name(&sysinfo);
- const char *vendor = "microsoft";
- const char *sysname = "windows";
- const char *sysver = apr_psprintf(pool, "%u.%u.%u",
- (unsigned int)osinfo.dwMajorVersion,
- (unsigned int)osinfo.dwMinorVersion,
- (unsigned int)osinfo.dwBuildNumber);
-
- if (sysinfo.wProcessorArchitecture
- == local_sysinfo.wProcessorArchitecture)
- return apr_psprintf(pool, "%s-%s-%s%s",
- machine, vendor, sysname, sysver);
- return apr_psprintf(pool, "%s/%s-%s-%s%s",
- arch, machine, vendor, sysname, sysver);
- }
-
- return "unknown-microsoft-windows";
-}
-
-/* Convert a Unicode string to UTF-8. */
-static char *
-wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
-{
- const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
- NULL, 0, NULL, NULL);
- if (bufsize > 0)
- {
- char *const utf8 = apr_palloc(pool, bufsize + 1);
- WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
- return utf8;
- }
- return NULL;
-}
-
-/* Query the value called NAME of the registry key HKEY. */
-static char *
-registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
-{
- DWORD size;
- wchar_t *value;
-
- if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
- return NULL;
-
- value = apr_palloc(pool, size + sizeof *value);
- if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
- return NULL;
- value[size / sizeof *value] = 0;
- return wcs_to_utf8(value, pool);
-}
-
-/* Try to glean the Windows release name and associated info from the
- registry. Failing that, construct a release name from the version
- info. */
-static const char *
-win32_release_name(apr_pool_t *pool)
-{
- SYSTEM_INFO sysinfo;
- OSVERSIONINFOEXW osinfo;
- HKEY hkcv;
-
- if (!system_info(&sysinfo, NULL, &osinfo))
- return NULL;
-
- if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
- L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
- 0, KEY_QUERY_VALUE, &hkcv))
- {
- const char *release = registry_value(hkcv, L"ProductName", pool);
- const char *spack = registry_value(hkcv, L"CSDVersion", pool);
- const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
- const char *curtype = registry_value(hkcv, L"CurrentType", pool);
- const char *install = registry_value(hkcv, L"InstallationType", pool);
- const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);
-
- if (!spack && *osinfo.szCSDVersion)
- spack = wcs_to_utf8(osinfo.szCSDVersion, pool);
-
- if (!curbuild)
- curbuild = registry_value(hkcv, L"CurrentBuild", pool);
-
- if (release || spack || curver || curtype || curbuild)
- {
- const char *bootinfo = "";
- if (curver || install || curtype)
- {
- bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
- (curver ? curver : ""),
- (install ? (curver ? " " : "") : ""),
- (install ? install : ""),
- (curtype
- ? (curver||install ? " " : "")
- : ""),
- (curtype ? curtype : ""));
- }
-
- return apr_psprintf(pool, "%s%s%s%s%s%s%s",
- (release ? release : ""),
- (spack ? (release ? ", " : "") : ""),
- (spack ? spack : ""),
- (curbuild
- ? (release||spack ? ", build " : "build ")
- : ""),
- (curbuild ? curbuild : ""),
- (bootinfo
- ? (release||spack||curbuild ? " " : "")
- : ""),
- (bootinfo ? bootinfo : ""));
- }
- }
-
- if (*osinfo.szCSDVersion)
- {
- const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);
-
- if (servicepack)
- return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
- (unsigned int)osinfo.dwMajorVersion,
- (unsigned int)osinfo.dwMinorVersion,
- servicepack,
- (unsigned int)osinfo.dwBuildNumber);
-
- /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
- if (osinfo.wServicePackMinor)
- return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
- (unsigned int)osinfo.dwMajorVersion,
- (unsigned int)osinfo.dwMinorVersion,
- (unsigned int)osinfo.wServicePackMajor,
- (unsigned int)osinfo.wServicePackMinor,
- (unsigned int)osinfo.dwBuildNumber);
-
- return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
- (unsigned int)osinfo.dwMajorVersion,
- (unsigned int)osinfo.dwMinorVersion,
- (unsigned int)osinfo.wServicePackMajor,
- (unsigned int)osinfo.dwBuildNumber);
- }
-
- return apr_psprintf(pool, "Windows NT %u.%u, build %u",
- (unsigned int)osinfo.dwMajorVersion,
- (unsigned int)osinfo.dwMinorVersion,
- (unsigned int)osinfo.dwBuildNumber);
-}
-
-
-/* Get a list of handles of shared libs loaded by the current
- process. Returns a NULL-terminated array alocated from POOL. */
-static HMODULE *
-enum_loaded_modules(apr_pool_t *pool)
-{
- HANDLE current = GetCurrentProcess();
- HMODULE dummy[1];
- HMODULE *handles;
- DWORD size;
-
- if (!EnumProcessModules(current, dummy, sizeof(dummy), &size))
- return NULL;
-
- handles = apr_palloc(pool, size + sizeof *handles);
- if (!EnumProcessModules(current, handles, size, &size))
- return NULL;
- handles[size / sizeof *handles] = NULL;
- return handles;
-}
-
-/* Find the version number, if any, embedded in FILENAME. */
-static const char *
-file_version_number(const wchar_t *filename, apr_pool_t *pool)
-{
- VS_FIXEDFILEINFO info;
- unsigned int major, minor, micro, nano;
- void *data;
- DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
- void *vinfo;
- UINT vinfo_size;
-
- if (!data_size)
- return NULL;
-
- data = apr_palloc(pool, data_size);
- if (!GetFileVersionInfoW(filename, 0, data_size, data))
- return NULL;
-
- if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
- return NULL;
-
- if (vinfo_size != sizeof info)
- return NULL;
-
- memcpy(&info, vinfo, sizeof info);
- major = (info.dwFileVersionMS >> 16) & 0xFFFF;
- minor = info.dwFileVersionMS & 0xFFFF;
- micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
- nano = info.dwFileVersionLS & 0xFFFF;
-
- if (!nano)
- {
- if (!micro)
- return apr_psprintf(pool, "%u.%u", major, minor);
- else
- return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
- }
- return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
-}
-
-/* List the shared libraries loaded by the current process. */
-static const char *
-win32_shared_libs(apr_pool_t *pool)
-{
- wchar_t buffer[MAX_PATH + 1];
- HMODULE *handles = enum_loaded_modules(pool);
- char *libinfo = "";
- HMODULE *module;
-
- for (module = handles; module && *module; ++module)
- {
- const char *filename;
- const char *version;
- if (GetModuleFileNameW(*module, buffer, MAX_PATH))
- {
- buffer[MAX_PATH] = 0;
- version = file_version_number(buffer, pool);
- filename = wcs_to_utf8(buffer, pool);
- if (filename)
- {
- char *truename;
- if (0 == apr_filepath_merge(&truename, "", filename,
- APR_FILEPATH_NATIVE
- | APR_FILEPATH_TRUENAME,
- pool))
- filename = truename;
- if (version)
- libinfo = apr_pstrcat(pool, libinfo, " - ", filename,
- " (", version, ")\n", NULL);
- else
- libinfo = apr_pstrcat(pool, libinfo, " - ", filename,
- "\n", NULL);
- }
- }
- }
-
- if (*libinfo)
- return libinfo;
- return NULL;
-}
-#endif /* WIN32 */
-
-#ifdef SVN_HAVE_MACOS_PLIST
-/* Load the SystemVersion.plist or ServerVersion.plist file into a
- property list. Set SERVER to TRUE if the file read was
- ServerVersion.plist. */
-static CFDictionaryRef
-system_version_plist(svn_boolean_t *server, apr_pool_t *pool)
-{
- static const UInt8 server_version[] =
- "/System/Library/CoreServices/ServerVersion.plist";
- static const UInt8 system_version[] =
- "/System/Library/CoreServices/SystemVersion.plist";
-
- CFPropertyListRef plist = NULL;
- CFDataRef resource = NULL;
- CFStringRef errstr = NULL;
- CFURLRef url = NULL;
- SInt32 errcode;
-
- url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
- server_version,
- sizeof(server_version) - 1,
- FALSE);
- if (!url)
- return NULL;
-
- if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
- url, &resource,
- NULL, NULL, &errcode))
- {
- CFRelease(url);
- url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
- system_version,
- sizeof(system_version) - 1,
- FALSE);
- if (!url)
- return NULL;
-
- if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
- url, &resource,
- NULL, NULL, &errcode))
- {
- CFRelease(url);
- return NULL;
- }
- else
- {
- CFRelease(url);
- *server = FALSE;
- }
- }
- else
- {
- CFRelease(url);
- *server = TRUE;
- }
-
- /* ### CFPropertyListCreateFromXMLData is obsolete, but its
- replacement CFPropertyListCreateWithData is only available
- from Mac OS 1.6 onward. */
- plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
- kCFPropertyListImmutable,
- &errstr);
- if (resource)
- CFRelease(resource);
- if (errstr)
- CFRelease(errstr);
-
- if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
- {
- /* Oops ... this really should be a dict. */
- CFRelease(plist);
- return NULL;
- }
-
- return plist;
-}
-
-/* Return the value for KEY from PLIST, or NULL if not available. */
-static const char *
-value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
-{
- CFStringRef valref;
- CFIndex bufsize;
- const void *valptr;
- const char *value;
-
- if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
- return NULL;
-
- valref = valptr;
- if (CFStringGetTypeID() != CFGetTypeID(valref))
- return NULL;
-
- value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
- if (value)
- return apr_pstrdup(pool, value);
-
- bufsize = 5 * CFStringGetLength(valref) + 1;
- value = apr_palloc(pool, bufsize);
- if (!CFStringGetCString(valref, (char*)value, bufsize,
- kCFStringEncodingUTF8))
- value = NULL;
-
- return value;
-}
-
-/* Return the commercial name of the OS, given the version number in
- a format that matches the regular expression /^10\.\d+(\..*)?$/ */
-static const char *
-release_name_from_version(const char *osver)
-{
- char *end = NULL;
- unsigned long num = strtoul(osver, &end, 10);
-
- if (!end || *end != '.' || num != 10)
- return NULL;
-
- osver = end + 1;
- end = NULL;
- num = strtoul(osver, &end, 10);
- if (!end || (*end && *end != '.'))
- return NULL;
-
- /* See http://en.wikipedia.org/wiki/History_of_OS_X#Release_timeline */
- switch(num)
- {
- case 0: return "Cheetah";
- case 1: return "Puma";
- case 2: return "Jaguar";
- case 3: return "Panther";
- case 4: return "Tiger";
- case 5: return "Leopard";
- case 6: return "Snow Leopard";
- case 7: return "Lion";
- case 8: return "Mountain Lion";
- }
-
- return NULL;
-}
-
-static const char *
-macos_release_name(apr_pool_t *pool)
-{
- svn_boolean_t server;
- CFDictionaryRef plist = system_version_plist(&server, pool);
-
- if (plist)
- {
- const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
- const char *osver = value_from_dict(plist,
- CFSTR("ProductUserVisibleVersion"),
- pool);
- const char *build = value_from_dict(plist,
- CFSTR("ProductBuildVersion"),
- pool);
- const char *release;
-
- if (!osver)
- osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
- release = release_name_from_version(osver);
-
- CFRelease(plist);
- return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
- (osname ? osname : ""),
- (osver ? (osname ? " " : "") : ""),
- (osver ? osver : ""),
- (release ? (osname||osver ? " " : "") : ""),
- (release ? release : ""),
- (build
- ? (osname||osver||release ? ", " : "")
- : ""),
- (build
- ? (server ? "server build " : "build ")
- : ""),
- (build ? build : ""));
- }
-
- return NULL;
-}
-#endif /* SVN_HAVE_MACOS_PLIST */
+/*
+ * sysinfo.c : information about the running system
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#define PSAPI_VERSION 1
+#include <windows.h>
+#include <psapi.h>
+#endif
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr_lib.h>
+#include <apr_pools.h>
+#include <apr_file_info.h>
+#include <apr_signal.h>
+#include <apr_strings.h>
+#include <apr_thread_proc.h>
+#include <apr_version.h>
+#include <apu_version.h>
+
+#include "svn_ctype.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_string.h"
+#include "svn_utf.h"
+#include "svn_version.h"
+
+#include "private/svn_sqlite.h"
+
+#include "sysinfo.h"
+#include "svn_private_config.h"
+
+#if HAVE_SYS_UTSNAME_H
+#include <sys/utsname.h>
+#endif
+
+#ifdef SVN_HAVE_MACOS_PLIST
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef SVN_HAVE_MACHO_ITERATE
+#include <mach-o/dyld.h>
+#include <mach-o/loader.h>
+#endif
+
+#if HAVE_UNAME
+static const char *canonical_host_from_uname(apr_pool_t *pool);
+# ifndef SVN_HAVE_MACOS_PLIST
+static const char *release_name_from_uname(apr_pool_t *pool);
+# endif
+#endif
+
+#ifdef WIN32
+static const char *win32_canonical_host(apr_pool_t *pool);
+static const char *win32_release_name(apr_pool_t *pool);
+static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
+#endif /* WIN32 */
+
+#ifdef SVN_HAVE_MACOS_PLIST
+static const char *macos_release_name(apr_pool_t *pool);
+#endif
+
+#ifdef SVN_HAVE_MACHO_ITERATE
+static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
+#endif
+
+
+#if __linux__
+static const char *linux_release_name(apr_pool_t *pool);
+#endif
+
+const char *
+svn_sysinfo__canonical_host(apr_pool_t *pool)
+{
+#ifdef WIN32
+ return win32_canonical_host(pool);
+#elif HAVE_UNAME
+ return canonical_host_from_uname(pool);
+#else
+ return "unknown-unknown-unknown";
+#endif
+}
+
+
+const char *
+svn_sysinfo__release_name(apr_pool_t *pool)
+{
+#ifdef WIN32
+ return win32_release_name(pool);
+#elif defined(SVN_HAVE_MACOS_PLIST)
+ return macos_release_name(pool);
+#elif __linux__
+ return linux_release_name(pool);
+#elif HAVE_UNAME
+ return release_name_from_uname(pool);
+#else
+ return NULL;
+#endif
+}
+
+const apr_array_header_t *
+svn_sysinfo__linked_libs(apr_pool_t *pool)
+{
+ svn_version_ext_linked_lib_t *lib;
+ apr_array_header_t *array = apr_array_make(pool, 3, sizeof(*lib));
+
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
+ lib->name = "APR";
+ lib->compiled_version = APR_VERSION_STRING;
+ lib->runtime_version = apr_pstrdup(pool, apr_version_string());
+
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
+ lib->name = "APR-Util";
+ lib->compiled_version = APU_VERSION_STRING;
+ lib->runtime_version = apr_pstrdup(pool, apu_version_string());
+
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
+ lib->name = "SQLite";
+ lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version());
+#ifdef SVN_SQLITE_INLINE
+ lib->runtime_version = NULL;
+#else
+ lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version());
+#endif
+
+ return array;
+}
+
+const apr_array_header_t *
+svn_sysinfo__loaded_libs(apr_pool_t *pool)
+{
+#ifdef WIN32
+ return win32_shared_libs(pool);
+#elif defined(SVN_HAVE_MACHO_ITERATE)
+ return macos_shared_libs(pool);
+#else
+ return NULL;
+#endif
+}
+
+
+#if HAVE_UNAME
+static const char*
+canonical_host_from_uname(apr_pool_t *pool)
+{
+ const char *machine = "unknown";
+ const char *vendor = "unknown";
+ const char *sysname = "unknown";
+ const char *sysver = "";
+ struct utsname info;
+
+ if (0 <= uname(&info))
+ {
+ svn_error_t *err;
+ const char *tmp;
+
+ err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ machine = tmp;
+
+ err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ {
+ char *lwr = apr_pstrdup(pool, tmp);
+ char *it = lwr;
+ while (*it)
+ {
+ if (svn_ctype_isupper(*it))
+ *it = apr_tolower(*it);
+ ++it;
+ }
+ sysname = lwr;
+ }
+
+ if (0 == strcmp(sysname, "darwin"))
+ vendor = "apple";
+ if (0 == strcmp(sysname, "linux"))
+ sysver = "-gnu";
+ else
+ {
+ err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
+ if (err)
+ svn_error_clear(err);
+ else
+ {
+ apr_size_t n = strspn(tmp, ".0123456789");
+ if (n > 0)
+ {
+ char *ver = apr_pstrdup(pool, tmp);
+ ver[n] = 0;
+ sysver = ver;
+ }
+ else
+ sysver = tmp;
+ }
+ }
+ }
+
+ return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
+}
+
+# ifndef SVN_HAVE_MACOS_PLIST
+/* Generate a release name from the uname(3) info, effectively
+ returning "`uname -s` `uname -r`". */
+static const char *
+release_name_from_uname(apr_pool_t *pool)
+{
+ struct utsname info;
+ if (0 <= uname(&info))
+ {
+ svn_error_t *err;
+ const char *sysname;
+ const char *sysver;
+
+ err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool);
+ if (err)
+ {
+ sysname = NULL;
+ svn_error_clear(err);
+ }
+
+
+ err = svn_utf_cstring_to_utf8(&sysver, info.release, pool);
+ if (err)
+ {
+ sysver = NULL;
+ svn_error_clear(err);
+ }
+
+ if (sysname || sysver)
+ {
+ return apr_psprintf(pool, "%s%s%s",
+ (sysname ? sysname : ""),
+ (sysver ? (sysname ? " " : "") : ""),
+ (sysver ? sysver : ""));
+ }
+ }
+ return NULL;
+}
+# endif /* !SVN_HAVE_MACOS_PLIST */
+#endif /* HAVE_UNAME */
+
+
+#if __linux__
+/* Split a stringbuf into a key/value pair.
+ Return the key, leaving the striped value in the stringbuf. */
+static const char *
+stringbuf_split_key(svn_stringbuf_t *buffer, char delim)
+{
+ char *key;
+ char *end;
+
+ end = strchr(buffer->data, delim);
+ if (!end)
+ return NULL;
+
+ svn_stringbuf_strip_whitespace(buffer);
+ key = buffer->data;
+ end = strchr(key, delim);
+ *end = '\0';
+ buffer->len = 1 + end - key;
+ buffer->data = end + 1;
+ svn_stringbuf_strip_whitespace(buffer);
+
+ return key;
+}
+
+/* Parse `/usr/bin/lsb_rlease --all` */
+static const char *
+lsb_release(apr_pool_t *pool)
+{
+ static const char *const args[3] =
+ {
+ "/usr/bin/lsb_release",
+ "--all",
+ NULL
+ };
+
+ const char *distributor = NULL;
+ const char *description = NULL;
+ const char *release = NULL;
+ const char *codename = NULL;
+
+ apr_proc_t lsbproc;
+ svn_stream_t *lsbinfo;
+ svn_error_t *err;
+
+ /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */
+ {
+ apr_file_t *stdin_handle;
+ apr_file_t *stdout_handle;
+
+ err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
+ APR_READ, APR_OS_DEFAULT, pool);
+ if (!err)
+ err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME,
+ APR_WRITE, APR_OS_DEFAULT, pool);
+ if (!err)
+ err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE,
+ FALSE, stdin_handle,
+ TRUE, NULL,
+ FALSE, stdout_handle,
+ pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+ }
+
+ /* Parse the output and try to populate the */
+ lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool);
+ if (lsbinfo)
+ {
+ for (;;)
+ {
+ svn_boolean_t eof = FALSE;
+ svn_stringbuf_t *line;
+ const char *key;
+
+ err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool);
+ if (err || eof)
+ break;
+
+ key = stringbuf_split_key(line, ':');
+ if (!key)
+ continue;
+
+ if (0 == svn_cstring_casecmp(key, "Distributor ID"))
+ distributor = line->data;
+ else if (0 == svn_cstring_casecmp(key, "Description"))
+ description = line->data;
+ else if (0 == svn_cstring_casecmp(key, "Release"))
+ release = line->data;
+ else if (0 == svn_cstring_casecmp(key, "Codename"))
+ codename = line->data;
+ }
+ err = svn_error_compose_create(err,
+ svn_stream_close(lsbinfo));
+ if (err)
+ {
+ svn_error_clear(err);
+ apr_proc_kill(&lsbproc, SIGKILL);
+ return NULL;
+ }
+ }
+
+ /* Reap the child process */
+ err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ if (description)
+ return apr_psprintf(pool, "%s%s%s%s", description,
+ (codename ? " (" : ""),
+ (codename ? codename : ""),
+ (codename ? ")" : ""));
+ if (distributor)
+ return apr_psprintf(pool, "%s%s%s%s%s%s", distributor,
+ (release ? " " : ""),
+ (release ? release : ""),
+ (codename ? " (" : ""),
+ (codename ? codename : ""),
+ (codename ? ")" : ""));
+
+ return NULL;
+}
+
+/* Read the whole contents of a file. */
+static svn_stringbuf_t *
+read_file_contents(const char *filename, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_stringbuf_t *buffer;
+
+ err = svn_stringbuf_from_file2(&buffer, filename, pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+/* Strip everything but the first line from a stringbuf. */
+static void
+stringbuf_first_line_only(svn_stringbuf_t *buffer)
+{
+ char *eol = strchr(buffer->data, '\n');
+ if (eol)
+ {
+ *eol = '\0';
+ buffer->len = 1 + eol - buffer->data;
+ }
+ svn_stringbuf_strip_whitespace(buffer);
+}
+
+/* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */
+static const char *
+redhat_release(apr_pool_t *pool)
+{
+ svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool);
+ if (buffer)
+ {
+ stringbuf_first_line_only(buffer);
+ return buffer->data;
+ }
+ return NULL;
+}
+
+/* Look at /etc/SuSE-release to detect non-LSB SuSE. */
+static const char *
+suse_release(apr_pool_t *pool)
+{
+ const char *release = NULL;
+ const char *codename = NULL;
+
+ svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool);
+ svn_stringbuf_t *line;
+ svn_stream_t *stream;
+ svn_boolean_t eof;
+ svn_error_t *err;
+ if (!buffer)
+ return NULL;
+
+ stream = svn_stream_from_stringbuf(buffer, pool);
+ err = svn_stream_readline(stream, &line, "\n", &eof, pool);
+ if (err || eof)
+ {
+ svn_error_clear(err);
+ return NULL;
+ }
+
+ svn_stringbuf_strip_whitespace(line);
+ release = line->data;
+
+ for (;;)
+ {
+ const char *key;
+
+ err = svn_stream_readline(stream, &line, "\n", &eof, pool);
+ if (err || eof)
+ {
+ svn_error_clear(err);
+ break;
+ }
+
+ key = stringbuf_split_key(line, '=');
+ if (!key)
+ continue;
+
+ if (0 == strncmp(key, "CODENAME", 8))
+ codename = line->data;
+ }
+
+ return apr_psprintf(pool, "%s%s%s%s",
+ release,
+ (codename ? " (" : ""),
+ (codename ? codename : ""),
+ (codename ? ")" : ""));
+}
+
+/* Look at /etc/debian_version to detect non-LSB Debian. */
+static const char *
+debian_release(apr_pool_t *pool)
+{
+ svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool);
+ if (!buffer)
+ return NULL;
+
+ stringbuf_first_line_only(buffer);
+ return apr_pstrcat(pool, "Debian ", buffer->data, NULL);
+}
+
+/* Try to find the Linux distribution name, or return info from uname. */
+static const char *
+linux_release_name(apr_pool_t *pool)
+{
+ const char *uname_release = release_name_from_uname(pool);
+
+ /* Try anything that has /usr/bin/lsb_release.
+ Covers, for example, Debian, Ubuntu and SuSE. */
+ const char *release_name = lsb_release(pool);
+
+ /* Try RHEL/Fedora/CentOS */
+ if (!release_name)
+ release_name = redhat_release(pool);
+
+ /* Try Non-LSB SuSE */
+ if (!release_name)
+ release_name = suse_release(pool);
+
+ /* Try non-LSB Debian */
+ if (!release_name)
+ release_name = debian_release(pool);
+
+ if (!release_name)
+ return uname_release;
+
+ if (!uname_release)
+ return release_name;
+
+ return apr_psprintf(pool, "%s [%s]", release_name, uname_release);
+}
+#endif /* __linux__ */
+
+
+#ifdef WIN32
+typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
+typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE, DWORD, LPDWORD);
+
+/* Get system and version info, and try to tell the difference
+ between the native system type and the runtime environment of the
+ current process. Populate results in SYSINFO, LOCAL_SYSINFO
+ (optional) and OSINFO. */
+static BOOL
+system_info(SYSTEM_INFO *sysinfo,
+ SYSTEM_INFO *local_sysinfo,
+ OSVERSIONINFOEXW *osinfo)
+{
+ FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
+ GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo");
+
+ ZeroMemory(sysinfo, sizeof *sysinfo);
+ if (local_sysinfo)
+ {
+ ZeroMemory(local_sysinfo, sizeof *local_sysinfo);
+ GetSystemInfo(local_sysinfo);
+ if (GetNativeSystemInfo_)
+ GetNativeSystemInfo_(sysinfo);
+ else
+ memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
+ }
+ else
+ GetSystemInfo(sysinfo);
+
+ ZeroMemory(osinfo, sizeof *osinfo);
+ osinfo->dwOSVersionInfoSize = sizeof *osinfo;
+ if (!GetVersionExW((LPVOID)osinfo))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Map the proccessor type from SYSINFO to a string. */
+static const char *
+processor_name(SYSTEM_INFO *sysinfo)
+{
+ switch (sysinfo->wProcessorArchitecture)
+ {
+ case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64";
+ case PROCESSOR_ARCHITECTURE_IA64: return "ia64";
+ case PROCESSOR_ARCHITECTURE_INTEL: return "x86";
+ case PROCESSOR_ARCHITECTURE_MIPS: return "mips";
+ case PROCESSOR_ARCHITECTURE_ALPHA: return "alpha32";
+ case PROCESSOR_ARCHITECTURE_PPC: return "powerpc";
+ case PROCESSOR_ARCHITECTURE_SHX: return "shx";
+ case PROCESSOR_ARCHITECTURE_ARM: return "arm";
+ case PROCESSOR_ARCHITECTURE_ALPHA64: return "alpha";
+ case PROCESSOR_ARCHITECTURE_MSIL: return "msil";
+ case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
+ default: return "unknown";
+ }
+}
+
+/* Return the Windows-specific canonical host name. */
+static const char *
+win32_canonical_host(apr_pool_t *pool)
+{
+ SYSTEM_INFO sysinfo;
+ SYSTEM_INFO local_sysinfo;
+ OSVERSIONINFOEXW osinfo;
+
+ if (system_info(&sysinfo, &local_sysinfo, &osinfo))
+ {
+ const char *arch = processor_name(&local_sysinfo);
+ const char *machine = processor_name(&sysinfo);
+ const char *vendor = "microsoft";
+ const char *sysname = "windows";
+ const char *sysver = apr_psprintf(pool, "%u.%u.%u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.dwBuildNumber);
+
+ if (sysinfo.wProcessorArchitecture
+ == local_sysinfo.wProcessorArchitecture)
+ return apr_psprintf(pool, "%s-%s-%s%s",
+ machine, vendor, sysname, sysver);
+ return apr_psprintf(pool, "%s/%s-%s-%s%s",
+ arch, machine, vendor, sysname, sysver);
+ }
+
+ return "unknown-microsoft-windows";
+}
+
+/* Convert a Unicode string to UTF-8. */
+static char *
+wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
+{
+ const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
+ NULL, 0, NULL, NULL);
+ if (bufsize > 0)
+ {
+ char *const utf8 = apr_palloc(pool, bufsize + 1);
+ WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
+ return utf8;
+ }
+ return NULL;
+}
+
+/* Query the value called NAME of the registry key HKEY. */
+static char *
+registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
+{
+ DWORD size;
+ wchar_t *value;
+
+ if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
+ return NULL;
+
+ value = apr_palloc(pool, size + sizeof *value);
+ if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
+ return NULL;
+ value[size / sizeof *value] = 0;
+ return wcs_to_utf8(value, pool);
+}
+
+/* Try to glean the Windows release name and associated info from the
+ registry. Failing that, construct a release name from the version
+ info. */
+static const char *
+win32_release_name(apr_pool_t *pool)
+{
+ SYSTEM_INFO sysinfo;
+ OSVERSIONINFOEXW osinfo;
+ HKEY hkcv;
+
+ if (!system_info(&sysinfo, NULL, &osinfo))
+ return NULL;
+
+ if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+ 0, KEY_QUERY_VALUE, &hkcv))
+ {
+ const char *release = registry_value(hkcv, L"ProductName", pool);
+ const char *spack = registry_value(hkcv, L"CSDVersion", pool);
+ const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
+ const char *curtype = registry_value(hkcv, L"CurrentType", pool);
+ const char *install = registry_value(hkcv, L"InstallationType", pool);
+ const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);
+
+ if (!spack && *osinfo.szCSDVersion)
+ spack = wcs_to_utf8(osinfo.szCSDVersion, pool);
+
+ if (!curbuild)
+ curbuild = registry_value(hkcv, L"CurrentBuild", pool);
+
+ if (release || spack || curver || curtype || curbuild)
+ {
+ const char *bootinfo = "";
+ if (curver || install || curtype)
+ {
+ bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
+ (curver ? curver : ""),
+ (install ? (curver ? " " : "") : ""),
+ (install ? install : ""),
+ (curtype
+ ? (curver||install ? " " : "")
+ : ""),
+ (curtype ? curtype : ""));
+ }
+
+ return apr_psprintf(pool, "%s%s%s%s%s%s%s",
+ (release ? release : ""),
+ (spack ? (release ? ", " : "") : ""),
+ (spack ? spack : ""),
+ (curbuild
+ ? (release||spack ? ", build " : "build ")
+ : ""),
+ (curbuild ? curbuild : ""),
+ (bootinfo
+ ? (release||spack||curbuild ? " " : "")
+ : ""),
+ (bootinfo ? bootinfo : ""));
+ }
+ }
+
+ if (*osinfo.szCSDVersion)
+ {
+ const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);
+
+ if (servicepack)
+ return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ servicepack,
+ (unsigned int)osinfo.dwBuildNumber);
+
+ /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
+ if (osinfo.wServicePackMinor)
+ return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.wServicePackMajor,
+ (unsigned int)osinfo.wServicePackMinor,
+ (unsigned int)osinfo.dwBuildNumber);
+
+ return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.wServicePackMajor,
+ (unsigned int)osinfo.dwBuildNumber);
+ }
+
+ return apr_psprintf(pool, "Windows NT %u.%u, build %u",
+ (unsigned int)osinfo.dwMajorVersion,
+ (unsigned int)osinfo.dwMinorVersion,
+ (unsigned int)osinfo.dwBuildNumber);
+}
+
+
+/* Get a list of handles of shared libs loaded by the current
+ process. Returns a NULL-terminated array alocated from POOL. */
+static HMODULE *
+enum_loaded_modules(apr_pool_t *pool)
+{
+ HANDLE current = GetCurrentProcess();
+ HMODULE dummy[1];
+ HMODULE *handles;
+ DWORD size;
+
+ if (!EnumProcessModules(current, dummy, sizeof(dummy), &size))
+ return NULL;
+
+ handles = apr_palloc(pool, size + sizeof *handles);
+ if (!EnumProcessModules(current, handles, size, &size))
+ return NULL;
+ handles[size / sizeof *handles] = NULL;
+ return handles;
+}
+
+/* Find the version number, if any, embedded in FILENAME. */
+static const char *
+file_version_number(const wchar_t *filename, apr_pool_t *pool)
+{
+ VS_FIXEDFILEINFO info;
+ unsigned int major, minor, micro, nano;
+ void *data;
+ DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
+ void *vinfo;
+ UINT vinfo_size;
+
+ if (!data_size)
+ return NULL;
+
+ data = apr_palloc(pool, data_size);
+ if (!GetFileVersionInfoW(filename, 0, data_size, data))
+ return NULL;
+
+ if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
+ return NULL;
+
+ if (vinfo_size != sizeof info)
+ return NULL;
+
+ memcpy(&info, vinfo, sizeof info);
+ major = (info.dwFileVersionMS >> 16) & 0xFFFF;
+ minor = info.dwFileVersionMS & 0xFFFF;
+ micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
+ nano = info.dwFileVersionLS & 0xFFFF;
+
+ if (!nano)
+ {
+ if (!micro)
+ return apr_psprintf(pool, "%u.%u", major, minor);
+ else
+ return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
+ }
+ return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
+}
+
+/* List the shared libraries loaded by the current process. */
+static const apr_array_header_t *
+win32_shared_libs(apr_pool_t *pool)
+{
+ apr_array_header_t *array = NULL;
+ wchar_t buffer[MAX_PATH + 1];
+ HMODULE *handles = enum_loaded_modules(pool);
+ HMODULE *module;
+
+ for (module = handles; module && *module; ++module)
+ {
+ const char *filename;
+ const char *version;
+ if (GetModuleFileNameW(*module, buffer, MAX_PATH))
+ {
+ buffer[MAX_PATH] = 0;
+ version = file_version_number(buffer, pool);
+ filename = wcs_to_utf8(buffer, pool);
+ if (filename)
+ {
+ svn_version_ext_loaded_lib_t *lib;
+ char *truename;
+
+ if (0 == apr_filepath_merge(&truename, "", filename,
+ APR_FILEPATH_NATIVE
+ | APR_FILEPATH_TRUENAME,
+ pool))
+ filename = truename;
+
+ if (!array)
+ {
+ array = apr_array_make(pool, 32, sizeof(*lib));
+ }
+ lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t);
+ lib->name = filename;
+ lib->version = version;
+ }
+ }
+ }
+
+ return array;
+}
+#endif /* WIN32 */
+
+
+#ifdef SVN_HAVE_MACOS_PLIST
+/* Load the SystemVersion.plist or ServerVersion.plist file into a
+ property list. Set SERVER to TRUE if the file read was
+ ServerVersion.plist. */
+static CFDictionaryRef
+system_version_plist(svn_boolean_t *server, apr_pool_t *pool)
+{
+ static const UInt8 server_version[] =
+ "/System/Library/CoreServices/ServerVersion.plist";
+ static const UInt8 system_version[] =
+ "/System/Library/CoreServices/SystemVersion.plist";
+
+ CFPropertyListRef plist = NULL;
+ CFDataRef resource = NULL;
+ CFStringRef errstr = NULL;
+ CFURLRef url = NULL;
+ SInt32 errcode;
+
+ url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ server_version,
+ sizeof(server_version) - 1,
+ FALSE);
+ if (!url)
+ return NULL;
+
+ if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
+ url, &resource,
+ NULL, NULL, &errcode))
+ {
+ CFRelease(url);
+ url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ system_version,
+ sizeof(system_version) - 1,
+ FALSE);
+ if (!url)
+ return NULL;
+
+ if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault,
+ url, &resource,
+ NULL, NULL, &errcode))
+ {
+ CFRelease(url);
+ return NULL;
+ }
+ else
+ {
+ CFRelease(url);
+ *server = FALSE;
+ }
+ }
+ else
+ {
+ CFRelease(url);
+ *server = TRUE;
+ }
+
+ /* ### CFPropertyListCreateFromXMLData is obsolete, but its
+ replacement CFPropertyListCreateWithData is only available
+ from Mac OS 1.6 onward. */
+ plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
+ kCFPropertyListImmutable,
+ &errstr);
+ if (resource)
+ CFRelease(resource);
+ if (errstr)
+ CFRelease(errstr);
+
+ if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
+ {
+ /* Oops ... this really should be a dict. */
+ CFRelease(plist);
+ return NULL;
+ }
+
+ return plist;
+}
+
+/* Return the value for KEY from PLIST, or NULL if not available. */
+static const char *
+value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
+{
+ CFStringRef valref;
+ CFIndex bufsize;
+ const void *valptr;
+ const char *value;
+
+ if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
+ return NULL;
+
+ valref = valptr;
+ if (CFStringGetTypeID() != CFGetTypeID(valref))
+ return NULL;
+
+ value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
+ if (value)
+ return apr_pstrdup(pool, value);
+
+ bufsize = 5 * CFStringGetLength(valref) + 1;
+ value = apr_palloc(pool, bufsize);
+ if (!CFStringGetCString(valref, (char*)value, bufsize,
+ kCFStringEncodingUTF8))
+ value = NULL;
+
+ return value;
+}
+
+/* Return the commercial name of the OS, given the version number in
+ a format that matches the regular expression /^10\.\d+(\..*)?$/ */
+static const char *
+release_name_from_version(const char *osver)
+{
+ char *end = NULL;
+ unsigned long num = strtoul(osver, &end, 10);
+
+ if (!end || *end != '.' || num != 10)
+ return NULL;
+
+ osver = end + 1;
+ end = NULL;
+ num = strtoul(osver, &end, 10);
+ if (!end || (*end && *end != '.'))
+ return NULL;
+
+ /* See http://en.wikipedia.org/wiki/History_of_OS_X#Release_timeline */
+ switch(num)
+ {
+ case 0: return "Cheetah";
+ case 1: return "Puma";
+ case 2: return "Jaguar";
+ case 3: return "Panther";
+ case 4: return "Tiger";
+ case 5: return "Leopard";
+ case 6: return "Snow Leopard";
+ case 7: return "Lion";
+ case 8: return "Mountain Lion";
+ }
+
+ return NULL;
+}
+
+/* Construct the release name from information stored in the Mac OS X
+ "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
+ Server. */
+static const char *
+macos_release_name(apr_pool_t *pool)
+{
+ svn_boolean_t server;
+ CFDictionaryRef plist = system_version_plist(&server, pool);
+
+ if (plist)
+ {
+ const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
+ const char *osver = value_from_dict(plist,
+ CFSTR("ProductUserVisibleVersion"),
+ pool);
+ const char *build = value_from_dict(plist,
+ CFSTR("ProductBuildVersion"),
+ pool);
+ const char *release;
+
+ if (!osver)
+ osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
+ release = release_name_from_version(osver);
+
+ CFRelease(plist);
+ return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
+ (osname ? osname : ""),
+ (osver ? (osname ? " " : "") : ""),
+ (osver ? osver : ""),
+ (release ? (osname||osver ? " " : "") : ""),
+ (release ? release : ""),
+ (build
+ ? (osname||osver||release ? ", " : "")
+ : ""),
+ (build
+ ? (server ? "server build " : "build ")
+ : ""),
+ (build ? build : ""));
+ }
+
+ return NULL;
+}
+#endif /* SVN_HAVE_MACOS_PLIST */
+
+#ifdef SVN_HAVE_MACHO_ITERATE
+/* List the shared libraries loaded by the current process.
+ Ignore frameworks and system libraries, they're just clutter. */
+static const apr_array_header_t *
+macos_shared_libs(apr_pool_t *pool)
+{
+ static const char slb_prefix[] = "/usr/lib/system/";
+ static const char fwk_prefix[] = "/System/Library/Frameworks/";
+ static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";
+
+ const size_t slb_prefix_len = strlen(slb_prefix);
+ const size_t fwk_prefix_len = strlen(fwk_prefix);
+ const size_t pfk_prefix_len = strlen(pfk_prefix);
+
+ apr_array_header_t *result = NULL;
+ apr_array_header_t *dylibs = NULL;
+
+ uint32_t i;
+ for (i = 0;; ++i)
+ {
+ const struct mach_header *header = _dyld_get_image_header(i);
+ const char *filename = _dyld_get_image_name(i);
+ const char *version;
+ char *truename;
+ svn_version_ext_loaded_lib_t *lib;
+
+ if (!(header && filename))
+ break;
+
+ switch (header->cputype)
+ {
+ case CPU_TYPE_I386: version = _("Intel"); break;
+ case CPU_TYPE_X86_64: version = _("Intel 64-bit"); break;
+ case CPU_TYPE_POWERPC: version = _("PowerPC"); break;
+ case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
+ default:
+ version = NULL;
+ }
+
+ if (0 == apr_filepath_merge(&truename, "", filename,
+ APR_FILEPATH_NATIVE
+ | APR_FILEPATH_TRUENAME,
+ pool))
+ filename = truename;
+ else
+ filename = apr_pstrdup(pool, filename);
+
+ if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
+ || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
+ || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
+ {
+ /* Ignore frameworks and system libraries. */
+ continue;
+ }
+
+ if (header->filetype == MH_EXECUTE)
+ {
+ /* Make sure the program filename is first in the list */
+ if (!result)
+ {
+ result = apr_array_make(pool, 32, sizeof(*lib));
+ }
+ lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
+ }
+ else
+ {
+ if (!dylibs)
+ {
+ dylibs = apr_array_make(pool, 32, sizeof(*lib));
+ }
+ lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
+ }
+
+ lib->name = filename;
+ lib->version = version;
+ }
+
+ /* Gather results into one array. */
+ if (dylibs)
+ {
+ if (result)
+ apr_array_cat(result, dylibs);
+ else
+ result = dylibs;
+ }
+
+ return result;
+}
+#endif /* SVN_HAVE_MACHO_ITERATE */
diff --git a/subversion/libsvn_subr/sysinfo.h b/subversion/libsvn_subr/sysinfo.h
index 3869e85..a92d939 100644
--- a/subversion/libsvn_subr/sysinfo.h
+++ b/subversion/libsvn_subr/sysinfo.h
@@ -1,59 +1,69 @@
-/*
- * sysinfo.h: share svn_sysinfo__* functions
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- */
-
-#ifndef SVN_LIBSVN_SUBR_SYSINFO_H
-#define SVN_LIBSVN_SUBR_SYSINFO_H
-
-#include <apr_pools.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-/* Return a canonical name similar to the output of config.guess,
- * identifying the running system.
- *
- * All allocations are done in POOL.
- */
-const char *svn_sysinfo__canonical_host(apr_pool_t *pool);
-
-/* Return the release name (i.e., marketing name) of the running
- * system, or NULL if it's not available.
- *
- * All allocations are done in POOL.
- */
-const char *svn_sysinfo__release_name(apr_pool_t *pool);
-
-/* Return a string containing a list of shared libraries loaded by the
- * running process, including their versions where applicable, or NULL
- * if the information is not available.
- *
- * All allocations are done in POOL.
- */
-const char *svn_sysinfo__loaded_libs(apr_pool_t *pool);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* SVN_LIBSVN_SUBR_SYSINFO_H */
+/*
+ * sysinfo.h: share svn_sysinfo__* functions
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_SUBR_SYSINFO_H
+#define SVN_LIBSVN_SUBR_SYSINFO_H
+
+#include <apr_pools.h>
+#include <apr_tables.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Return a canonical name similar to the output of config.guess,
+ * identifying the running system.
+ *
+ * All allocations are done in POOL.
+ */
+const char *svn_sysinfo__canonical_host(apr_pool_t *pool);
+
+/* Return the release name (i.e., marketing name) of the running
+ * system, or NULL if it's not available.
+ *
+ * All allocations are done in POOL.
+ */
+const char *svn_sysinfo__release_name(apr_pool_t *pool);
+
+/* Return an array of svn_version_linked_lib_t of descriptions of the
+ * link-time and run-time versions of dependent libraries, or NULL of
+ * the info is not available.
+ *
+ * All allocations are done in POOL.
+ */
+const apr_array_header_t *svn_sysinfo__linked_libs(apr_pool_t *pool);
+
+/* Return an array of svn_version_loaded_lib_t of descriptions of
+ * shared libraries loaded by the running process, including their
+ * versions where applicable, or NULL if the information is not
+ * available.
+ *
+ * All allocations are done in POOL.
+ */
+const apr_array_header_t *svn_sysinfo__loaded_libs(apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_SYSINFO_H */
diff --git a/subversion/libsvn_subr/temp_serializer.c b/subversion/libsvn_subr/temp_serializer.c
index e8f3873..85b9288 100644
--- a/subversion/libsvn_subr/temp_serializer.c
+++ b/subversion/libsvn_subr/temp_serializer.c
@@ -51,7 +51,9 @@
/* offset within the target buffer to where the structure got copied */
apr_size_t target_offset;
- /* parent stack entry. Will be NULL for the root entry. */
+ /* parent stack entry. Will be NULL for the root entry.
+ * Items in the svn_temp_serializer__context_t recycler will use this
+ * to link to the next unused item. */
struct source_stack_t *upper;
} source_stack_t;
@@ -70,6 +72,9 @@
* process has been finished. However, it is not necessarily NULL when
* the application end serialization. */
source_stack_t *source;
+
+ /* unused stack elements will be put here for later reuse. */
+ source_stack_t *recycler;
};
/* Make sure the serialized data len is a multiple of the default alignment,
@@ -110,6 +115,7 @@
svn_temp_serializer__context_t *context = apr_palloc(pool, sizeof(*context));
context->pool = pool;
context->buffer = svn_stringbuf_create_ensure(init_size, pool);
+ context->recycler = NULL;
/* If a source struct has been given, make it the root struct. */
if (source_struct)
@@ -168,6 +174,9 @@
context->source->target_offset = (char *)source_struct - (char *)buffer;
context->source->upper = NULL;
+ /* initialize the RECYCLER */
+ context->recycler = NULL;
+
/* done */
return context;
}
@@ -219,9 +228,16 @@
apr_size_t struct_size)
{
const void *source = *source_struct;
+ source_stack_t *new;
- /* create a new entry for the structure stack */
- source_stack_t *new = apr_palloc(context->pool, sizeof(*new));
+ /* recycle an old entry or create a new one for the structure stack */
+ if (context->recycler)
+ {
+ new = context->recycler;
+ context->recycler = new->upper;
+ }
+ else
+ new = apr_palloc(context->pool, sizeof(*new));
/* the serialized structure must be properly aligned */
if (source)
@@ -250,11 +266,17 @@
void
svn_temp_serializer__pop(svn_temp_serializer__context_t *context)
{
+ source_stack_t *old = context->source;
+
/* we may pop the original struct but not further */
assert(context->source);
/* one level up the structure stack */
context->source = context->source->upper;
+
+ /* put the old stack element into the recycler for later reuse */
+ old->upper = context->recycler;
+ context->recycler = old;
}
/* Serialize a string referenced from the current structure within the
@@ -311,7 +333,7 @@
return context->buffer->len;
}
-/* Return the data buffer that receives the serialialized data from
+/* Return the data buffer that receives the serialized data from
* the given serialization CONTEXT.
*/
svn_stringbuf_t *
diff --git a/subversion/libsvn_subr/utf.c b/subversion/libsvn_subr/utf.c
index 2d83028..2af475d 100644
--- a/subversion/libsvn_subr/utf.c
+++ b/subversion/libsvn_subr/utf.c
@@ -892,7 +892,9 @@
*dest = svn_stringbuf_dup(src, pool);
}
- put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
return err;
}
@@ -925,7 +927,9 @@
*dest = svn_string_dup(src, pool);
}
- put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
return err;
}
@@ -939,11 +943,13 @@
xlate_handle_node_t *node;
svn_error_t *err;
- SVN_ERR(check_utf8(src, strlen(src), pool));
+ SVN_ERR(check_cstring_utf8(src, pool));
SVN_ERR(get_uton_xlate_handle_node(&node, pool));
err = convert_cstring(dest, src, node, pool);
- put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
return err;
}
@@ -960,12 +966,14 @@
const char *convset_key = get_xlate_key(topage, SVN_APR_UTF8_CHARSET,
pool);
- SVN_ERR(check_utf8(src, strlen(src), pool));
+ SVN_ERR(check_cstring_utf8(src, pool));
SVN_ERR(get_xlate_handle_node(&node, topage, SVN_APR_UTF8_CHARSET,
convset_key, pool));
err = convert_cstring(dest, src, node, pool);
- put_xlate_handle_node(node, convset_key, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, convset_key, pool));
return err;
}
@@ -1062,7 +1070,9 @@
*dest = apr_pstrmemdup(pool, src->data, src->len);
}
- put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool);
+ err = svn_error_compose_create(
+ err,
+ put_xlate_handle_node(node, SVN_UTF_UTON_XLATE_HANDLE, pool));
return err;
}
diff --git a/subversion/libsvn_subr/utf_validate.c b/subversion/libsvn_subr/utf_validate.c
index 6f00f73..0e7c548 100644
--- a/subversion/libsvn_subr/utf_validate.c
+++ b/subversion/libsvn_subr/utf_validate.c
@@ -57,6 +57,8 @@
*/
#include "private/svn_utf_private.h"
+#include "private/svn_eol_private.h"
+#include "private/svn_dep_compat.h"
/* Lookup table to categorise each octet in the string. */
static const char octet_category[256] = {
@@ -249,12 +251,92 @@
FSM_ERROR}, /* 0xf5-0xff */
};
+/* Scan MAX_LEN bytes in *DATA for chars that are not in the octet
+ * category 0 (FSM_START). Return the position of the first such char
+ * or DATA + MAX_LEN if all were cat 0.
+ */
+static const char *
+first_non_fsm_start_char(const char *data, apr_size_t max_len)
+{
+#if !SVN_UNALIGNED_ACCESS_IS_OK
+
+ /* On some systems, we need to make sure that buf is properly aligned
+ * for chunky data access.
+ */
+ if ((apr_uintptr_t)data & (sizeof(apr_uintptr_t)-1))
+ {
+ apr_size_t len = (~(apr_uintptr_t)data) & (sizeof(apr_uintptr_t)-1);
+ if (len > max_len)
+ len = max_len;
+ max_len -= len;
+
+ for (; len > 0; ++data, --len)
+ if (*data < 0 || *data >= 0x80)
+ return data;
+ }
+
+#endif
+
+ /* Scan the input one machine word at a time. */
+ for (; max_len > sizeof(apr_uintptr_t)
+ ; data += sizeof(apr_uintptr_t), max_len -= sizeof(apr_uintptr_t))
+ if (*(const apr_uintptr_t *)data & SVN__BIT_7_SET)
+ break;
+
+ /* The remaining odd bytes will be examined the naive way: */
+ for (; max_len > 0; ++data, --max_len)
+ if (*data < 0 || *data >= 0x80)
+ break;
+
+ return data;
+}
+
+/* Scan the C string in *DATA for chars that are not in the octet
+ * category 0 (FSM_START). Return the position of either the such
+ * char or of the terminating NUL.
+ */
+static const char *
+first_non_fsm_start_char_cstring(const char *data)
+{
+ /* We need to make sure that BUF is properly aligned for chunky data
+ * access because we don't know the string's length. Unaligned chunk
+ * read access beyond the NUL terminator could therefore result in a
+ * segfault.
+ */
+ for (; (apr_uintptr_t)data & (sizeof(apr_uintptr_t)-1); ++data)
+ if (*data <= 0 || *data >= 0x80)
+ return data;
+
+ /* Scan the input one machine word at a time. */
+ for (; ; data += sizeof(apr_uintptr_t))
+ {
+ /* Check for non-ASCII chars: */
+ apr_uintptr_t chunk = *(const apr_uintptr_t *)data;
+ if (chunk & SVN__BIT_7_SET)
+ break;
+
+ /* This is the well-known strlen test: */
+ chunk |= (chunk & SVN__LOWER_7BITS_SET) + SVN__LOWER_7BITS_SET;
+ if ((chunk & SVN__BIT_7_SET) != SVN__BIT_7_SET)
+ break;
+ }
+
+ /* The remaining odd bytes will be examined the naive way: */
+ for (; ; ++data)
+ if (*data <= 0 || *data >= 0x80)
+ break;
+
+ return data;
+}
const char *
svn_utf__last_valid(const char *data, apr_size_t len)
{
- const char *start = data, *end = data + len;
+ const char *start = first_non_fsm_start_char(data, len);
+ const char *end = data + len;
int state = FSM_START;
+
+ data = start;
while (data < end)
{
unsigned char octet = *data++;
@@ -270,6 +352,8 @@
svn_utf__cstring_is_valid(const char *data)
{
int state = FSM_START;
+ data = first_non_fsm_start_char_cstring(data);
+
while (*data)
{
unsigned char octet = *data++;
@@ -284,6 +368,8 @@
{
const char *end = data + len;
int state = FSM_START;
+ data = first_non_fsm_start_char(data, len);
+
while (data < end)
{
unsigned char octet = *data++;
@@ -296,8 +382,11 @@
const char *
svn_utf__last_valid2(const char *data, apr_size_t len)
{
- const char *start = data, *end = data + len;
+ const char *start = first_non_fsm_start_char(data, len);
+ const char *end = data + len;
int state = FSM_START;
+
+ data = start;
while (data < end)
{
unsigned char octet = *data++;
diff --git a/subversion/libsvn_subr/version.c b/subversion/libsvn_subr/version.c
index 78229d3..4bc653a 100644
--- a/subversion/libsvn_subr/version.c
+++ b/subversion/libsvn_subr/version.c
@@ -26,7 +26,9 @@
#include "svn_error.h"
#include "svn_version.h"
+#include "sysinfo.h"
#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
const svn_version_t *
svn_subr_version(void)
@@ -96,3 +98,181 @@
return err;
}
+
+
+struct svn_version_extended_t
+{
+ const char *build_date; /* Compilation date */
+ const char *build_time; /* Compilation time */
+ const char *build_host; /* Build canonical host name */
+ const char *copyright; /* Copyright notice (localized) */
+ const char *runtime_host; /* Runtime canonical host name */
+ const char *runtime_osname; /* Running OS release name */
+
+ /* Array of svn_version_ext_linked_lib_t describing dependent
+ libraries. */
+ const apr_array_header_t *linked_libs;
+
+ /* Array of svn_version_ext_loaded_lib_t describing loaded shared
+ libraries. */
+ const apr_array_header_t *loaded_libs;
+};
+
+
+const svn_version_extended_t *
+svn_version_extended(svn_boolean_t verbose,
+ apr_pool_t *pool)
+{
+ svn_version_extended_t *info = apr_pcalloc(pool, sizeof(*info));
+
+ info->build_date = __DATE__;
+ info->build_time = __TIME__;
+ info->build_host = SVN_BUILD_HOST;
+ info->copyright = apr_pstrdup
+ (pool, _("Copyright (C) 2012 The Apache Software Foundation.\n"
+ "This software consists of contributions made by many people;\n"
+ "see the NOTICE file for more information.\n"
+ "Subversion is open source software, see "
+ "http://subversion.apache.org/\n"));
+
+ if (verbose)
+ {
+ info->runtime_host = svn_sysinfo__canonical_host(pool);
+ info->runtime_osname = svn_sysinfo__release_name(pool);
+ info->linked_libs = svn_sysinfo__linked_libs(pool);
+ info->loaded_libs = svn_sysinfo__loaded_libs(pool);
+ }
+
+ return info;
+}
+
+
+const char *
+svn_version_ext_build_date(const svn_version_extended_t *ext_info)
+{
+ return ext_info->build_date;
+}
+
+const char *
+svn_version_ext_build_time(const svn_version_extended_t *ext_info)
+{
+ return ext_info->build_time;
+}
+
+const char *
+svn_version_ext_build_host(const svn_version_extended_t *ext_info)
+{
+ return ext_info->build_host;
+}
+
+const char *
+svn_version_ext_copyright(const svn_version_extended_t *ext_info)
+{
+ return ext_info->copyright;
+}
+
+const char *
+svn_version_ext_runtime_host(const svn_version_extended_t *ext_info)
+{
+ return ext_info->runtime_host;
+}
+
+const char *
+svn_version_ext_runtime_osname(const svn_version_extended_t *ext_info)
+{
+ return ext_info->runtime_osname;
+}
+
+const apr_array_header_t *
+svn_version_ext_linked_libs(const svn_version_extended_t *ext_info)
+{
+ return ext_info->linked_libs;
+}
+
+const apr_array_header_t *
+svn_version_ext_loaded_libs(const svn_version_extended_t *ext_info)
+{
+ return ext_info->loaded_libs;
+}
+
+svn_error_t *
+svn_version__parse_version_string(svn_version_t **version_p,
+ const char *version_string,
+ apr_pool_t *result_pool)
+{
+ svn_error_t *err;
+ svn_version_t *version;
+ apr_array_header_t *pieces =
+ svn_cstring_split(version_string, ".", FALSE, result_pool);
+
+ if ((pieces->nelts < 2) || (pieces->nelts > 3))
+ return svn_error_create(SVN_ERR_MALFORMED_VERSION_STRING, NULL, NULL);
+
+ version = apr_pcalloc(result_pool, sizeof(*version));
+ version->tag = "";
+
+ /* Parse the major and minor integers strictly. */
+ err = svn_cstring_atoi(&(version->major),
+ APR_ARRAY_IDX(pieces, 0, const char *));
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_VERSION_STRING, err, NULL);
+ err = svn_cstring_atoi(&(version->minor),
+ APR_ARRAY_IDX(pieces, 1, const char *));
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_VERSION_STRING, err, NULL);
+
+ /* If there's a third component, we'll parse it, too. But we don't
+ require that it be present. */
+ if (pieces->nelts == 3)
+ {
+ const char *piece = APR_ARRAY_IDX(pieces, 2, const char *);
+ char *hyphen = strchr(piece, '-');
+ if (hyphen)
+ {
+ version->tag = apr_pstrdup(result_pool, hyphen + 1);
+ *hyphen = '\0';
+ }
+ err = svn_cstring_atoi(&(version->patch), piece);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_VERSION_STRING,
+ err, NULL);
+ }
+
+ *version_p = version;
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_version__at_least(svn_version_t *version,
+ int major,
+ int minor,
+ int patch)
+{
+ /* Compare major versions. */
+ if (version->major < major)
+ return FALSE;
+ if (version->major > major)
+ return TRUE;
+
+ /* Major versions are the same. Compare minor versions. */
+ if (version->minor < minor)
+ return FALSE;
+ if (version->minor > minor)
+ return TRUE;
+
+ /* Major and minor versions are the same. Compare patch
+ versions. */
+ if (version->patch < patch)
+ return FALSE;
+ if (version->patch > patch)
+ return TRUE;
+
+ /* Major, minor, and patch versions are identical matches. But tags
+ in our schema are always used for versions not yet quite at the
+ given patch level. */
+ if (version->tag && version->tag[0])
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/subversion/libsvn_subr/win32_crypto.c b/subversion/libsvn_subr/win32_crypto.c
index 974af06..f8a58d1 100644
--- a/subversion/libsvn_subr/win32_crypto.c
+++ b/subversion/libsvn_subr/win32_crypto.c
@@ -145,7 +145,7 @@
SVN_ERR(svn_auth__simple_password_get(done, &in, creds, realmstring, username,
parameters, non_interactive, pool));
- if (!done)
+ if (!*done)
return SVN_NO_ERROR;
orig = svn_base64_decode_string(svn_string_create(in, pool), pool);
@@ -270,7 +270,7 @@
SVN_ERR(svn_auth__ssl_client_cert_pw_get(done, &in, creds, realmstring,
username, parameters,
non_interactive, pool));
- if (!done)
+ if (!*done)
return SVN_NO_ERROR;
orig = svn_base64_decode_string(svn_string_create(in, pool), pool);
diff --git a/subversion/libsvn_wc/adm_files.c b/subversion/libsvn_wc/adm_files.c
index 44ac31e..d7cfa39 100644
--- a/subversion/libsvn_wc/adm_files.c
+++ b/subversion/libsvn_wc/adm_files.c
@@ -592,7 +592,7 @@
SVN_ERR(svn_wc__db_open(&db,
NULL /* config */,
- TRUE /* auto_upgrade */,
+ FALSE /* auto_upgrade */,
TRUE /* enforce_empty_wq */,
pool, pool));
diff --git a/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c
index 2171073..ec1a4ef 100644
--- a/subversion/libsvn_wc/adm_ops.c
+++ b/subversion/libsvn_wc/adm_ops.c
@@ -1019,6 +1019,7 @@
const char *base_name = svn_dirent_basename(local_abspath, scratch_pool);
svn_boolean_t is_wc_root;
svn_node_kind_t kind;
+ svn_boolean_t is_special;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url,
@@ -1035,7 +1036,8 @@
SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool));
/* Make sure something's there; set KIND and *KIND_P. */
- SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special,
+ scratch_pool));
if (kind == svn_node_none)
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("'%s' not found"),
@@ -1097,14 +1099,20 @@
SVN_ERR_ASSERT(!is_wc_root);
break;
case svn_wc__db_status_normal:
- if (copyfrom_url)
- {
- SVN_ERR(svn_wc__check_wc_root(&is_wc_root, NULL, NULL,
- db, local_abspath,
- scratch_pool));
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath,
+ scratch_pool));
- if (is_wc_root)
- break;
+ if (is_wc_root && copyfrom_url)
+ {
+ /* Integrate a sub working copy in a parent working copy
+ (legacy behavior) */
+ break;
+ }
+ else if (is_wc_root && is_special)
+ {
+ /* Adding a symlink to a working copy root.
+ (special_tests.py 23: externals as symlink targets) */
+ break;
}
/* else: Fall through in default error */
@@ -2203,7 +2211,7 @@
SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
- SVN_ERR(svn_wc__db_open(&db, NULL, TRUE, TRUE, pool, pool));
+ SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool));
/* DB is now open. This is seemingly a "light" function that a caller
may use repeatedly despite error return values. The rest of this
function should aggressively close DB, even in the error case. */
diff --git a/subversion/libsvn_wc/cleanup.c b/subversion/libsvn_wc/cleanup.c
index dd5bc24..3f45379 100644
--- a/subversion/libsvn_wc/cleanup.c
+++ b/subversion/libsvn_wc/cleanup.c
@@ -206,7 +206,7 @@
/* We need a DB that allows a non-empty work queue (though it *will*
auto-upgrade). We'll handle everything manually. */
SVN_ERR(svn_wc__db_open(&db,
- NULL /* ### config */, TRUE, FALSE,
+ NULL /* ### config */, FALSE, FALSE,
scratch_pool, scratch_pool));
SVN_ERR(cleanup_internal(db, local_abspath, cancel_func, cancel_baton,
diff --git a/subversion/libsvn_wc/conflicts.c b/subversion/libsvn_wc/conflicts.c
index 804ef72..1fbdcc1 100644
--- a/subversion/libsvn_wc/conflicts.c
+++ b/subversion/libsvn_wc/conflicts.c
@@ -511,9 +511,6 @@
{ "replaced", svn_wc_conflict_reason_replaced },
{ "moved-away", svn_wc_conflict_reason_moved_away },
{ "moved-here", svn_wc_conflict_reason_moved_here },
- /* ### Do we really need this. The edit state is on a different node,
- which might just change while the tree conflict exists? */
- { "moved-and-edited", svn_wc_conflict_reason_moved_away_and_edited },
{ NULL }
};
@@ -1165,7 +1162,7 @@
{
const char *propname = svn__apr_hash_index_key(hi);
- prop_conflict_skel_add(
+ SVN_ERR(prop_conflict_skel_add(
prop_data, propname,
old_props
? apr_hash_get(old_props, propname,
@@ -1183,7 +1180,7 @@
? apr_hash_get(their_original_props, propname,
APR_HASH_KEY_STRING)
: NULL,
- result_pool, scratch_pool);
+ result_pool, scratch_pool));
}
SVN_ERR(svn_wc__wq_build_prej_install(work_items,
@@ -1603,6 +1600,10 @@
FALSE /* record_fileinfo */,
result_pool, scratch_pool));
*work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
+ result_pool, scratch_pool));
+ *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
if (remove_source)
{
@@ -1616,34 +1617,6 @@
return SVN_NO_ERROR;
}
-/* Helper for maybe_resolve_conflicts() below. */
-static const svn_wc_conflict_description2_t *
-setup_text_conflict_desc(const char *left_abspath,
- const char *right_abspath,
- const char *target_abspath,
- const svn_wc_conflict_version_t *left_version,
- const svn_wc_conflict_version_t *right_version,
- const char *result_target,
- const char *detranslated_target,
- const char *mimeprop,
- svn_boolean_t is_binary,
- apr_pool_t *pool)
-{
- svn_wc_conflict_description2_t *cdesc;
-
- cdesc = svn_wc_conflict_description_create_text2(target_abspath, pool);
- cdesc->is_binary = is_binary;
- cdesc->mime_type = mimeprop;
- cdesc->base_abspath = left_abspath;
- cdesc->their_abspath = right_abspath;
- cdesc->my_abspath = detranslated_target;
- cdesc->merged_file = result_target;
-
- cdesc->src_left_version = left_version;
- cdesc->src_right_version = right_version;
-
- return cdesc;
-}
/* Create a new file in the same directory as VERSIONED_ABSPATH, with the
same basename as VERSIONED_ABSPATH, with a ".edited" extension, and set
@@ -1682,7 +1655,6 @@
db, local_abspath,
source, edited_copy_abspath,
result_pool, scratch_pool));
-
return SVN_NO_ERROR;
}
@@ -1711,7 +1683,7 @@
{
svn_wc_conflict_result_t *result;
svn_skel_t *work_item;
- const svn_wc_conflict_description2_t *cdesc;
+ svn_wc_conflict_description2_t *cdesc;
apr_hash_t *props;
*work_items = NULL;
@@ -1719,38 +1691,37 @@
/* Give the conflict resolution callback a chance to clean
up the conflicts before we mark the file 'conflicted' */
- SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
- cdesc = setup_text_conflict_desc(left_abspath,
- right_abspath,
- local_abspath,
- left_version,
- right_version,
- result_target,
- detranslated_target,
- svn_prop_get_value(props,
- SVN_PROP_MIME_TYPE),
- FALSE,
- scratch_pool);
+ cdesc = svn_wc_conflict_description_create_text2(local_abspath,
+ scratch_pool);
+ cdesc->is_binary = FALSE;
+ cdesc->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
+ cdesc->base_abspath = left_abspath;
+ cdesc->their_abspath = right_abspath;
+ cdesc->my_abspath = detranslated_target;
+ cdesc->merged_file = result_target;
+ cdesc->src_left_version = left_version;
+ cdesc->src_right_version = right_version;
- SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool,
+ SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool,
scratch_pool));
- if (result == NULL)
- return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
- NULL, _("Conflict callback violated API:"
- " returned no results"));
+ if (result == NULL)
+ return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Conflict callback violated API: "
+ "returned no results"));
- if (result->save_merged)
+ if (result->save_merged)
{
- SVN_ERR(save_merge_result(work_items,
- db, local_abspath,
- /* Look for callback's own
- merged-file first: */
- result->merged_file
- ? result->merged_file
- : result_target,
- result_pool, scratch_pool));
+ SVN_ERR(save_merge_result(work_items,
+ db, local_abspath,
+ /* Look for callback's own
+ merged-file first: */
+ result->merged_file
+ ? result->merged_file
+ : result_target,
+ result_pool, scratch_pool));
}
SVN_ERR(eval_text_conflict_func_result(&work_item,
@@ -1779,6 +1750,107 @@
}
+static svn_error_t *
+setup_tree_conflict_desc(svn_wc_conflict_description2_t **desc,
+ svn_wc_operation_t operation,
+ const apr_array_header_t *locations,
+ const svn_skel_t *conflict_skel,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_conflict_version_t *v1;
+ svn_wc_conflict_version_t *v2;
+ svn_node_kind_t tc_kind;
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change,
+ &incoming_change,
+ db, local_abspath,
+ conflict_skel,
+ result_pool, scratch_pool));
+
+ v1 = (locations && locations->nelts > 0)
+ ? APR_ARRAY_IDX(locations, 0, svn_wc_conflict_version_t *)
+ : NULL;
+
+ v2 = (locations && locations->nelts > 1)
+ ? APR_ARRAY_IDX(locations, 1, svn_wc_conflict_version_t *)
+ : NULL;
+
+ if (incoming_change != svn_wc_conflict_action_delete
+ && (operation == svn_wc_operation_update
+ || operation == svn_wc_operation_switch))
+ {
+ svn_wc__db_status_t status;
+ svn_revnum_t revision;
+ const char *repos_relpath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ svn_kind_t kind;
+ svn_error_t *err;
+
+ /* ### Theoretically we should just fetch the BASE information
+ here. This code might need tweaks until all tree conflicts
+ are installed in the proper state */
+
+ SVN_ERR_ASSERT(v2 == NULL); /* Not set for update and switch */
+
+ /* With an update or switch we have to fetch the second location
+ for a tree conflict from WORKING. (For text or prop from BASE)
+ */
+ err = svn_wc__db_base_get_info(&status, &kind, &revision,
+ &repos_relpath, &repos_root_url,
+ &repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ /* Ignore BASE */
+
+ tc_kind = svn_node_file; /* Avoid assertion */
+ }
+ else if (repos_relpath)
+ {
+ v2 = svn_wc_conflict_version_create2(repos_root_url,
+ repos_uuid,
+ repos_relpath,
+ revision,
+ svn__node_kind_from_kind(kind),
+ result_pool);
+ tc_kind = svn__node_kind_from_kind(kind);
+ }
+ else
+ tc_kind = svn_node_file; /* Avoid assertion */
+ }
+ else
+ {
+ if (v1)
+ tc_kind = v1->node_kind;
+ else if (v2)
+ tc_kind = v2->node_kind;
+ else
+ tc_kind = svn_node_file; /* Avoid assertion */
+ }
+
+ *desc = svn_wc_conflict_description_create_tree2(local_abspath, tc_kind,
+ operation, v1, v2,
+ result_pool);
+ (*desc)->reason = local_change;
+ (*desc)->action = incoming_change;
+
+ return SVN_NO_ERROR;
+}
+
+
svn_error_t *
svn_wc__conflict_invoke_resolver(svn_wc__db_t *db,
const char *local_abspath,
@@ -1790,10 +1862,13 @@
{
svn_boolean_t text_conflicted;
svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
svn_wc_operation_t operation;
+ const apr_array_header_t *locations;
- SVN_ERR(svn_wc__conflict_read_info(&operation, NULL,
- &text_conflicted, &prop_conflicted, NULL,
+ SVN_ERR(svn_wc__conflict_read_info(&operation, &locations,
+ &text_conflicted, &prop_conflicted,
+ &tree_conflicted,
db, local_abspath, conflict_skel,
scratch_pool, scratch_pool));
@@ -1907,6 +1982,183 @@
}
}
+ if (tree_conflicted)
+ {
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_result_t *result;
+ svn_wc_conflict_description2_t *desc;
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change,
+ &incoming_change,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+ SVN_ERR(setup_tree_conflict_desc(&desc, operation, locations,
+ conflict_skel, local_abspath, db,
+ scratch_pool, scratch_pool));
+ /* Tell the resolver func about this conflict. */
+ SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool,
+ scratch_pool));
+
+ /* Ignore the result. We cannot apply it here since this code runs
+ * during an update or merge operation. Tree conflicts are always
+ * postponed and resolved after the operation has completed. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read all property conflicts contained in CONFLICT_SKEL into
+ * individual conflict descriptions, and append those descriptions
+ * to the CONFLICTS array.
+ *
+ * If NOT create_tempfiles, always create a legacy property conflict
+ * descriptor.
+ *
+ * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary
+ * allocations. */
+static svn_error_t *
+read_prop_conflicts(apr_array_header_t *conflicts,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_skel_t *conflict_skel,
+ svn_boolean_t create_tempfiles,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *prop_reject_file;
+ apr_hash_t *my_props;
+ apr_hash_t *their_old_props;
+ apr_hash_t *their_props;
+ apr_hash_t *conflicted_props;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file,
+ &my_props,
+ &their_old_props,
+ &their_props,
+ &conflicted_props,
+ db, local_abspath,
+ conflict_skel,
+ scratch_pool, scratch_pool));
+
+ if ((! create_tempfiles) || apr_hash_count(conflicted_props) == 0)
+ {
+ /* Legacy prop conflict with only a .reject file. */
+ svn_wc_conflict_description2_t *desc;
+
+ desc = svn_wc_conflict_description_create_prop2(local_abspath,
+ svn_node_unknown,
+ "", result_pool);
+
+ /* ### This should be changed. The prej file should be stored
+ * ### separately from the other files. We need to rev the
+ * ### conflict description struct for this. */
+ desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file);
+
+ APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc;
+
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, conflicted_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_string_t *old_value;
+ svn_string_t *my_value;
+ svn_string_t *their_value;
+ svn_wc_conflict_description2_t *desc;
+
+ svn_pool_clear(iterpool);
+
+ desc = svn_wc_conflict_description_create_prop2(local_abspath,
+ svn_node_unknown,
+ propname,
+ result_pool);
+
+ desc->property_name = apr_pstrdup(result_pool, propname);
+
+ my_value = apr_hash_get(my_props, propname, APR_HASH_KEY_STRING);
+ their_value = apr_hash_get(their_props, propname,
+ APR_HASH_KEY_STRING);
+
+ /* Compute the incoming side of the conflict ('action'). */
+ if (their_value == NULL)
+ desc->action = svn_wc_conflict_action_delete;
+ else if (my_value == NULL)
+ desc->action = svn_wc_conflict_action_add;
+ else
+ desc->action = svn_wc_conflict_action_edit;
+
+ /* Compute the local side of the conflict ('reason'). */
+ if (my_value == NULL)
+ desc->reason = svn_wc_conflict_reason_deleted;
+ else if (their_value == NULL)
+ desc->reason = svn_wc_conflict_reason_added;
+ else
+ desc->reason = svn_wc_conflict_reason_edited;
+
+ /* ### This should be changed. The prej file should be stored
+ * ### separately from the other files. We need to rev the
+ * ### conflict description struct for this. */
+ desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file);
+
+ /* ### This should be changed. The conflict description for
+ * ### props should contain these values as svn_string_t,
+ * ### rather than in temporary files. We need to rev the
+ * ### conflict description struct for this. */
+ if (my_value)
+ {
+ svn_stream_t *s;
+ apr_size_t len;
+
+ SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, iterpool));
+ len = my_value->len;
+ SVN_ERR(svn_stream_write(s, my_value->data, &len));
+ SVN_ERR(svn_stream_close(s));
+ }
+
+ if (their_value)
+ {
+ svn_stream_t *s;
+ apr_size_t len;
+
+ /* ### Currently, their_abspath is used for the prop reject file.
+ * ### Put their value into merged instead...
+ * ### We need to rev the conflict description struct to fix this. */
+ SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, iterpool));
+ len = their_value->len;
+ SVN_ERR(svn_stream_write(s, their_value->data, &len));
+ SVN_ERR(svn_stream_close(s));
+ }
+
+ old_value = apr_hash_get(their_old_props, propname, APR_HASH_KEY_STRING);
+ if (old_value)
+ {
+ svn_stream_t *s;
+ apr_size_t len;
+
+ SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, iterpool));
+ len = old_value->len;
+ SVN_ERR(svn_stream_write(s, old_value->data, &len));
+ SVN_ERR(svn_stream_close(s));
+ }
+
+ APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc;
+ }
+ svn_pool_destroy(iterpool);
+
return SVN_NO_ERROR;
}
@@ -1914,6 +2166,7 @@
svn_wc__read_conflicts(const apr_array_header_t **conflicts,
svn_wc__db_t *db,
const char *local_abspath,
+ svn_boolean_t create_tempfiles,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
@@ -1945,21 +2198,9 @@
sizeof(svn_wc_conflict_description2_t*));
if (prop_conflicted)
- {
- svn_wc_conflict_description2_t *desc;
- desc = svn_wc_conflict_description_create_prop2(local_abspath,
- svn_node_unknown,
- "",
- result_pool);
-
- SVN_ERR(svn_wc__conflict_read_prop_conflict(&desc->their_abspath,
- NULL, NULL, NULL, NULL,
- db, local_abspath,
- conflict_skel,
- result_pool, scratch_pool));
-
- APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc;
- }
+ SVN_ERR(read_prop_conflicts(cflcts, db, local_abspath, conflict_skel,
+ create_tempfiles,
+ result_pool, scratch_pool));
if (text_conflicted)
{
@@ -1982,94 +2223,10 @@
if (tree_conflicted)
{
svn_wc_conflict_description2_t *desc;
- svn_wc_conflict_version_t *v1;
- svn_wc_conflict_version_t *v2;
- svn_node_kind_t tc_kind;
- svn_wc_conflict_reason_t local_change;
- svn_wc_conflict_action_t incoming_change;
- SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change,
- &incoming_change,
- db, local_abspath,
- conflict_skel,
- scratch_pool, scratch_pool));
-
- v1 = (locations && locations->nelts > 0)
- ? APR_ARRAY_IDX(locations, 0, svn_wc_conflict_version_t *)
- : NULL;
-
- v2 = (locations && locations->nelts > 1)
- ? APR_ARRAY_IDX(locations, 1, svn_wc_conflict_version_t *)
- : NULL;
-
- if (incoming_change != svn_wc_conflict_action_delete
- && (operation == svn_wc_operation_update
- || operation == svn_wc_operation_switch))
- {
- svn_wc__db_status_t status;
- svn_revnum_t revision;
- const char *repos_relpath;
- const char *repos_root_url;
- const char *repos_uuid;
- svn_kind_t kind;
- svn_error_t *err;
-
- /* ### Theoretically we should just fetch the BASE information
- here. This code might need tweaks until all tree conflicts
- are installed in the proper state */
-
- SVN_ERR_ASSERT(v2 == NULL); /* Not set for update and switch */
-
- /* With an update or switch we have to fetch the second location
- for a tree conflict from WORKING. (For text or prop from BASE)
- */
- err = svn_wc__db_base_get_info(&status, &kind, &revision,
- &repos_relpath, &repos_root_url,
- &repos_uuid, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- db, local_abspath,
- scratch_pool, scratch_pool);
-
- if (err)
- {
- if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
- return svn_error_trace(err);
-
- svn_error_clear(err);
- /* Ignore BASE */
-
- tc_kind = svn_node_file; /* Avoid assertion */
- }
- else if (repos_relpath)
- {
- v2 = svn_wc_conflict_version_create2(repos_root_url,
- repos_uuid,
- repos_relpath,
- revision,
- svn__node_kind_from_kind(kind),
- scratch_pool);
- tc_kind = svn__node_kind_from_kind(kind);
- }
- else
- tc_kind = svn_node_file; /* Avoid assertion */
- }
- else
- {
- if (v1)
- tc_kind = v1->node_kind;
- else if (v2)
- tc_kind = v2->node_kind;
- else
- tc_kind = svn_node_file; /* Avoid assertion */
- }
-
- desc = svn_wc_conflict_description_create_tree2(local_abspath, tc_kind,
- operation, v1, v2,
- result_pool);
-
- desc->reason = local_change;
- desc->action = incoming_change;
-
+ SVN_ERR(setup_tree_conflict_desc(&desc, operation, locations,
+ conflict_skel, local_abspath, db,
+ result_pool, scratch_pool));
APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc;
}
@@ -2101,6 +2258,7 @@
svn_boolean_t resolve_props,
svn_boolean_t resolve_tree,
svn_wc_conflict_choice_t conflict_choice,
+ svn_skel_t *work_items,
svn_cancel_func_t cancel_func_t,
void *cancel_baton,
apr_pool_t *scratch_pool)
@@ -2110,7 +2268,6 @@
svn_boolean_t text_conflicted;
svn_boolean_t prop_conflicted;
svn_boolean_t tree_conflicted;
- svn_skel_t *work_items = NULL;
svn_skel_t *work_item;
apr_pool_t *pool = scratch_pool;
@@ -2210,6 +2367,11 @@
&work_item, db, local_abspath,
auto_resolve_src, local_abspath, pool, pool));
work_items = svn_wc__wq_merge(work_items, work_item, pool);
+
+ SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db,
+ local_abspath,
+ pool, pool));
+ work_items = svn_wc__wq_merge(work_items, work_item, pool);
}
/* Legacy behavior: Only report text conflicts as resolved when at least
@@ -2269,10 +2431,8 @@
apr_hash_t *their_old_props;
apr_hash_t *their_props;
apr_hash_t *conflicted_props;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
apr_hash_t *old_props;
apr_hash_t *resolve_from = NULL;
-#endif
SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file,
&mine_props, &their_old_props,
@@ -2280,7 +2440,6 @@
db, local_abspath, conflicts,
scratch_pool, scratch_pool));
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
if (operation == svn_wc_operation_merge)
SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath,
scratch_pool, scratch_pool));
@@ -2344,7 +2503,6 @@
FALSE, NULL, NULL,
scratch_pool));
}
-#endif
/* Legacy behavior: Only report property conflicts as resolved when the
property reject file exists
@@ -2399,6 +2557,7 @@
FALSE /* resolve_props */,
FALSE /* resolve_tree */,
svn_wc_conflict_choose_merged,
+ NULL,
NULL, NULL, /* cancel_func */
scratch_pool));
}
@@ -2440,7 +2599,7 @@
iterpool = svn_pool_create(scratch_pool);
- SVN_ERR(svn_wc__read_conflicts(&conflicts, db, local_abspath,
+ SVN_ERR(svn_wc__read_conflicts(&conflicts, db, local_abspath, TRUE,
scratch_pool, iterpool));
for (i = 0; i < conflicts->nelts; i++)
@@ -2448,6 +2607,8 @@
const svn_wc_conflict_description2_t *cd;
svn_boolean_t did_resolve;
svn_wc_conflict_choice_t my_choice = cswb->conflict_choice;
+ svn_skel_t *work_items = NULL;
+
cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *);
@@ -2478,15 +2639,32 @@
if (!cswb->resolve_tree)
break;
- /* For now, we only clear tree conflict information and resolve
- * to the working state. There is no way to pick theirs-full
- * or mine-full, etc. Throw an error if the user expects us
- * to be smarter than we really are. */
- if (my_choice != svn_wc_conflict_choose_merged)
+ /* After updates, we can resolve local moved-away vs. any incoming
+ * change, either by updating the moved-away node (mine-conflict)
+ * or by breaking the move (theirs-conflict). */
+ if ((cd->operation == svn_wc_operation_update ||
+ cd->operation == svn_wc_operation_switch) &&
+ cd->reason == svn_wc_conflict_reason_moved_away)
{
+ if (my_choice == svn_wc_conflict_choose_mine_conflict)
+ SVN_ERR(svn_wc__db_update_moved_away_conflict_victim(
+ &work_items, local_abspath, cswb->db,
+ cswb->notify_func, cswb->notify_baton,
+ cswb->cancel_func, cswb->cancel_baton,
+ scratch_pool, scratch_pool));
+ else if (my_choice == svn_wc_conflict_choose_theirs_conflict)
+ {
+ /* ### TODO break move */
+ }
+ }
+ else if (my_choice != svn_wc_conflict_choose_merged)
+ {
+ /* For other tree conflicts, there is no way to pick
+ * theirs-full or mine-full, etc. Throw an error if the
+ * user expects us to be smarter than we really are. */
return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
NULL,
- _("Tree conflicts can only be "
+ _("Tree conflict can only be "
"resolved to 'working' state; "
"'%s' not resolved"),
svn_dirent_local_style(local_abspath,
@@ -2500,6 +2678,7 @@
FALSE /* resolve_props */,
TRUE /* resolve_tree */,
my_choice,
+ work_items,
cswb->cancel_func,
cswb->cancel_baton,
iterpool));
@@ -2518,6 +2697,7 @@
FALSE /* resolve_props */,
FALSE /* resolve_tree */,
my_choice,
+ NULL,
cswb->cancel_func,
cswb->cancel_baton,
iterpool));
@@ -2547,6 +2727,7 @@
TRUE /* resolve_props */,
FALSE /* resolve_tree */,
my_choice,
+ NULL,
cswb->cancel_func,
cswb->cancel_baton,
iterpool));
diff --git a/subversion/libsvn_wc/conflicts.h b/subversion/libsvn_wc/conflicts.h
index 830d3b5..10a7a53 100644
--- a/subversion/libsvn_wc/conflicts.h
+++ b/subversion/libsvn_wc/conflicts.h
@@ -260,6 +260,8 @@
*
* Output arguments can be NULL if the value is not necessary.
*
+ * ### stsp asks: what is LOCATIONS?
+ *
* TEXT_, PROP_ and TREE_CONFLICTED (when not NULL) will be set to TRUE
* when the conflict contains the specified kind of conflict, otherwise
* to false.
@@ -392,7 +394,6 @@
const char *local_abspath,
apr_pool_t *scratch_pool);
-
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_wc/context.c b/subversion/libsvn_wc/context.c
index 6ae3fd9..4bf2369 100644
--- a/subversion/libsvn_wc/context.c
+++ b/subversion/libsvn_wc/context.c
@@ -65,10 +65,12 @@
{
svn_wc_context_t *ctx = apr_pcalloc(result_pool, sizeof(*ctx));
- /* Create the state_pool, and open up a wc_db in it. */
+ /* Create the state_pool, and open up a wc_db in it.
+ * Since config contains a private mutable member but C doesn't support
+ * we need to make it writable */
ctx->state_pool = result_pool;
- SVN_ERR(svn_wc__db_open(&ctx->db, config,
- TRUE, TRUE, ctx->state_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_open(&ctx->db, (svn_config_t *)config,
+ FALSE, TRUE, ctx->state_pool, scratch_pool));
ctx->close_db_on_destroy = TRUE;
apr_pool_cleanup_register(result_pool, ctx, close_ctx_apr,
diff --git a/subversion/libsvn_wc/copy.c b/subversion/libsvn_wc/copy.c
index 7d06b23..1687b29 100644
--- a/subversion/libsvn_wc/copy.c
+++ b/subversion/libsvn_wc/copy.c
@@ -36,6 +36,7 @@
#include "wc.h"
#include "workqueue.h"
#include "props.h"
+#include "conflicts.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
@@ -206,7 +207,6 @@
if (!metadata_only)
{
const char *my_src_abspath = NULL;
- int i;
svn_boolean_t handle_as_unversioned = FALSE;
/* By default, take the copy source as given. */
@@ -214,26 +214,27 @@
if (conflicted)
{
- const apr_array_header_t *conflicts;
- const char *conflict_working = NULL;
+ svn_skel_t *conflict;
+ const char *conflict_working;
+ svn_error_t *err;
/* Is there a text conflict at the source path? */
- SVN_ERR(svn_wc__read_conflicts(&conflicts, db, src_abspath,
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
scratch_pool, scratch_pool));
- for (i = 0; i < conflicts->nelts; i++)
+ err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL,
+ db, src_abspath, conflict,
+ scratch_pool,
+ scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_MISSING)
{
- const svn_wc_conflict_description2_t *desc;
-
- desc = APR_ARRAY_IDX(conflicts, i,
- const svn_wc_conflict_description2_t*);
-
- if (desc->kind == svn_wc_conflict_kind_text)
- {
- conflict_working = desc->my_abspath;
- break;
- }
+ /* not text conflicted */
+ svn_error_clear(err);
+ conflict_working = NULL;
}
+ else
+ SVN_ERR(err);
if (conflict_working)
{
@@ -287,7 +288,9 @@
otherwise copy both the versioned metadata and the filesystem nodes (even
if they are the wrong kind, and including unversioned children).
If IS_MOVE is true, record move information in working copy meta
- data in addition to copying the directory.
+ data in addition to copying the directory. If IS_MOVE is TRUE and
+ ALLOW_MIXED_REVISIONS is FALSE, raise an error if a move of a
+ mixed-revision subtree is attempted.
WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root)
*/
@@ -299,6 +302,7 @@
const char *tmpdir_abspath,
svn_boolean_t metadata_only,
svn_boolean_t is_move,
+ svn_boolean_t allow_mixed_revisions,
svn_boolean_t within_one_wc,
svn_cancel_func_t cancel_func,
void *cancel_baton,
@@ -315,6 +319,24 @@
svn_node_kind_t disk_kind;
apr_pool_t *iterpool;
+ if (is_move && !allow_mixed_revisions)
+ {
+ svn_revnum_t min_rev;
+ svn_revnum_t max_rev;
+
+ /* Verify that the move source is a single-revision subtree. */
+ SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db,
+ src_abspath, FALSE, scratch_pool));
+ if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) &&
+ min_rev != max_rev)
+ return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL,
+ _("Cannot move mixed-revision subtree '%s' "
+ "[%lu:%lu]; try updating it first"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool),
+ min_rev, max_rev);
+ }
+
/* Prepare a temp copy of the single filesystem node (usually a dir). */
if (!metadata_only)
{
@@ -412,7 +434,8 @@
SVN_ERR(copy_versioned_dir(db,
child_src_abspath, child_dst_abspath,
dst_op_root_abspath, tmpdir_abspath,
- metadata_only, is_move, within_one_wc,
+ metadata_only, is_move,
+ allow_mixed_revisions, within_one_wc,
cancel_func, cancel_baton, NULL, NULL,
iterpool));
else
@@ -523,6 +546,7 @@
const char *dst_abspath,
svn_boolean_t metadata_only,
svn_boolean_t is_move,
+ svn_boolean_t allow_mixed_revisions,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
@@ -737,8 +761,8 @@
else
{
err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath,
- tmpdir_abspath,
- metadata_only, is_move, within_one_wc,
+ tmpdir_abspath, metadata_only, is_move,
+ allow_mixed_revisions, within_one_wc,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool);
@@ -773,6 +797,7 @@
{
return svn_error_trace(copy_or_move(wc_ctx, src_abspath, dst_abspath,
metadata_only, FALSE /* is_move */,
+ TRUE /* allow_mixed_revisions */,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
@@ -792,75 +817,40 @@
const char *node_abspath,
apr_pool_t *scratch_pool)
{
- const apr_array_header_t *conflicts;
+ svn_skel_t *conflict;
- SVN_ERR(svn_wc__read_conflicts(&conflicts, db, src_abspath,
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath,
scratch_pool, scratch_pool));
/* Do we have conflict markers that should be removed? */
- if (conflicts != NULL)
+ if (conflict != NULL)
{
+ const apr_array_header_t *markers;
int i;
const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool);
const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool);
- /* No iterpool: Maximum number of possible conflict markers is 4 */
+ SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath,
+ conflict,
+ scratch_pool, scratch_pool));
- for (i = 0; i < conflicts->nelts; i++)
+ /* No iterpool: Maximum number of possible conflict markers is 4 */
+ for (i = 0; markers && (i < markers->nelts); i++)
{
- const svn_wc_conflict_description2_t *desc;
+ const char *marker_abspath;
const char *child_relpath;
const char *child_abpath;
- desc = APR_ARRAY_IDX(conflicts, i,
- const svn_wc_conflict_description2_t*);
+ marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
- if (desc->kind != svn_wc_conflict_kind_text
- && desc->kind != svn_wc_conflict_kind_property)
- continue;
+ child_relpath = svn_dirent_is_child(src_dir, marker_abspath, NULL);
- if (desc->base_abspath != NULL)
+ if (child_relpath)
{
- child_relpath = svn_dirent_is_child(src_dir, desc->base_abspath,
- NULL);
+ child_abpath = svn_dirent_join(dst_dir, child_relpath,
+ scratch_pool);
- if (child_relpath)
- {
- child_abpath = svn_dirent_join(dst_dir, child_relpath,
- scratch_pool);
-
- SVN_ERR(svn_io_remove_file2(child_abpath, TRUE,
- scratch_pool));
- }
- }
- if (desc->their_abspath != NULL)
- {
- child_relpath = svn_dirent_is_child(src_dir, desc->their_abspath,
- NULL);
-
- if (child_relpath)
- {
- child_abpath = svn_dirent_join(dst_dir, child_relpath,
- scratch_pool);
-
- SVN_ERR(svn_io_remove_file2(child_abpath, TRUE,
- scratch_pool));
- }
- }
- if (desc->my_abspath != NULL)
- {
- child_relpath = svn_dirent_is_child(src_dir, desc->my_abspath,
- NULL);
-
- if (child_relpath)
- {
- child_abpath = svn_dirent_join(dst_dir, child_relpath,
- scratch_pool);
-
- /* ### Copy child_abspath to node_abspath if it exists? */
- SVN_ERR(svn_io_remove_file2(child_abpath, TRUE,
- scratch_pool));
- }
+ SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, scratch_pool));
}
}
}
@@ -928,21 +918,23 @@
}
svn_error_t *
-svn_wc_move(svn_wc_context_t *wc_ctx,
- const char *src_abspath,
- const char *dst_abspath,
- svn_boolean_t metadata_only,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- svn_wc_notify_func2_t notify_func,
- void *notify_baton,
- apr_pool_t *scratch_pool)
+svn_wc__move2(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_boolean_t allow_mixed_revisions,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
{
svn_wc__db_t *db = wc_ctx->db;
SVN_ERR(copy_or_move(wc_ctx, src_abspath, dst_abspath,
TRUE /* metadata_only */,
TRUE /* is_move */,
+ allow_mixed_revisions,
cancel_func, cancel_baton,
notify_func, notify_baton,
scratch_pool));
diff --git a/subversion/libsvn_wc/deprecated.c b/subversion/libsvn_wc/deprecated.c
index 69f591f..5b2bc45 100644
--- a/subversion/libsvn_wc/deprecated.c
+++ b/subversion/libsvn_wc/deprecated.c
@@ -3269,7 +3269,7 @@
target_revision,
wc_ctx,
anchor_abspath,
- target_basename,
+ target_basename, NULL,
use_commit_times,
depth, depth_is_sticky,
allow_unver_obstructions,
@@ -3455,7 +3455,7 @@
target_revision,
wc_ctx,
anchor_abspath, target_basename,
- switch_url,
+ switch_url, NULL,
use_commit_times,
depth, depth_is_sticky,
allow_unver_obstructions,
@@ -4463,3 +4463,22 @@
return svn_error_trace(svn_wc_context_destroy(wc_ctx));
}
+
+svn_error_t *
+svn_wc_move(svn_wc_context_t *wc_ctx,
+ const char *src_abspath,
+ const char *dst_abspath,
+ svn_boolean_t metadata_only,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__move2(wc_ctx, src_abspath, dst_abspath,
+ metadata_only,
+ TRUE, /* allow_mixed_revisions */
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+}
diff --git a/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c
index 7a999d4..3c3b9ae 100644
--- a/subversion/libsvn_wc/entries.c
+++ b/subversion/libsvn_wc/entries.c
@@ -455,6 +455,7 @@
SVN_ERR(svn_wc__read_conflicts(&child_conflicts,
db, child_abspath,
+ FALSE /* create tempfiles */,
scratch_pool, scratch_pool));
for (j = 0; j < child_conflicts->nelts; j++)
@@ -874,37 +875,53 @@
if (conflicted)
{
- const apr_array_header_t *conflicts;
- int j;
- SVN_ERR(svn_wc__read_conflicts(&conflicts, db, entry_abspath,
- scratch_pool, scratch_pool));
+ svn_skel_t *conflict;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath,
+ scratch_pool, scratch_pool));
- for (j = 0; j < conflicts->nelts; j++)
+ SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted,
+ &prop_conflicted, NULL,
+ db, dir_abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (text_conflicted)
{
- const svn_wc_conflict_description2_t *cd;
- cd = APR_ARRAY_IDX(conflicts, j,
- const svn_wc_conflict_description2_t *);
+ const char *my_abspath;
+ const char *their_old_abspath;
+ const char *their_abspath;
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath,
+ &their_old_abspath,
+ &their_abspath,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
- switch (cd->kind)
- {
- case svn_wc_conflict_kind_text:
- if (cd->base_abspath)
- entry->conflict_old = svn_dirent_basename(cd->base_abspath,
- result_pool);
- if (cd->their_abspath)
- entry->conflict_new = svn_dirent_basename(cd->their_abspath,
- result_pool);
- if (cd->my_abspath)
- entry->conflict_wrk = svn_dirent_basename(cd->my_abspath,
- result_pool);
- break;
- case svn_wc_conflict_kind_property:
- entry->prejfile = svn_dirent_basename(cd->their_abspath,
- result_pool);
- break;
- case svn_wc_conflict_kind_tree:
- break;
- }
+ if (my_abspath)
+ entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool);
+
+ if (their_old_abspath)
+ entry->conflict_old = svn_dirent_basename(their_old_abspath,
+ result_pool);
+
+ if (their_abspath)
+ entry->conflict_new = svn_dirent_basename(their_abspath,
+ result_pool);
+ }
+
+ if (prop_conflicted)
+ {
+ const char *prej_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL,
+ NULL, NULL, NULL,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (prej_abspath)
+ entry->prejfile = svn_dirent_basename(prej_abspath, result_pool);
}
}
@@ -1372,10 +1389,10 @@
}
svn_error_t *
-svn_wc_entries_read(apr_hash_t **entries,
- svn_wc_adm_access_t *adm_access,
- svn_boolean_t show_hidden,
- apr_pool_t *pool)
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
{
apr_hash_t *new_entries;
@@ -1384,7 +1401,7 @@
{
svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
const char *local_abspath = svn_wc__adm_access_abspath(adm_access);
- apr_pool_t *result_pool = svn_wc_adm_access_pool(adm_access);
+ apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access);
svn_sqlite__db_t *sdb;
struct entries_read_baton_t erb;
@@ -1408,12 +1425,21 @@
*entries = new_entries;
else
SVN_ERR(prune_deleted(entries, new_entries,
- svn_wc_adm_access_pool(adm_access),
+ svn_wc__adm_access_pool_internal(adm_access),
pool));
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_wc_entries_read(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access,
+ show_hidden, pool));
+}
/* No transaction required: called from write_entry which is itself
transaction-wrapped. */
@@ -1459,7 +1485,7 @@
else if (node->presence == svn_wc__db_status_excluded)
SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded"));
else if (node->presence == svn_wc__db_status_server_excluded)
- SVN_ERR(svn_sqlite__bind_text(stmt, 8, "absent"));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded"));
if (node->kind == svn_node_none)
SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown"));
@@ -1508,9 +1534,7 @@
apr_pool_t *scratch_pool)
{
svn_sqlite__stmt_t *stmt;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
svn_skel_t *conflict_data = NULL;
-#endif
SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE));
@@ -1525,22 +1549,6 @@
if (actual_node->changelist)
SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist));
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- if (actual_node->conflict_old
- || actual_node->conflict_new
- || actual_node->conflict_working)
- {
- SVN_ERR(svn_sqlite__bind_text(stmt, 7, actual_node->conflict_old));
- SVN_ERR(svn_sqlite__bind_text(stmt, 8, actual_node->conflict_new));
- SVN_ERR(svn_sqlite__bind_text(stmt, 9, actual_node->conflict_working));
- }
-
- if (actual_node->prop_reject)
- SVN_ERR(svn_sqlite__bind_text(stmt, 10, actual_node->prop_reject));
-
- if (actual_node->tree_conflict_data)
- SVN_ERR(svn_sqlite__bind_text(stmt, 11, actual_node->tree_conflict_data));
-#else
SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(
&conflict_data,
db, wri_abspath,
@@ -1559,7 +1567,6 @@
SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len));
}
-#endif
/* Execute and reset the insert clause. */
return svn_error_trace(svn_sqlite__insert(NULL, stmt));
@@ -2447,7 +2454,8 @@
svn_error_t *err;
svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
- err = svn_wc_entries_read(&entries, adm_access, show_hidden, pool);
+ err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden,
+ pool);
if (err)
SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
diff --git a/subversion/libsvn_wc/entries.h b/subversion/libsvn_wc/entries.h
index 7e07266..87caa46 100644
--- a/subversion/libsvn_wc/entries.h
+++ b/subversion/libsvn_wc/entries.h
@@ -148,6 +148,15 @@
const svn_opt_revision_t *rev,
apr_pool_t *pool);
+/* Non-deprecated wrapper variant of svn_wc_entries_read used implement
+ legacy API functions. See svn_wc_entries_read for a detailed description.
+ */
+svn_error_t *
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c
index 5a6b3d7..159313f 100644
--- a/subversion/libsvn_wc/externals.c
+++ b/subversion/libsvn_wc/externals.c
@@ -381,6 +381,10 @@
/* List of incoming propchanges */
apr_array_header_t *propchanges;
+ /* Array of svn_prop_inherited_item_t * structures representing the
+ properties inherited by the base node at LOCAL_ABSPATH. */
+ apr_array_header_t *iprops;
+
/* The last change information */
svn_revnum_t changed_rev;
apr_time_t changed_date;
@@ -810,6 +814,7 @@
eb->repos_uuid,
*eb->target_revision,
new_pristine_props,
+ eb->iprops,
eb->changed_rev,
eb->changed_date,
eb->changed_author,
@@ -825,6 +830,12 @@
all_work_items,
pool));
+ /* close_edit may also update iprops for switched files, catching
+ those for which close_file is never called (e.g. an update of a
+ file external with no changes). So as a minor optimization we
+ clear the iprops so as not to set them again in close_edit. */
+ eb->iprops = NULL;
+
SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
eb->cancel_func, eb->cancel_baton, pool));
}
@@ -864,8 +875,18 @@
{
struct edit_baton *eb = edit_baton;
- if (!eb->file_closed)
+ if (!eb->file_closed
+ || eb->iprops)
{
+ apr_hash_t *wcroot_iprops = NULL;
+
+ if (eb->iprops)
+ {
+ wcroot_iprops = apr_hash_make(pool);
+ apr_hash_set(wcroot_iprops, eb->local_abspath, APR_HASH_KEY_STRING,
+ eb->iprops);
+ }
+
/* The node wasn't updated, so we just have to bump its revision */
SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
eb->local_abspath,
@@ -873,6 +894,7 @@
NULL, NULL, NULL,
*eb->target_revision,
apr_hash_make(pool),
+ wcroot_iprops,
pool));
}
@@ -889,6 +911,7 @@
const char *url,
const char *repos_root_url,
const char *repos_uuid,
+ apr_array_header_t *iprops,
svn_boolean_t use_commit_times,
const char *diff3_cmd,
const apr_array_header_t *preserved_exts,
@@ -924,6 +947,8 @@
eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
+ eb->iprops = iprops;
+
eb->use_commit_times = use_commit_times;
eb->ext_patterns = preserved_exts;
eb->diff3cmd = diff3_cmd;
diff --git a/subversion/libsvn_wc/info.c b/subversion/libsvn_wc/info.c
index 1485de0..168aaad 100644
--- a/subversion/libsvn_wc/info.c
+++ b/subversion/libsvn_wc/info.c
@@ -281,6 +281,7 @@
if (conflicted)
SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db,
local_abspath,
+ TRUE /* ### create tempfiles */,
result_pool, scratch_pool));
else
wc_info->conflicts = NULL;
@@ -475,25 +476,25 @@
&& fetch_actual_only
&& err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
- const svn_wc_conflict_description2_t *root_tree_conflict;
+ svn_boolean_t tree_conflicted;
svn_error_t *err2;
- err2 = svn_wc__get_tree_conflict(&root_tree_conflict,
- wc_ctx, local_abspath,
- scratch_pool, iterpool);
+ err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted,
+ wc_ctx->db, local_abspath,
+ iterpool);
if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND))
{
svn_error_clear(err2);
return svn_error_trace(err);
}
- else if (err2 || !root_tree_conflict)
+ else if (err2 || !tree_conflicted)
return svn_error_compose_create(err, err2);
svn_error_clear(err);
apr_hash_set(fe_baton.tree_conflicts, local_abspath,
- APR_HASH_KEY_STRING, root_tree_conflict);
+ APR_HASH_KEY_STRING, "");
}
else
SVN_ERR(err);
@@ -526,6 +527,7 @@
SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts,
wc_ctx->db, this_abspath,
+ TRUE /* ### create tempfiles */,
iterpool, iterpool));
if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts)
diff --git a/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c
index d99f134..f3c1b5c 100644
--- a/subversion/libsvn_wc/lock.c
+++ b/subversion/libsvn_wc/lock.c
@@ -97,7 +97,9 @@
{
svn_node_kind_t kind;
- if (err->apr_err != SVN_ERR_WC_MISSING)
+ if (err->apr_err != SVN_ERR_WC_MISSING &&
+ err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT &&
+ err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
return svn_error_trace(err);
svn_error_clear(err);
@@ -330,7 +332,7 @@
run, but the subpools will NOT be destroyed) */
scratch_pool = svn_pool_create(lock->pool);
- err = svn_wc__db_open(&db, NULL /* ### config. need! */, TRUE, TRUE,
+ err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
scratch_pool, scratch_pool);
if (!err)
{
@@ -780,7 +782,7 @@
do it here. */
/* ### we could optimize around levels_to_lock==0, but much of this
### is going to be simplified soon anyways. */
- SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, TRUE, TRUE,
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE,
pool, pool));
db_provided = FALSE;
}
@@ -810,7 +812,7 @@
/* Ugh. Too bad about having to open a DB. */
SVN_ERR(svn_wc__db_open(&db,
- NULL /* ### config */, TRUE, TRUE, pool, pool));
+ NULL /* ### config */, FALSE, TRUE, pool, pool));
err = probe(db, &dir, path, pool);
svn_error_clear(svn_wc__db_close(db));
SVN_ERR(err);
@@ -1156,7 +1158,7 @@
### given that we need DB for format detection, may as well keep this.
### in any case, much of this is going to be simplified soon anyways. */
if (!db_provided)
- SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ TRUE, TRUE,
+ SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE,
pool, pool));
if (svn_path_is_empty(path)
@@ -1477,6 +1479,11 @@
return adm_access->pool;
}
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->pool;
+}
void
svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access,
diff --git a/subversion/libsvn_wc/lock.h b/subversion/libsvn_wc/lock.h
index f5b9874..e015c7e 100644
--- a/subversion/libsvn_wc/lock.h
+++ b/subversion/libsvn_wc/lock.h
@@ -77,6 +77,13 @@
const char *
svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access);
+/* Return the pool used by access baton ADM_ACCESS.
+ * Note: This is a non-deprecated variant of svn_wc_adm_access_pool for
+ * libsvn_wc internal usage only.
+ */
+apr_pool_t *
+svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_wc/node.c b/subversion/libsvn_wc/node.c
index 6c5dd03..c71b32e 100644
--- a/subversion/libsvn_wc/node.c
+++ b/subversion/libsvn_wc/node.c
@@ -219,6 +219,70 @@
return SVN_NO_ERROR;
}
+/* ### This is essentially a copy-paste of svn_wc__internal_get_url().
+ * ### If we decide to keep this one, then it should be rewritten to avoid
+ * ### code duplication.*/
+svn_error_t *
+svn_wc__internal_get_repos_relpath(const char **repos_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_boolean_t have_base;
+
+ SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, repos_relpath,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ &have_base, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ if (*repos_relpath == NULL)
+ {
+ if (status == svn_wc__db_status_added)
+ {
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else if (have_base)
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, NULL,
+ NULL,
+ db, local_abspath,
+ result_pool, scratch_pool));
+ }
+ else if (status == svn_wc__db_status_excluded
+ || (!have_base && (status == svn_wc__db_status_deleted)))
+ {
+ const char *parent_abspath, *name, *parent_relpath;
+
+ svn_dirent_split(&parent_abspath, &name, local_abspath,
+ scratch_pool);
+ SVN_ERR(svn_wc__internal_get_repos_relpath(&parent_relpath, db,
+ parent_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (parent_relpath)
+ *repos_relpath = svn_relpath_join(parent_relpath, name,
+ result_pool);
+ }
+ else
+ {
+ /* Status: obstructed, obstructed_add */
+ *repos_relpath = NULL;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_wc__node_get_repos_info(const char **repos_root_url,
const char **repos_uuid,
@@ -348,9 +412,6 @@
result_pool, scratch_pool));
}
-/* ### This is essentially a copy-paste of svn_wc__internal_get_url().
- * ### If we decide to keep this one, then it should be rewritten to avoid
- * ### code duplication.*/
svn_error_t *
svn_wc__node_get_repos_relpath(const char **repos_relpath,
svn_wc_context_t *wc_ctx,
@@ -358,58 +419,11 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_wc__db_status_t status;
- svn_boolean_t have_base;
-
- SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, repos_relpath,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- &have_base, NULL, NULL,
- wc_ctx->db, local_abspath,
- result_pool, scratch_pool));
- if (*repos_relpath == NULL)
- {
- if (status == svn_wc__db_status_added)
- {
- SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath,
- NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wc_ctx->db, local_abspath,
- result_pool, scratch_pool));
- }
- else if (have_base)
- {
- SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, NULL,
- NULL,
- wc_ctx->db, local_abspath,
- result_pool, scratch_pool));
- }
- else if (status == svn_wc__db_status_excluded
- || (!have_base && (status == svn_wc__db_status_deleted)))
- {
- const char *parent_abspath, *name, *parent_relpath;
-
- svn_dirent_split(&parent_abspath, &name, local_abspath,
- scratch_pool);
- SVN_ERR(svn_wc__node_get_repos_relpath(&parent_relpath, wc_ctx,
- parent_abspath,
- scratch_pool,
- scratch_pool));
-
- if (parent_relpath)
- *repos_relpath = svn_relpath_join(parent_relpath, name,
- result_pool);
- }
- else
- {
- /* Status: obstructed, obstructed_add */
- *repos_relpath = NULL;
- return SVN_NO_ERROR;
- }
- }
-
- return SVN_NO_ERROR;
+ return svn_error_trace(svn_wc__internal_get_repos_relpath(repos_relpath,
+ wc_ctx->db,
+ local_abspath,
+ result_pool,
+ scratch_pool));
}
svn_error_t *
@@ -768,10 +782,12 @@
}
svn_error_t *
-svn_wc__node_is_status_server_excluded(svn_boolean_t *is_server_excluded,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool)
+svn_wc__node_is_not_present(svn_boolean_t *is_not_present,
+ svn_boolean_t *is_excluded,
+ svn_boolean_t *is_server_excluded,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
{
svn_wc__db_status_t status;
@@ -782,47 +798,15 @@
NULL, NULL, NULL, NULL, NULL,
wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
- *is_server_excluded = (status == svn_wc__db_status_server_excluded);
- return SVN_NO_ERROR;
-}
+ if (is_not_present)
+ *is_not_present = (status == svn_wc__db_status_not_present);
-svn_error_t *
-svn_wc__node_is_status_not_present(svn_boolean_t *is_not_present,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool)
-{
- svn_wc__db_status_t status;
+ if (is_excluded)
+ *is_excluded = (status == svn_wc__db_status_excluded);
- SVN_ERR(svn_wc__db_read_info(&status,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL,
- wc_ctx->db, local_abspath,
- scratch_pool, scratch_pool));
- *is_not_present = (status == svn_wc__db_status_not_present);
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_wc__node_is_status_excluded(svn_boolean_t *is_excluded,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool)
-{
- svn_wc__db_status_t status;
-
- SVN_ERR(svn_wc__db_read_info(&status,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL,
- wc_ctx->db, local_abspath,
- scratch_pool, scratch_pool));
- *is_excluded = (status == svn_wc__db_status_excluded);
+ if (is_server_excluded)
+ *is_server_excluded = (status == svn_wc__db_status_server_excluded);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_wc/old-and-busted.c b/subversion/libsvn_wc/old-and-busted.c
index c0ebaba..511d686 100644
--- a/subversion/libsvn_wc/old-and-busted.c
+++ b/subversion/libsvn_wc/old-and-busted.c
@@ -1327,7 +1327,7 @@
/* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and
fetch all entries here (optimization) since we know how to filter
out a "hidden" node. */
- SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, pool));
+ SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool));
*entry = apr_hash_get(entries, entry_name, APR_HASH_KEY_STRING);
if (!show_hidden && *entry != NULL)
diff --git a/subversion/libsvn_wc/props.c b/subversion/libsvn_wc/props.c
index 4b35e85..df9ecaa 100644
--- a/subversion/libsvn_wc/props.c
+++ b/subversion/libsvn_wc/props.c
@@ -46,6 +46,7 @@
#include "svn_wc.h"
#include "svn_utf.h"
#include "svn_diff.h"
+#include "svn_sorts.h"
#include "private/svn_wc_private.h"
#include "private/svn_mergeinfo_private.h"
@@ -1674,6 +1675,8 @@
const char *file_prohibit[] = { SVN_PROP_IGNORE,
SVN_PROP_EXTERNALS,
+ SVN_PROP_INHERITABLE_AUTO_PROPS,
+ SVN_PROP_INHERITABLE_IGNORES,
NULL };
const char *dir_prohibit[] = { SVN_PROP_EXECUTABLE,
SVN_PROP_KEYWORDS,
@@ -2143,7 +2146,9 @@
SVN_ERR(svn_mime_type_validate(new_value->data, pool));
}
else if (strcmp(propname, SVN_PROP_IGNORE) == 0
- || strcmp(propname, SVN_PROP_EXTERNALS) == 0)
+ || strcmp(propname, SVN_PROP_EXTERNALS) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
+ || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
{
/* Make sure that the last line ends in a newline */
if (propval->len == 0
@@ -2332,3 +2337,163 @@
}
return FALSE;
}
+
+/* Remove all prop name value pairs from PROP_HASH where the property
+ name is not PROPNAME. */
+static void
+filter_unwanted_props(apr_hash_t *prop_hash,
+ const char * propname,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *ipropname = svn__apr_hash_index_key(hi);
+
+ if (strcmp(ipropname, propname) != 0)
+ apr_hash_set(prop_hash, ipropname, APR_HASH_KEY_STRING, NULL);
+ }
+ return;
+}
+
+svn_error_t *
+svn_wc__internal_get_iprops(apr_array_header_t **inherited_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *cached_iprops = NULL;
+ const char *parent_abspath = local_abspath;
+ svn_boolean_t is_wc_root = FALSE;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR_ASSERT(inherited_props);
+ *inherited_props = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+
+ /* Walk up to the root of the WC looking for inherited properties. When we
+ reach the WC root also check for cached inherited properties. */
+ while (TRUE)
+ {
+ apr_hash_t *actual_props;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_wc__internal_is_wc_root(&is_wc_root, db, parent_abspath,
+ iterpool));
+
+ if (is_wc_root)
+ {
+ const char *child_repos_relpath;
+
+ SVN_ERR(svn_wc__internal_get_repos_relpath(&child_repos_relpath,
+ db, parent_abspath,
+ iterpool, iterpool));
+
+ /* If the WC root is also the root of the repository then by
+ definition there are no inheritable properties to be had. */
+ if (child_repos_relpath[0] != '\0')
+ {
+ /* Grab the cached inherited properties for the WC root. */
+ SVN_ERR(svn_wc__db_read_cached_iprops(&cached_iprops, db,
+ parent_abspath,
+ scratch_pool,
+ iterpool));
+ }
+ }
+
+ /* If PARENT_ABSPATH is a true parent of LOCAL_ABSPATH, then
+ LOCAL_ABSPATH can inherit properties from it. */
+ if (strcmp(local_abspath, parent_abspath) != 0)
+ {
+ SVN_ERR(svn_wc__db_read_props(&actual_props, db, parent_abspath,
+ result_pool, iterpool));
+ if (actual_props)
+ {
+ /* If we only want PROPNAME filter out any other properties. */
+ if (propname)
+ filter_unwanted_props(actual_props, propname, iterpool);
+
+ if (apr_hash_count(actual_props))
+ {
+ svn_prop_inherited_item_t *iprop_elt =
+ apr_pcalloc(result_pool,
+ sizeof(svn_prop_inherited_item_t));
+ iprop_elt->path_or_url = apr_pstrdup(result_pool,
+ parent_abspath);
+ iprop_elt->prop_hash = actual_props;
+ /* Build the output array in depth-first order. */
+ svn_sort__array_insert(&iprop_elt, *inherited_props, 0);
+ }
+ }
+ }
+
+ /* Inheritance only goes as far as the nearest WC root. */
+ if (is_wc_root)
+ break;
+
+ /* Keep looking for the WC root. */
+ parent_abspath = svn_dirent_dirname(parent_abspath, scratch_pool);
+ }
+
+ if (cached_iprops)
+ {
+ for (i = cached_iprops->nelts - 1; i >= 0; i--)
+ {
+ svn_prop_inherited_item_t *cached_iprop =
+ APR_ARRAY_IDX(cached_iprops, i, svn_prop_inherited_item_t *);
+
+ /* An empty property hash in the iprops cache means there are no
+ inherited properties. */
+ if (apr_hash_count(cached_iprop->prop_hash) == 0)
+ continue;
+
+ if (propname)
+ filter_unwanted_props(cached_iprop->prop_hash, propname,
+ scratch_pool);
+
+ /* If we didn't filter everything then keep this iprop. */
+ if (apr_hash_count(cached_iprop->prop_hash))
+ svn_sort__array_insert(&cached_iprop, *inherited_props, 0);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__get_iprops(apr_array_header_t **inherited_props,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_get_iprops(inherited_props, wc_ctx->db,
+ local_abspath, propname,
+ result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
+ depth,
+ local_abspath,
+ wc_ctx->db,
+ result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c
index a4f6580..1d6d858 100644
--- a/subversion/libsvn_wc/status.c
+++ b/subversion/libsvn_wc/status.c
@@ -939,10 +939,14 @@
}
-/* Store in PATTERNS a list of all svn:ignore properties from
+/* Store in *PATTERNS a list of all svn:ignore properties from
the working copy directory, including the default ignores
passed in as IGNORES.
+ If INHERITED_PATTERNS is not NULL, then store in *INHERITED_PATTERNS
+ a list of all ignore patterns defined by the svn:inherited-ignores
+ properties explicitly set on, or inherited by, LOCAL_ABSPATH.
+
Upon return, *PATTERNS will contain zero or more (const char *)
patterns from the value of the SVN_PROP_IGNORE property set on
the working directory path.
@@ -961,6 +965,7 @@
*/
static svn_error_t *
collect_ignore_patterns(apr_array_header_t **patterns,
+ apr_array_header_t **inherited_patterns,
svn_wc__db_t *db,
const char *local_abspath,
const apr_array_header_t *ignores,
@@ -970,7 +975,7 @@
{
int i;
const svn_string_t *value;
- apr_hash_t *props;
+ apr_hash_t *props = NULL;
/* ### assert we are passed a directory? */
@@ -984,21 +989,56 @@
ignore);
}
- if (!may_have_props)
- return SVN_NO_ERROR;
+ if (may_have_props)
+ {
+ /* Add any svn:ignore globs to the PATTERNS array. */
+ SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath,
+ scratch_pool, scratch_pool));
- /* Then add any svn:ignore globs to the PATTERNS array. */
- SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath,
- scratch_pool, scratch_pool));
+ if (!props)
+ return SVN_NO_ERROR;
- if (!props)
- return SVN_NO_ERROR;
+ value = apr_hash_get(props, SVN_PROP_IGNORE, APR_HASH_KEY_STRING);
- value = apr_hash_get(props, SVN_PROP_IGNORE, APR_HASH_KEY_STRING);
+ if (value != NULL)
+ svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
+ result_pool);
+ }
- if (value != NULL)
- svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
- result_pool);
+ if (inherited_patterns)
+ {
+ apr_array_header_t *inherited_props;
+
+ *inherited_patterns = apr_array_make(result_pool, 1,
+ sizeof(const char *));
+ if (props)
+ {
+ value = apr_hash_get(props, SVN_PROP_INHERITABLE_IGNORES,
+ APR_HASH_KEY_STRING);
+ if (value != NULL)
+ svn_cstring_split_append(*inherited_patterns, value->data, "\n\r",
+ FALSE, result_pool);
+ }
+
+ SVN_ERR(svn_wc__internal_get_iprops(&inherited_props, db, local_abspath,
+ SVN_PROP_INHERITABLE_IGNORES,
+ scratch_pool, scratch_pool));
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_props, i, svn_prop_inherited_item_t *);
+
+ for (hi = apr_hash_first(scratch_pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+ svn_cstring_split_append(*inherited_patterns, propval->data,
+ "\n\r", FALSE, result_pool);
+ }
+ }
+ }
return SVN_NO_ERROR;
}
@@ -1043,13 +1083,13 @@
requested. PATH_KIND is the node kind of NAME as determined by the
caller. PATH_SPECIAL is the special status of the path, also determined
by the caller.
- PATTERNS points to a list of filename patterns which are marked as
- ignored. None of these parameter may be NULL. EXTERNALS is a hash
- of known externals definitions for this status run.
+ PATTERNS and INHERITED_PATTERNS point to a list of filename patterns which
+ are marked as ignored. None of these parameter may be NULL. EXTERNALS is
+ a hash of known externals definitions for this status run.
- If NO_IGNORE is non-zero, the item will be added regardless of
+ If NO_IGNORE is TRUE, the item will be added regardless of
whether it is ignored; otherwise we will only add the item if it
- does not match any of the patterns in PATTERNS.
+ does not match any of the patterns in PATTERN or INHERITED_IGNORES.
Allocate everything in POOL.
*/
@@ -1059,22 +1099,26 @@
const svn_io_dirent2_t *dirent,
svn_boolean_t tree_conflicted,
const apr_array_header_t *patterns,
+ const apr_array_header_t *inherited_patterns,
svn_boolean_t no_ignore,
svn_wc_status_func4_t status_func,
void *status_baton,
apr_pool_t *scratch_pool)
{
svn_boolean_t is_ignored;
+ svn_boolean_t is_mandatory_ignored;
svn_boolean_t is_external;
svn_wc_status3_t *status;
+ const char *base_name = svn_dirent_basename(local_abspath, NULL);
- is_ignored = svn_wc_match_ignore_list(
- svn_dirent_basename(local_abspath, NULL),
- patterns, scratch_pool);
-
+ is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
+ is_mandatory_ignored = svn_wc_match_ignore_list(base_name,
+ inherited_patterns,
+ scratch_pool);
SVN_ERR(assemble_unversioned(&status,
wb->db, local_abspath,
- dirent, tree_conflicted, is_ignored,
+ dirent, tree_conflicted,
+ is_ignored || is_mandatory_ignored,
scratch_pool, scratch_pool));
is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
@@ -1089,7 +1133,9 @@
/* If we aren't ignoring it, or if it's an externals path, pass this
entry to the status func. */
- if (no_ignore || (! is_ignored) || is_external)
+ if (no_ignore
+ || !(is_ignored || is_mandatory_ignored)
+ || is_external)
return svn_error_trace((*status_func)(status_baton, local_abspath,
status, scratch_pool));
@@ -1137,15 +1183,17 @@
*
* DIR_HAS_PROPS is a boolean indicating whether PARENT_ABSPATH has properties.
*
- * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in
- * this call, *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
+ * If *COLLECTED_IGNORE_PATTERNS or COLLECTED_INHERITED_IGNORE_PATTERNS are NULL
+ * and ignore patterns are needed in this call, then *COLLECTED_IGNORE_PATTERNS
+ * *COLLECTED_INHERITED_IGNORE_PATTERNS will be set to an apr_array_header_t*
* containing all ignore patterns, as returned by collect_ignore_patterns() on
- * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
- * non-NULL, it is assumed to already hold that result. This speeds up
- * repeated calls with the same PARENT_ABSPATH.
+ * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS and
+ * COLLECTED_INHERITED_IGNORE_PATTERNS is passed non-NULL, it is assumed they
+ * already hold those results. This speeds up repeated calls with the same
+ * PARENT_ABSPATH.
*
- * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
- * allocations are made in SCRATCH_POOL.
+ * *COLLECTED_IGNORE_PATTERNS and COLLECTED_INHERITED_IGNORE_PATTERNS will be
+ * allocated in RESULT_POOL. All other allocations are made in SCRATCH_POOL.
*
* The remaining parameters correspond to get_dir_status(). */
static svn_error_t*
@@ -1160,6 +1208,7 @@
svn_boolean_t dir_has_props,
svn_boolean_t unversioned_tree_conflicted,
apr_array_header_t **collected_ignore_patterns,
+ apr_array_header_t **collected_inherited_ignore_patterns,
const apr_array_header_t *ignore_patterns,
svn_depth_t depth,
svn_boolean_t get_all,
@@ -1242,9 +1291,12 @@
* determined. For example, in 'svn status', plain unversioned nodes show
* as '? C', where ignored ones show as 'I C'. */
- if (ignore_patterns && ! *collected_ignore_patterns)
- SVN_ERR(collect_ignore_patterns(collected_ignore_patterns, wb->db,
- parent_abspath, ignore_patterns,
+ if ((ignore_patterns && ! *collected_ignore_patterns)
+ || (collected_inherited_ignore_patterns
+ && ! collected_inherited_ignore_patterns))
+ SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
+ collected_inherited_ignore_patterns,
+ wb->db, parent_abspath, ignore_patterns,
dir_has_props,
result_pool, scratch_pool));
@@ -1253,6 +1305,7 @@
dirent,
conflicted,
*collected_ignore_patterns,
+ *collected_inherited_ignore_patterns,
no_ignore,
status_func, status_baton,
scratch_pool));
@@ -1306,6 +1359,7 @@
apr_hash_t *dirents, *nodes, *conflicts, *all_children;
apr_array_header_t *sorted_children;
apr_array_header_t *collected_ignore_patterns = NULL;
+ apr_array_header_t *collected_inherited_ignore_patterns = NULL;
apr_pool_t *iterpool, *subpool = svn_pool_create(scratch_pool);
svn_error_t *err;
int i;
@@ -1436,6 +1490,7 @@
dir_has_props,
apr_hash_get(conflicts, key, klen) != NULL,
&collected_ignore_patterns,
+ &collected_inherited_ignore_patterns,
ignore_patterns,
depth,
get_all,
@@ -1485,6 +1540,7 @@
const char *dir_repos_uuid;
const struct svn_wc__db_info_t *dir_info;
apr_array_header_t *collected_ignore_patterns = NULL;
+ apr_array_header_t *collected_inherited_ignore_patterns = NULL;
const svn_io_dirent2_t *dirent_p;
const char *parent_abspath = svn_dirent_dirname(local_abspath,
scratch_pool);
@@ -1523,6 +1579,7 @@
(dir_info->had_props || dir_info->props_mod),
FALSE, /* unversioned_tree_conflicted */
&collected_ignore_patterns,
+ &collected_inherited_ignore_patterns,
ignore_patterns,
svn_depth_empty,
get_all,
@@ -2985,7 +3042,7 @@
apr_array_header_t *default_ignores;
SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
- return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
+ return svn_error_trace(collect_ignore_patterns(patterns, NULL, wc_ctx->db,
local_abspath,
default_ignores, TRUE,
result_pool, scratch_pool));
diff --git a/subversion/libsvn_wc/tree_conflicts.c b/subversion/libsvn_wc/tree_conflicts.c
index 23e0a37..7a14cff 100644
--- a/subversion/libsvn_wc/tree_conflicts.c
+++ b/subversion/libsvn_wc/tree_conflicts.c
@@ -78,7 +78,6 @@
{ "replaced", svn_wc_conflict_reason_replaced },
{ "unversioned", svn_wc_conflict_reason_unversioned },
{ "moved-away", svn_wc_conflict_reason_moved_away },
- { "moved-away-and-edited", svn_wc_conflict_reason_moved_away_and_edited },
{ "moved-here", svn_wc_conflict_reason_moved_here },
{ NULL }
};
@@ -183,11 +182,12 @@
skel->children->next->next->next->next));
kind = (svn_node_kind_t)n;
- *version_info = svn_wc_conflict_version_create(repos_root,
- repos_relpath,
- peg_rev,
- kind,
- result_pool);
+ *version_info = svn_wc_conflict_version_create2(repos_root,
+ NULL,
+ repos_relpath,
+ peg_rev,
+ kind,
+ result_pool);
return SVN_NO_ERROR;
}
@@ -473,7 +473,7 @@
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR(svn_wc__read_conflicts(&conflicts,
- wc_ctx->db, local_abspath,
+ wc_ctx->db, local_abspath, FALSE,
scratch_pool, scratch_pool));
if (!conflicts || conflicts->nelts == 0)
diff --git a/subversion/libsvn_wc/update_editor.c b/subversion/libsvn_wc/update_editor.c
index aa03b66..064515e 100644
--- a/subversion/libsvn_wc/update_editor.c
+++ b/subversion/libsvn_wc/update_editor.c
@@ -170,6 +170,12 @@
generated conflict files. */
const apr_array_header_t *ext_patterns;
+ /* Hash mapping const char * absolute working copy paths to depth-first
+ ordered arrays of svn_prop_inherited_item_t * structures representing
+ the properties inherited by the base node at that working copy path.
+ May be NULL. */
+ apr_hash_t *wcroot_iprops;
+
/* The revision we're targeting...or something like that. This
starts off as a pointer to the revision to which we are updating,
or SVN_INVALID_REVNUM, but by the end of the edit, should be
@@ -868,7 +874,7 @@
SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict));
if (is_complete)
- return SVN_NO_ERROR; /* Already competed */
+ return SVN_NO_ERROR; /* Already completed */
if (old_repos_relpath)
src_left_version = svn_wc_conflict_version_create2(eb->repos_root,
@@ -1404,7 +1410,18 @@
case svn_wc__db_status_deleted:
- reason = svn_wc_conflict_reason_deleted;
+ {
+ const char *moved_to_abspath;
+
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, &moved_to_abspath,
+ NULL, NULL, eb->db,
+ local_abspath,
+ scratch_pool, scratch_pool));
+ if (moved_to_abspath)
+ reason = svn_wc_conflict_reason_moved_away;
+ else
+ reason = svn_wc_conflict_reason_deleted;
+ }
break;
case svn_wc__db_status_incomplete:
@@ -1474,7 +1491,6 @@
if (reason == svn_wc_conflict_reason_edited
|| reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
- || reason == svn_wc_conflict_reason_moved_away_and_edited
|| reason == svn_wc_conflict_reason_replaced)
/* When the node existed before (it was locally deleted, replaced or
* edited), then 'update' cannot add it "again". So it can only send
@@ -1613,7 +1629,7 @@
if (is_root)
{
/* Just skip this node; a future update will handle it */
- remember_skipped_tree(eb, local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
do_notification(eb, local_abspath, svn_node_unknown,
svn_wc_notify_update_skip_obstruction, scratch_pool);
@@ -1663,7 +1679,7 @@
- /* Receive the remote removal of excluded/absent/not present node.
+ /* Receive the remote removal of excluded/server-excluded/not present node.
Do not notify, but perform the change even when the node is shadowed */
if (base_status == svn_wc__db_status_not_present
|| base_status == svn_wc__db_status_excluded
@@ -1730,7 +1746,6 @@
}
else if (reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
- || reason == svn_wc_conflict_reason_moved_away_and_edited
|| reason == svn_wc_conflict_reason_replaced)
{
/* The item does not exist locally because it was already shadowed.
@@ -1903,7 +1918,7 @@
NULL, NULL,
pool));
- remember_skipped_tree(eb, db->local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
@@ -1927,7 +1942,7 @@
file externals.
*/
- remember_skipped_tree(eb, db->local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
@@ -2185,7 +2200,7 @@
if (is_root)
{
/* Just skip this node; a future update will handle it */
- remember_skipped_tree(eb, db->local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
@@ -2264,7 +2279,6 @@
db->pool, db->pool));
SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
- || reason == svn_wc_conflict_reason_moved_away_and_edited
|| reason == svn_wc_conflict_reason_replaced);
/* Continue updating BASE */
@@ -2630,6 +2644,7 @@
else
{
apr_hash_t *props;
+ apr_array_header_t *iprops = NULL;
/* ### we know a base node already exists. it was created in
### open_directory or add_directory. let's just preserve the
@@ -2689,6 +2704,22 @@
scratch_pool);
}
+ /* Any inherited props to be set set for this base node? */
+ if (eb->wcroot_iprops)
+ {
+ iprops = apr_hash_get(eb->wcroot_iprops, db->local_abspath,
+ APR_HASH_KEY_STRING);
+
+ /* close_edit may also update iprops for switched nodes, catching
+ those for which close_directory is never called (e.g. a switch
+ with no changes). So as a minor optimization we remove any
+ iprops from the hash so as not to set them again in
+ close_edit. */
+ if (iprops)
+ apr_hash_set(eb->wcroot_iprops, db->local_abspath,
+ APR_HASH_KEY_STRING, NULL);
+ }
+
/* Update the BASE data for the directory and mark the directory
complete */
SVN_ERR(svn_wc__db_base_add_directory(
@@ -2707,7 +2738,7 @@
conflict_skel,
(! db->shadowed) && new_base_props != NULL,
new_actual_props,
- all_work_items,
+ iprops, all_work_items,
scratch_pool));
}
@@ -2962,7 +2993,7 @@
apr_hash_set(pb->not_present_files, apr_pstrdup(pb->pool, fb->name),
APR_HASH_KEY_STRING, (void*)1);
- remember_skipped_tree(eb, fb->local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
@@ -2987,7 +3018,7 @@
The reason we get here is that the adm crawler doesn't report
file externals.
*/
- remember_skipped_tree(eb, fb->local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
@@ -3232,7 +3263,7 @@
if (is_root)
{
/* Just skip this node; a future update will handle it */
- remember_skipped_tree(eb, fb->local_abspath, pool);
+ SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
@@ -3309,7 +3340,6 @@
fb->pool, fb->pool));
SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
- || reason == svn_wc_conflict_reason_moved_away_and_edited
|| reason == svn_wc_conflict_reason_replaced);
/* Continue updating BASE */
@@ -4384,6 +4414,7 @@
eb->repos_uuid,
*(eb->target_revision),
eb->skipped_trees,
+ eb->wcroot_iprops,
eb->pool));
if (*eb->target_basename != '\0')
@@ -4457,6 +4488,7 @@
svn_wc__db_t *db,
const char *anchor_abspath,
const char *target_basename,
+ apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
const char *switch_url,
svn_depth_t depth,
@@ -4522,6 +4554,7 @@
eb->db = db;
eb->target_basename = target_basename;
eb->anchor_abspath = anchor_abspath;
+ eb->wcroot_iprops = wcroot_iprops;
SVN_ERR(svn_wc__db_get_wcroot(&eb->wcroot_abspath, db, anchor_abspath,
edit_pool, scratch_pool));
@@ -4743,6 +4776,7 @@
svn_wc_context_t *wc_ctx,
const char *anchor_abspath,
const char *target_basename,
+ apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
svn_depth_t depth,
svn_boolean_t depth_is_sticky,
@@ -4766,7 +4800,7 @@
apr_pool_t *scratch_pool)
{
return make_editor(target_revision, wc_ctx->db, anchor_abspath,
- target_basename, use_commit_times,
+ target_basename, wcroot_iprops, use_commit_times,
NULL, depth, depth_is_sticky, allow_unver_obstructions,
adds_as_modification, server_performs_filtering,
clean_checkout,
@@ -4787,6 +4821,7 @@
const char *anchor_abspath,
const char *target_basename,
const char *switch_url,
+ apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
svn_depth_t depth,
svn_boolean_t depth_is_sticky,
@@ -4810,7 +4845,7 @@
SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, scratch_pool));
return make_editor(target_revision, wc_ctx->db, anchor_abspath,
- target_basename, use_commit_times,
+ target_basename, wcroot_iprops, use_commit_times,
switch_url,
depth, depth_is_sticky, allow_unver_obstructions,
FALSE /* adds_as_modification */,
@@ -5042,10 +5077,10 @@
}
svn_error_t *
-svn_wc_is_wc_root2(svn_boolean_t *wc_root,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool)
+svn_wc__internal_is_wc_root(svn_boolean_t *wc_root,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
{
svn_boolean_t is_root;
svn_boolean_t is_switched;
@@ -5054,7 +5089,7 @@
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
err = svn_wc__check_wc_root(&is_root, &kind, &is_switched,
- wc_ctx->db, local_abspath, scratch_pool);
+ db, local_abspath, scratch_pool);
if (err)
{
@@ -5065,11 +5100,21 @@
return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND, err, err->message);
}
- *wc_root = is_root || (kind == svn_kind_dir && is_switched);
+ *wc_root = is_root || is_switched;
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_wc_is_wc_root2(svn_boolean_t *wc_root,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__internal_is_wc_root(wc_root, wc_ctx->db,
+ local_abspath,
+ scratch_pool));
+}
svn_error_t*
svn_wc__strictly_is_wc_root(svn_boolean_t *wc_root,
diff --git a/subversion/libsvn_wc/upgrade.c b/subversion/libsvn_wc/upgrade.c
index fac032e..a30c762 100644
--- a/subversion/libsvn_wc/upgrade.c
+++ b/subversion/libsvn_wc/upgrade.c
@@ -1380,7 +1380,11 @@
apr_pool_t *scratch_pool)
{
svn_skel_t *conflict_data = NULL;
-
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath,
+ scratch_pool, scratch_pool));
+
if (conflict_old || conflict_new || conflict_wrk)
{
const char *old_abspath = NULL;
@@ -1390,19 +1394,16 @@
conflict_data = svn_wc__conflict_skel_create(result_pool);
if (conflict_old)
- SVN_ERR(svn_wc__db_from_relpath(&old_abspath, db, wri_abspath,
- conflict_old,
- scratch_pool, scratch_pool));
+ old_abspath = svn_dirent_join(wcroot_abspath, conflict_old,
+ scratch_pool);
if (conflict_new)
- SVN_ERR(svn_wc__db_from_relpath(&new_abspath, db, wri_abspath,
- conflict_new,
- scratch_pool, scratch_pool));
+ new_abspath = svn_dirent_join(wcroot_abspath, conflict_new,
+ scratch_pool);
if (conflict_wrk)
- SVN_ERR(svn_wc__db_from_relpath(&wrk_abspath, db, wri_abspath,
- conflict_wrk,
- scratch_pool, scratch_pool));
+ wrk_abspath = svn_dirent_join(wcroot_abspath, conflict_wrk,
+ scratch_pool);
SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflict_data,
db, wri_abspath,
@@ -1420,9 +1421,7 @@
if (!conflict_data)
conflict_data = svn_wc__conflict_skel_create(result_pool);
- SVN_ERR(svn_wc__db_from_relpath(&prej_abspath, db, wri_abspath,
- prej_file,
- scratch_pool, scratch_pool));
+ prej_abspath = svn_dirent_join(wcroot_abspath, prej_file, scratch_pool);
SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_data,
db, wri_abspath,
@@ -1445,9 +1444,8 @@
tc_skel = svn_skel__parse(tree_conflict_data, tree_conflict_len,
scratch_pool);
- SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath,
- local_relpath,
- scratch_pool, scratch_pool));
+ local_abspath = svn_dirent_join(wcroot_abspath, local_relpath,
+ scratch_pool);
SVN_ERR(svn_wc__deserialize_conflict(&tc, tc_skel,
svn_dirent_dirname(local_abspath,
@@ -1561,6 +1559,47 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+bump_to_31(void *baton,
+ svn_sqlite__db_t *sdb,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt, *stmt_mark_switch_roots;
+ svn_boolean_t have_row;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_array_header_t *empty_iprops = apr_array_make(
+ scratch_pool, 0, sizeof(svn_prop_inherited_item_t *));
+
+ /* Add the inherited_props column to NODES. */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31));
+
+ /* Set inherited_props to an empty array for the roots of all
+ switched subtrees in the WC. This allows subsequent updates
+ to recognize these roots as needing an iprops cache. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+ STMT_UPGRADE_31_SELECT_WCROOT_NODES));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt_mark_switch_roots, sdb,
+ STMT_UPDATE_IPROP));
+ while (have_row)
+ {
+ const char *switched_relpath = svn_sqlite__column_text(stmt, 1, NULL);
+ apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0);
+
+ SVN_ERR(svn_sqlite__bindf(stmt_mark_switch_roots, "is", wc_id,
+ switched_relpath));
+ SVN_ERR(svn_sqlite__bind_iprops(stmt_mark_switch_roots, 3,
+ empty_iprops, iterpool));
+ SVN_ERR(svn_sqlite__step_done(stmt_mark_switch_roots));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
struct upgrade_data_t {
svn_sqlite__db_t *sdb;
@@ -1835,13 +1874,16 @@
*result_format = 29;
/* FALLTHROUGH */
-#if SVN_WC__VERSION >= 30
case 29:
SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_30, &bb,
scratch_pool));
*result_format = 30;
+
+ case 30:
+ SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_31, &bb,
+ scratch_pool));
+ *result_format = 31;
/* FALLTHROUGH */
-#endif
/* ### future bumps go here. */
#if 0
case XXX-1:
@@ -1851,6 +1893,9 @@
*result_format = XXX;
/* FALLTHROUGH */
#endif
+ case SVN_WC__VERSION:
+ /* already upgraded */
+ *result_format = SVN_WC__VERSION;
}
#ifdef SVN_DEBUG
@@ -1978,7 +2023,7 @@
{
return svn_error_createf(
SVN_ERR_WC_INVALID_OP_ON_CWD, err,
- _("Can't upgrade '%s' as it is not a pre-1.7 working copy directory"),
+ _("Can't upgrade '%s' as it is not a working copy"),
svn_dirent_local_style(local_abspath, scratch_pool));
}
else if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
@@ -2027,7 +2072,7 @@
return svn_error_createf(
SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
- _("Can't upgrade '%s' as it is not a pre-1.7 working copy root,"
+ _("Can't upgrade '%s' as it is not a working copy root,"
" the root is '%s'"),
svn_dirent_local_style(local_abspath, scratch_pool),
svn_dirent_local_style(parent_abspath, scratch_pool));
@@ -2087,6 +2132,34 @@
apr_hash_t *entries;
const char *root_adm_abspath;
upgrade_working_copy_baton_t cb_baton;
+ svn_error_t *err;
+ int result_format;
+
+ /* Try upgrading a wc-ng-style working copy. */
+ SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, TRUE, FALSE,
+ scratch_pool, scratch_pool));
+
+ err = svn_wc__db_bump_format(&result_format, local_abspath, db,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) /* pre-1.7 WC */
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_wc__db_close(db));
+ }
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* Auto-upgrade worked! */
+ SVN_ERR(svn_wc__db_close(db));
+
+ SVN_ERR_ASSERT(result_format == SVN_WC__VERSION);
+
+ return SVN_NO_ERROR;
+ }
SVN_ERR(is_old_wcroot(local_abspath, scratch_pool));
@@ -2100,7 +2173,7 @@
upgrade. */
SVN_ERR(svn_wc__db_open(&db,
- NULL /* ### config */, FALSE, FALSE,
+ NULL /* ### config */, TRUE, FALSE,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc__read_entries_old(&entries, local_abspath,
diff --git a/subversion/libsvn_wc/util.c b/subversion/libsvn_wc/util.c
index 5fedbaf..d3d5dfe 100644
--- a/subversion/libsvn_wc/util.c
+++ b/subversion/libsvn_wc/util.c
@@ -469,7 +469,7 @@
/* (Currently a no-op, but just make sure it is ok) */
if (old_status->repos_node_status == svn_wc_status_modified
|| old_status->repos_node_status == svn_wc_status_conflicted)
- (*status)->text_status = old_status->repos_text_status;
+ (*status)->repos_text_status = old_status->repos_text_status;
if (old_status->node_status == svn_wc_status_added)
(*status)->prop_status = svn_wc_status_none; /* No separate info */
diff --git a/subversion/libsvn_wc/wc-metadata.sql b/subversion/libsvn_wc/wc-metadata.sql
index 033b2ef..33483d7 100644
--- a/subversion/libsvn_wc/wc-metadata.sql
+++ b/subversion/libsvn_wc/wc-metadata.sql
@@ -32,7 +32,7 @@
* the PRESENCE column in these tables has one of the following values
* (see also the C type #svn_wc__db_status_t):
* "normal"
- * "absent" -- server has declared it "absent" (ie. authz failure)
+ * "server-excluded" -- server has declared it excluded (ie. authz failure)
* "excluded" -- administratively excluded (ie. sparse WC)
* "not-present" -- node not present at this REV
* "incomplete" -- state hasn't been filled in
@@ -367,7 +367,7 @@
current 'op_depth'. This state is badly named, it should be
something like 'deleted'.
- absent: in the 'BASE' tree this is a node that is excluded by
+ server-excluded: in the 'BASE' tree this is a node that is excluded by
authz. The name of the node is known from the parent, but no
other information is available. Not valid in the 'WORKING'
tree as there is no way to commit such a node.
@@ -475,6 +475,10 @@
### anyway. */
file_external INTEGER,
+ /* serialized skel of this node's inherited properties. NULL if this
+ is not the BASE of a WC root node. */
+ inherited_props BLOB,
+
PRIMARY KEY (wc_id, local_relpath, op_depth)
);
@@ -575,6 +579,8 @@
def_local_relpath,
local_relpath);
+/* ------------------------------------------------------------------------- */
+
/* Format 20 introduces NODES and removes BASE_NODE and WORKING_NODE */
-- STMT_UPGRADE_TO_20
@@ -773,20 +779,22 @@
/* ------------------------------------------------------------------------- */
-/* Format 30 currently just contains some nice to haves that should be included
- with the next format bump */
+/* Format 30 creates a new NODES index for move information, and a new
+ PRISTINE index for the md5_checksum column. It also activates use of
+ skel-based conflict storage -- see notes/wc-ng/conflict-storage-2.0.
+ It also renames the "absent" presence to "server-excluded". */
-- STMT_UPGRADE_TO_30
CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED
ON NODES (wc_id, moved_to, op_depth);
CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum);
+UPDATE nodes SET presence = "server-excluded" WHERE presence = "absent";
+
/* Just to be sure clear out file external skels from pre 1.7.0 development
working copies that were never updated by 1.7.0+ style clients */
UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL;
-PRAGMA user_version = 30;
-
-- STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE
SELECT wc_id, local_relpath,
conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data
@@ -806,6 +814,38 @@
/* ------------------------------------------------------------------------- */
+/* Format 31 adds the inherited_props column to the NODES table. C code then
+ initializes the update/switch roots to make sure future updates fetch the
+ inherited properties */
+-- STMT_UPGRADE_TO_31
+ALTER TABLE NODES ADD COLUMN inherited_props BLOB;
+
+PRAGMA user_version = 31;
+
+-- STMT_UPGRADE_31_SELECT_WCROOT_NODES
+/* Select all base nodes which are the root of a WC, including
+ switched subtrees, but excluding those which map to the root
+ of the repos.
+
+ ### IPROPS: Is this query horribly inefficient? Quite likely,
+ ### but it only runs during an upgrade, so do we care? */
+SELECT l.wc_id, l.local_relpath FROM nodes as l
+LEFT OUTER JOIN nodes as r
+ON l.wc_id = r.wc_id
+ AND l.repos_id = r.repos_id
+ AND r.local_relpath = l.parent_relpath
+WHERE (l.local_relpath = '' AND l.repos_path != '')
+ OR (l.op_depth = 0
+ AND l.local_relpath != ''
+ AND l.repos_path != ltrim(r.repos_path
+ || '/'
+ || ltrim(substr(l.local_relpath,
+ length(l.parent_relpath) + 1),
+ '/'),
+ '/'))
+
+/* ------------------------------------------------------------------------- */
+
/* Format YYY introduces new handling for conflict information. */
-- format: YYY
@@ -818,9 +858,6 @@
number will be, however, so we're just marking it as 99 for now. */
-- format: 99
-/* TODO: Rename the "absent" presence value to "server-excluded". wc_db.c
- and this file have references to "absent" which still need to be changed
- to "server-excluded". */
/* TODO: Un-confuse *_revision column names in the EXTERNALS table to
"-r<operative> foo@<peg>", as suggested by the patch attached to
http://svn.haxx.se/dev/archive-2011-09/0478.shtml */
diff --git a/subversion/libsvn_wc/wc-queries.sql b/subversion/libsvn_wc/wc-queries.sql
index 19b5b92..aff5c6e 100644
--- a/subversion/libsvn_wc/wc-queries.sql
+++ b/subversion/libsvn_wc/wc-queries.sql
@@ -102,8 +102,7 @@
LIMIT 1
-- STMT_SELECT_ACTUAL_NODE
-SELECT changelist, properties, conflict_data,
-conflict_old, conflict_new, conflict_working, prop_reject, tree_conflict_data
+SELECT changelist, properties, conflict_data
FROM actual_node
WHERE wc_id = ?1 AND local_relpath = ?2
@@ -127,8 +126,7 @@
WHERE wc_id = ?1 AND parent_relpath = ?2
-- STMT_SELECT_ACTUAL_CHILDREN_INFO
-SELECT local_relpath, changelist, properties, conflict_data,
-conflict_old, conflict_new, conflict_working, prop_reject, tree_conflict_data
+SELECT local_relpath, changelist, properties, conflict_data
FROM actual_node
WHERE wc_id = ?1 AND parent_relpath = ?2
@@ -149,9 +147,10 @@
wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
revision, presence, depth, kind, changed_revision, changed_date,
changed_author, checksum, properties, translated_size, last_mod_time,
- dav_cache, symlink_target, file_external, moved_to, moved_here)
+ dav_cache, symlink_target, file_external, moved_to, moved_here,
+ inherited_props)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14,
- ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22)
+ ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23)
-- STMT_SELECT_BASE_PRESENT
SELECT local_relpath, kind FROM nodes n
@@ -246,7 +245,7 @@
SELECT local_relpath FROM nodes
WHERE wc_id = ?1 AND op_depth = ?3
AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
- AND presence == 'not-present'
+ AND presence = 'not-present'
-- STMT_COMMIT_DESCENDANT_TO_BASE
UPDATE NODES SET op_depth = 0, repos_id = ?4, repos_path = ?5, revision = ?6,
@@ -413,16 +412,11 @@
WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?5
-- STMT_INSERT_ACTUAL_CONFLICT
-INSERT INTO actual_node (
- wc_id, local_relpath, conflict_data,
- conflict_old, conflict_new, conflict_working, prop_reject,
- tree_conflict_data, parent_relpath)
-VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
+INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath)
+VALUES (?1, ?2, ?3, ?4)
-- STMT_UPDATE_ACTUAL_CONFLICT
-UPDATE actual_node SET conflict_data = ?3,
- conflict_old = ?4, conflict_new = ?5, conflict_working = ?6,
- prop_reject = ?7, tree_conflict_data = ?8
+UPDATE actual_node SET conflict_data = ?3
WHERE wc_id = ?1 AND local_relpath = ?2
-- STMT_UPDATE_ACTUAL_CHANGELISTS
@@ -593,10 +587,6 @@
WHERE wc_id = ?1 AND local_relpath = ?2
AND properties IS NULL
AND conflict_data IS NULL
- AND conflict_old IS NULL
- AND conflict_new IS NULL
- AND prop_reject IS NULL
- AND tree_conflict_data IS NULL
AND changelist IS NULL
AND text_mod IS NULL
AND older_checksum IS NULL
@@ -609,10 +599,6 @@
AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
AND properties IS NULL
AND conflict_data IS NULL
- AND conflict_old IS NULL
- AND conflict_new IS NULL
- AND prop_reject IS NULL
- AND tree_conflict_data IS NULL
AND changelist IS NULL
AND text_mod IS NULL
AND older_checksum IS NULL
@@ -683,10 +669,6 @@
SET properties = NULL,
text_mod = NULL,
conflict_data = NULL,
- conflict_old = NULL,
- conflict_new = NULL,
- conflict_working = NULL,
- prop_reject = NULL,
tree_conflict_data = NULL,
older_checksum = NULL,
left_checksum = NULL,
@@ -698,10 +680,6 @@
SET properties = NULL,
text_mod = NULL,
conflict_data = NULL,
- conflict_old = NULL,
- conflict_new = NULL,
- conflict_working = NULL,
- prop_reject = NULL,
tree_conflict_data = NULL,
older_checksum = NULL,
left_checksum = NULL,
@@ -771,38 +749,7 @@
SELECT local_relpath, conflict_data
FROM actual_node
WHERE wc_id = ?1 AND parent_relpath = ?2 AND
- NOT ((conflict_data IS NULL) AND (conflict_old IS NULL)
- AND (conflict_new IS NULL) AND (conflict_working IS NULL)
- AND (prop_reject IS NULL) AND (tree_conflict_data IS NULL))
-
--- STMT_SELECT_CONFLICT_MARKER_FILES1
-SELECT prop_reject
-FROM actual_node
-WHERE wc_id = ?1 AND local_relpath = ?2
- AND (prop_reject IS NOT NULL)
-
--- STMT_SELECT_CONFLICT_MARKER_FILES2
-SELECT prop_reject, conflict_old, conflict_new, conflict_working
-FROM actual_node
-WHERE wc_id = ?1 AND parent_relpath = ?2
- AND ((prop_reject IS NOT NULL) OR (conflict_old IS NOT NULL)
- OR (conflict_new IS NOT NULL) OR (conflict_working IS NOT NULL))
-
--- STMT_CLEAR_TEXT_CONFLICT
-UPDATE actual_node SET
- conflict_old = NULL,
- conflict_new = NULL,
- conflict_working = NULL
-WHERE wc_id = ?1 AND local_relpath = ?2
-
--- STMT_CLEAR_PROPS_CONFLICT
-UPDATE actual_node SET
- prop_reject = NULL
-WHERE wc_id = ?1 AND local_relpath = ?2
-
--- STMT_CLEAR_TREE_CONFLICT
-UPDATE actual_node SET tree_conflict_data = NULL
-WHERE wc_id = ?1 AND local_relpath = ?2
+ NOT (conflict_data IS NULL)
-- STMT_INSERT_WC_LOCK
INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels)
@@ -879,7 +826,7 @@
AND (local_relpath = ?2
OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
AND op_depth = ?3
- AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'absent')
+ AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded')
-- STMT_INSERT_WORKING_NODE_FROM_BASE_COPY
INSERT INTO nodes (
@@ -917,7 +864,7 @@
SELECT local_relpath FROM nodes
WHERE wc_id = ?1
AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
- AND op_depth = 0 AND presence = 'absent'
+ AND op_depth = 0 AND presence = 'server-excluded'
LIMIT 1
/* Select all excluded nodes. Not valid on the WC-root */
@@ -926,7 +873,7 @@
WHERE wc_id = ?1
AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
AND op_depth = 0
- AND (presence = 'absent' OR presence = 'excluded')
+ AND (presence = 'server-excluded' OR presence = 'excluded')
/* Creates a copy from one top level NODE to a different location */
-- STMT_INSERT_WORKING_NODE_COPY_FROM
@@ -1093,15 +1040,17 @@
WHERE (wc_id = ?1 AND local_relpath = ?2)
OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
+-- STMT_PRAGMA_LOCKING_MODE
+PRAGMA locking_mode = exclusive
+
/* ------------------------------------------------------------------------- */
/* these are used in entries.c */
-- STMT_INSERT_ACTUAL_NODE
INSERT OR REPLACE INTO actual_node (
- wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data,
- conflict_old, conflict_new, conflict_working, prop_reject, tree_conflict_data)
-VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
+ wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data)
+VALUES (?1, ?2, ?3, ?4, ?5, ?6)
/* ------------------------------------------------------------------------- */
@@ -1112,8 +1061,7 @@
WHERE wc_id = ?1 AND local_relpath = ?2
-- STMT_INSERT_ACTUAL_CONFLICT_DATA
-INSERT INTO actual_node (
- wc_id, local_relpath, conflict_data, parent_relpath)
+INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath)
VALUES (?1, ?2, ?3, ?4)
-- STMT_SELECT_ALL_FILES
@@ -1191,10 +1139,6 @@
local_relpath TEXT NOT NULL,
actual INTEGER NOT NULL, /* 1 if an actual row, 0 if a nodes row */
conflict_data BLOB,
- conflict_old TEXT,
- conflict_new TEXT,
- conflict_working TEXT,
- prop_reject TEXT,
notify INTEGER, /* 1 if an actual row had props or tree conflict */
op_depth INTEGER,
repos_id INTEGER,
@@ -1214,11 +1158,8 @@
BEFORE DELETE ON actual_node
BEGIN
INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data,
- conflict_old, conflict_new,
- conflict_working, prop_reject, notify)
+ notify)
SELECT OLD.local_relpath, 1, OLD.conflict_data,
- OLD.conflict_old, OLD.conflict_new, OLD.conflict_working,
- OLD.prop_reject,
CASE
WHEN OLD.properties IS NOT NULL
THEN 1
@@ -1234,11 +1175,8 @@
BEFORE UPDATE ON actual_node
BEGIN
INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data,
- conflict_old, conflict_new,
- conflict_working, prop_reject, notify)
+ notify)
SELECT OLD.local_relpath, 1, OLD.conflict_data,
- OLD.conflict_old, OLD.conflict_new, OLD.conflict_working,
- OLD.prop_reject,
CASE
WHEN OLD.properties IS NOT NULL
THEN 1
@@ -1256,8 +1194,7 @@
DROP TRIGGER trigger_revert_list_actual_update
-- STMT_SELECT_REVERT_LIST
-SELECT actual, notify, kind, op_depth, repos_id, conflict_data,
- conflict_old, conflict_new, conflict_working, prop_reject
+SELECT actual, notify, kind, op_depth, repos_id, conflict_data
FROM revert_list
WHERE local_relpath = ?1
ORDER BY actual DESC
@@ -1309,7 +1246,7 @@
AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s
WHERE s.wc_id = ?1
AND s.local_relpath = n.local_relpath)
- AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'absent')
+ AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded')
-- STMT_SELECT_DELETE_LIST
SELECT local_relpath FROM delete_list
@@ -1339,7 +1276,7 @@
AND (local_relpath = ?2
OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
AND op_depth = 0
- AND (presence IN ('absent', 'excluded')
+ AND (presence IN ('server-excluded', 'excluded')
OR depth NOT IN ('infinity', 'unknown'))
AND file_external IS NULL
LIMIT 1
@@ -1487,7 +1424,41 @@
-- STMT_SELECT_ALL_NODES
SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes
-WHERE wc_id == ?1
+WHERE wc_id = ?1
+
+/* ------------------------------------------------------------------------- */
+
+/* Queries for cached inherited properties. */
+
+/* Select the inherited properties of a single base node. */
+-- STMT_SELECT_IPROPS
+SELECT inherited_props FROM nodes
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0
+
+/* Update the inherited properties of a single base node. */
+-- STMT_UPDATE_IPROP
+UPDATE nodes
+SET inherited_props = ?3
+WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0)
+
+/* Select a single path if its base node has cached inherited properties. */
+-- STMT_SELECT_INODES
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1
+ AND local_relpath = ?2
+ AND op_depth = 0
+ AND (inherited_props not null)
+
+/* Select all paths whose base nodes at or below a given path, which
+ have cached inherited properties. */
+-- STMT_SELECT_INODES_RECURSIVE
+SELECT local_relpath FROM nodes
+WHERE wc_id = ?1
+ AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+ AND op_depth = 0
+ AND (inherited_props not null)
/* ------------------------------------------------------------------------- */
diff --git a/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h
index bb6c235..74d32c6 100644
--- a/subversion/libsvn_wc/wc.h
+++ b/subversion/libsvn_wc/wc.h
@@ -152,12 +152,15 @@
* == 1.7.x shipped with format 29
*
* The bump to 30 switched the conflict storage to a skel inside conflict_data.
- * Also clears some known invalid state.
+ * Also clears some known invalid state. Bumped in r1387742.
+ *
+ * The bump to 31 added the inherited_props column in the NODES table.
+ * Bumped in r1395109.
*
* Please document any further format changes here.
*/
-#define SVN_WC__VERSION 29
+#define SVN_WC__VERSION 31
/* Formats <= this have no concept of "revert text-base/props". */
@@ -185,9 +188,6 @@
/* A version < this has no work queue (see workqueue.h). */
#define SVN_WC__HAS_WORK_QUEUE 13
-/* The first version that uses conflict skels for all conflicts */
-#define SVN_WC__USES_CONFLICT_SKELS 30
-
/* Return true iff error E indicates an "is not a working copy" type
of error, either because something wasn't a working copy at all, or
because it's a working copy from a previous version (in need of
@@ -640,6 +640,31 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Internal version of svn_wc__node_get_repos_relpath() */
+svn_error_t *
+svn_wc__internal_get_repos_relpath(const char **repos_relpath,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc__get_iprops() */
+svn_error_t *
+svn_wc__internal_get_iprops(apr_array_header_t **inherited_props,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Internal version of svn_wc_is_wc_root2() */
+svn_error_t *
+svn_wc__internal_is_wc_root(svn_boolean_t *wc_root,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool);
+
+
/* Upgrade the wc sqlite database given in SDB for the wc located at
WCROOT_ABSPATH. It's current/starting format is given by START_FORMAT.
After the upgrade is complete (to as far as the automatic upgrade will
@@ -708,6 +733,8 @@
*
* Victim must be versioned or be part of a tree conflict.
*
+ * If CREATE_TEMPFILES is TRUE, create temporary files for property conflicts.
+ *
* Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in
* SCRATCH_POOL
*/
@@ -715,6 +742,7 @@
svn_wc__read_conflicts(const apr_array_header_t **conflicts,
svn_wc__db_t *db,
const char *local_abspath,
+ svn_boolean_t create_tempfiles,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c
index 2030ae7..11fde26 100644
--- a/subversion/libsvn_wc/wc_db.c
+++ b/subversion/libsvn_wc/wc_db.c
@@ -43,9 +43,6 @@
#include "entries.h"
#include "lock.h"
#include "conflicts.h"
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
-#include "tree_conflicts.h"
-#endif
#include "wc_db_private.h"
#include "workqueue.h"
@@ -113,25 +110,6 @@
#define SQLITE_PROPERTIES_AVAILABLE(stmt, i) \
(svn_sqlite__column_bytes(stmt, i) > 2)
-/* Calculates the depth of the relpath below "" */
-APR_INLINE static int
-relpath_depth(const char *relpath)
-{
- int n = 1;
- if (*relpath == '\0')
- return 0;
-
- do
- {
- if (*relpath == '/')
- n++;
- }
- while (*(++relpath));
-
- return n;
-}
-
-
int
svn_wc__db_op_depth_for_upgrade(const char *local_relpath)
{
@@ -177,6 +155,11 @@
svn_boolean_t update_actual_props;
const apr_hash_t *new_actual_props;
+ /* A depth-first ordered array of svn_prop_inherited_item_t *
+ structures representing the properties inherited by the base
+ node. */
+ apr_array_header_t *iprops;
+
/* maybe we should copy information from a previous record? */
svn_boolean_t keep_recorded_info;
@@ -250,6 +233,7 @@
/* for file and symlink externals */
const apr_hash_t *props;
+ apr_array_header_t *iprops;
svn_revnum_t changed_rev;
apr_time_t changed_date;
const char *changed_author;
@@ -294,9 +278,7 @@
of all the status values. */
static const svn_token_map_t presence_map[] = {
{ "normal", svn_wc__db_status_normal },
- /* ### "absent" is the former name of the "server-excluded" presence.
- * ### We should change it to "server-excluded" with a format bump. */
- { "absent", svn_wc__db_status_server_excluded },
+ { "server-excluded", svn_wc__db_status_server_excluded },
{ "excluded", svn_wc__db_status_excluded },
{ "not-present", svn_wc__db_status_not_present },
{ "incomplete", svn_wc__db_status_incomplete },
@@ -343,26 +325,6 @@
apr_pool_t *scratch_pool);
static svn_error_t *
-base_get_info(svn_wc__db_status_t *status,
- svn_kind_t *kind,
- svn_revnum_t *revision,
- const char **repos_relpath,
- apr_int64_t *repos_id,
- svn_revnum_t *changed_rev,
- apr_time_t *changed_date,
- const char **changed_author,
- svn_depth_t *depth,
- const svn_checksum_t **checksum,
- const char **target,
- svn_wc__db_lock_t **lock,
- svn_boolean_t *had_props,
- svn_boolean_t *update_root,
- svn_wc__db_wcroot_t *wcroot,
- const char *local_relpath,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
-
-static svn_error_t *
read_info(svn_wc__db_status_t *status,
svn_kind_t *kind,
svn_revnum_t *revision,
@@ -410,16 +372,6 @@
apr_pool_t *scratch_pool);
static svn_error_t *
-scan_deletion(const char **base_del_relpath,
- const char **moved_to_relpath,
- const char **work_del_relpath,
- const char **moved_to_op_root_relpath,
- svn_wc__db_wcroot_t *wcroot,
- const char *local_relpath,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
-
-static svn_error_t *
convert_to_working_status(svn_wc__db_status_t *working_status,
svn_wc__db_status_t status);
@@ -431,7 +383,7 @@
apr_pool_t *scratch_pool);
-
+
/* Return the absolute path, in local path style, of LOCAL_RELPATH
in WCROOT. */
static const char *
@@ -832,6 +784,10 @@
SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props,
scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, pibb->iprops,
+ scratch_pool));
+
if (pibb->dav_cache)
SVN_ERR(svn_sqlite__bind_properties(stmt, 18, pibb->dav_cache,
scratch_pool));
@@ -1549,13 +1505,14 @@
const char *root_node_repos_relpath,
svn_revnum_t root_node_revision,
svn_depth_t root_node_depth,
+ svn_boolean_t exclusive,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct init_db_baton idb;
SVN_ERR(svn_wc__db_util_open_db(sdb, dir_abspath, sdb_fname,
- svn_sqlite__mode_rwcreate,
+ svn_sqlite__mode_rwcreate, exclusive,
NULL /* my_statements */,
result_pool, scratch_pool));
@@ -1589,6 +1546,7 @@
apr_int64_t repos_id;
apr_int64_t wc_id;
svn_wc__db_wcroot_t *wcroot;
+ svn_boolean_t sqlite_exclusive = FALSE;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(repos_relpath != NULL);
@@ -1599,10 +1557,15 @@
/* ### REPOS_ROOT_URL and REPOS_UUID may be NULL. ... more doc: tbd */
+ SVN_ERR(svn_config_get_bool((svn_config_t *)db->config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE));
+
/* Create the SDB and insert the basic rows. */
SVN_ERR(create_db(&sdb, &repos_id, &wc_id, local_abspath, repos_root_url,
- repos_uuid, SDB_FILE,
- repos_relpath, initial_rev, depth,
+ repos_uuid, SDB_FILE,
+ repos_relpath, initial_rev, depth, sqlite_exclusive,
db->state_pool, scratch_pool));
/* Create the PDB. */
@@ -1729,6 +1692,7 @@
const svn_skel_t *conflict,
svn_boolean_t update_actual_props,
apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
const svn_skel_t *work_items,
apr_pool_t *scratch_pool)
{
@@ -1763,6 +1727,7 @@
ibb.repos_relpath = repos_relpath;
ibb.revision = revision;
+ ibb.iprops = new_iprops;
ibb.props = props;
ibb.changed_rev = changed_rev;
ibb.changed_date = changed_date;
@@ -2153,11 +2118,12 @@
svn_kind_t kind;
svn_boolean_t keep_working;
- SVN_ERR(base_get_info(&status, &kind, NULL, &repos_relpath, &repos_id,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL,
- wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
/* ### This function should be turned into a helper of this function,
as this is the only valid caller */
@@ -2374,27 +2340,25 @@
}
-/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of
- DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */
-static svn_error_t *
-base_get_info(svn_wc__db_status_t *status,
- svn_kind_t *kind,
- svn_revnum_t *revision,
- const char **repos_relpath,
- apr_int64_t *repos_id,
- svn_revnum_t *changed_rev,
- apr_time_t *changed_date,
- const char **changed_author,
- svn_depth_t *depth,
- const svn_checksum_t **checksum,
- const char **target,
- svn_wc__db_lock_t **lock,
- svn_boolean_t *had_props,
- svn_boolean_t *update_root,
- svn_wc__db_wcroot_t *wcroot,
- const char *local_relpath,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+svn_error_t *
+svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status,
+ svn_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ svn_boolean_t *update_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_sqlite__stmt_t *stmt;
svn_boolean_t have_row;
@@ -2534,11 +2498,14 @@
local_abspath, scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
- SVN_ERR(base_get_info(status, kind, revision, repos_relpath, &repos_id,
- changed_rev, changed_date, changed_author, depth,
- checksum, target, lock, had_props,
- update_root,
- wcroot, local_relpath, result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(status, kind, revision,
+ repos_relpath, &repos_id,
+ changed_rev, changed_date,
+ changed_author, depth,
+ checksum, target, lock,
+ had_props, update_root,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID);
SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid,
wcroot->sdb, repos_id, result_pool));
@@ -2998,9 +2965,11 @@
wcroot->sdb, scratch_pool));
/* And there must be no existing BASE node or it must be a file external */
- err = base_get_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, &update_root,
- wcroot, local_relpath, scratch_pool, scratch_pool);
+ err = svn_wc__db_base_get_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &update_root,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
@@ -3026,6 +2995,7 @@
ibb.revision = ieb->revision;
ibb.props = ieb->props;
+ ibb.iprops = ieb->iprops;
ibb.changed_rev = ieb->changed_rev;
ibb.changed_date = ieb->changed_date;
ibb.changed_author = ieb->changed_author;
@@ -3090,6 +3060,7 @@
svn_revnum_t revision,
const apr_hash_t *props,
+ apr_array_header_t *iprops,
svn_revnum_t changed_rev,
apr_time_t changed_date,
@@ -3144,6 +3115,7 @@
ieb.revision = revision;
ieb.props = props;
+ ieb.iprops = iprops;
ieb.changed_rev = changed_rev;
ieb.changed_date = changed_date;
@@ -3818,9 +3790,10 @@
{
const char *base_del_relpath, *work_del_relpath;
- SVN_ERR(scan_deletion(&base_del_relpath, NULL, &work_del_relpath,
- NULL, wcroot, local_relpath, scratch_pool,
- scratch_pool));
+ SVN_ERR(svn_wc__db_scan_deletion_internal(&base_del_relpath, NULL,
+ &work_del_relpath,
+ NULL, wcroot, local_relpath,
+ scratch_pool, scratch_pool));
if (work_del_relpath)
{
const char *op_root_relpath;
@@ -3842,12 +3815,14 @@
}
else if (base_del_relpath)
{
- SVN_ERR(base_get_info(NULL, NULL, copyfrom_rev, copyfrom_relpath,
- copyfrom_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_relpath,
- result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, copyfrom_rev,
+ copyfrom_relpath,
+ copyfrom_id, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool,
+ scratch_pool));
}
else
SVN_ERR_MALFUNCTION();
@@ -3877,6 +3852,7 @@
static svn_error_t *
op_depth_for_copy(int *op_depth,
int *np_op_depth,
+ int *parent_op_depth,
apr_int64_t copyfrom_repos_id,
const char *copyfrom_relpath,
svn_revnum_t copyfrom_revision,
@@ -3886,14 +3862,16 @@
/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH
- * instead of DB+LOCAL_ABSPATH. */
+ * instead of DB+LOCAL_ABSPATH. A non-zero MOVE_OP_DEPTH implies that the
+ * copy operation is part of a move, and indicates the op-depth of the
+ * move destination op-root. */
static svn_error_t *
db_op_copy(svn_wc__db_wcroot_t *src_wcroot,
const char *src_relpath,
svn_wc__db_wcroot_t *dst_wcroot,
const char *dst_relpath,
const svn_skel_t *work_items,
- svn_boolean_t is_move,
+ int move_op_depth,
apr_pool_t *scratch_pool)
{
const char *copyfrom_relpath;
@@ -3904,6 +3882,7 @@
apr_int64_t copyfrom_id;
int dst_op_depth;
int dst_np_op_depth;
+ int dst_parent_op_depth;
svn_kind_t kind;
const apr_array_header_t *children;
@@ -3911,8 +3890,9 @@
&status, &kind, &op_root, src_wcroot,
src_relpath, scratch_pool, scratch_pool));
- SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth, copyfrom_id,
- copyfrom_relpath, copyfrom_rev,
+ SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth,
+ &dst_parent_op_depth,
+ copyfrom_id, copyfrom_relpath, copyfrom_rev,
dst_wcroot, dst_relpath, scratch_pool));
SVN_ERR_ASSERT(kind == svn_kind_file || kind == svn_kind_dir);
@@ -4013,9 +3993,9 @@
dst_parent_relpath,
presence_map, dst_presence));
- if (is_move)
+ if (move_op_depth > 0)
{
- if (dst_op_depth == relpath_depth(dst_relpath))
+ if (relpath_depth(dst_relpath) == move_op_depth)
{
/* We're moving the root of the move operation.
*
@@ -4034,8 +4014,8 @@
/* We're moving a child along with the root of the move.
*
- * Set moved-here depending on dst_parent, propagating
- * the above decision to moved-along children.
+ * Set moved-here depending on dst_parent, propagating the
+ * above decision to moved-along children at the same op_depth.
* We can't use scan_addition() to detect moved-here because
* the delete-half of the move might not yet exist. */
SVN_ERR(svn_sqlite__get_statement(&info_stmt, dst_wcroot->sdb,
@@ -4044,9 +4024,29 @@
dst_parent_relpath));
SVN_ERR(svn_sqlite__step(&have_row, info_stmt));
SVN_ERR_ASSERT(have_row);
- if (svn_sqlite__column_boolean(info_stmt, 15))
- SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
- SVN_ERR(svn_sqlite__reset(info_stmt));
+ if (svn_sqlite__column_boolean(info_stmt, 15) &&
+ dst_op_depth == dst_parent_op_depth)
+ {
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+
+ /* If the child has been moved into the tree we're moving,
+ * keep its moved-here bit set. */
+ SVN_ERR(svn_sqlite__get_statement(&info_stmt,
+ dst_wcroot->sdb,
+ STMT_SELECT_NODE_INFO));
+ SVN_ERR(svn_sqlite__bindf(info_stmt, "is",
+ dst_wcroot->wc_id, src_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, info_stmt));
+ SVN_ERR_ASSERT(have_row);
+ if (svn_sqlite__column_boolean(info_stmt, 15))
+ SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1));
+ SVN_ERR(svn_sqlite__reset(info_stmt));
+ }
}
}
@@ -4118,7 +4118,9 @@
const char *dst_relpath;
const svn_skel_t *work_items;
+
svn_boolean_t is_move;
+ const char *dst_op_root_relpath;
};
/* Helper for svn_wc__db_op_copy.
@@ -4127,6 +4129,7 @@
op_copy_txn(void * baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
{
struct op_copy_baton *ocb = baton;
+ int move_op_depth;
if (sdb != ocb->dst_wcroot->sdb)
{
@@ -4140,9 +4143,14 @@
/* From this point we can assume a lock in the src and dst databases */
+ if (ocb->is_move)
+ move_op_depth = relpath_depth(ocb->dst_op_root_relpath);
+ else
+ move_op_depth = 0;
+
SVN_ERR(db_op_copy(ocb->src_wcroot, ocb->src_relpath,
ocb->dst_wcroot, ocb->dst_relpath,
- ocb->work_items, ocb->is_move, scratch_pool));
+ ocb->work_items, move_op_depth, scratch_pool));
return SVN_NO_ERROR;
}
@@ -4160,6 +4168,7 @@
SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_op_root_abspath));
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot,
&ocb.src_relpath, db,
@@ -4175,6 +4184,8 @@
ocb.work_items = work_items;
ocb.is_move = is_move;
+ ocb.dst_op_root_relpath = svn_dirent_skip_ancestor(ocb.dst_wcroot->abspath,
+ dst_op_root_abspath);
/* Call with the sdb in src_wcroot. It might call itself again to
also obtain a lock in dst_wcroot */
@@ -4185,7 +4196,10 @@
}
-/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer */
+/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer.
+ *
+ * A non-zero MOVE_OP_DEPTH implies that the copy operation is part of
+ * a move, and indicates the op-depth of the move destination op-root. */
static svn_error_t *
db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot,
const char *src_relpath,
@@ -4197,7 +4211,7 @@
apr_int64_t repos_id,
const char *repos_relpath,
svn_revnum_t revision,
- svn_boolean_t is_move,
+ int move_op_depth,
apr_pool_t *scratch_pool)
{
const apr_array_header_t *children;
@@ -4311,15 +4325,13 @@
SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH));
- /* Perhaps we should avoid setting moved_here to 0 and leave it
- null instead? */
SVN_ERR(svn_sqlite__bindf(stmt, "issdstdd",
src_wcroot->wc_id, src_relpath,
dst_relpath,
dst_op_depth,
svn_relpath_dirname(dst_relpath, iterpool),
presence_map, dst_presence,
- (is_move ? 1 : 0),
+ (dst_op_depth == move_op_depth), /* moved_here */
src_op_depth));
SVN_ERR(svn_sqlite__step_done(stmt));
@@ -4378,8 +4390,8 @@
src_wcroot, child_src_relpath, src_op_depth,
dst_wcroot, child_dst_relpath, dst_op_depth,
del_op_depth,
- repos_id, child_repos_relpath, revision, is_move,
- scratch_pool));
+ repos_id, child_repos_relpath, revision,
+ move_op_depth, scratch_pool));
}
svn_pool_destroy(iterpool);
@@ -4453,7 +4465,8 @@
ocb->src_wcroot, ocb->src_relpath, src_op_depth,
ocb->dst_wcroot, ocb->dst_relpath, dst_op_depth,
del_op_depth,
- repos_id, repos_relpath, revision, ocb->is_move,
+ repos_id, repos_relpath, revision,
+ (ocb->is_move ? dst_op_depth : 0),
scratch_pool));
return SVN_NO_ERROR;
@@ -4484,6 +4497,8 @@
VERIFY_USABLE_WCROOT(ocb.dst_wcroot);
ocb.is_move = is_move;
+ ocb.dst_op_root_relpath = NULL; /* not used by op_copy_shadowed_layer_txn */
+
ocb.work_items = NULL;
/* Call with the sdb in src_wcroot. It might call itself again to
@@ -4562,10 +4577,14 @@
If the parent node is not the parent of the to be copied node, then
*OP_DEPTH will be set to the proper op_depth for a new operation root.
+
+ Set *PARENT_OP_DEPTH to the op_depth of the parent.
+
*/
static svn_error_t *
op_depth_for_copy(int *op_depth,
int *np_op_depth,
+ int *parent_op_depth,
apr_int64_t copyfrom_repos_id,
const char *copyfrom_relpath,
svn_revnum_t copyfrom_revision,
@@ -4582,6 +4601,9 @@
*op_depth = relpath_depth(local_relpath);
*np_op_depth = -1;
+ svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool);
+ *parent_op_depth = relpath_depth(parent_relpath);
+
if (!copyfrom_relpath)
return SVN_NO_ERROR;
@@ -4600,18 +4622,17 @@
}
SVN_ERR(svn_sqlite__reset(stmt));
- svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool);
SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
STMT_SELECT_WORKING_NODE));
SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath));
SVN_ERR(svn_sqlite__step(&have_row, stmt));
if (have_row)
{
- int parent_op_depth = svn_sqlite__column_int(stmt, 0);
svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1,
presence_map);
- if (parent_op_depth < min_op_depth)
+ *parent_op_depth = svn_sqlite__column_int(stmt, 0);
+ if (*parent_op_depth < min_op_depth)
{
/* We want to create a copy; not overwrite the lower layers */
SVN_ERR(svn_sqlite__reset(stmt));
@@ -4624,7 +4645,7 @@
SVN_ERR_ASSERT(presence == svn_wc__db_status_normal);
if ((incomplete_op_depth < 0)
- || (incomplete_op_depth == parent_op_depth))
+ || (incomplete_op_depth == *parent_op_depth))
{
apr_int64_t parent_copyfrom_repos_id
= svn_sqlite__column_int64(stmt, 10);
@@ -4639,7 +4660,7 @@
&& !strcmp(copyfrom_relpath,
svn_relpath_join(parent_copyfrom_relpath, name,
scratch_pool)))
- *op_depth = parent_op_depth;
+ *op_depth = *parent_op_depth;
else if (incomplete_op_depth > 0)
*np_op_depth = incomplete_op_depth;
}
@@ -4672,6 +4693,7 @@
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
insert_working_baton_t iwb;
+ int parent_op_depth;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(props != NULL);
@@ -4694,7 +4716,6 @@
iwb.changed_rev = changed_rev;
iwb.changed_date = changed_date;
iwb.changed_author = changed_author;
- iwb.moved_here = is_move;
if (original_root_url != NULL)
{
@@ -4707,12 +4728,14 @@
/* ### Should we do this inside the transaction? */
SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
- iwb.original_repos_id,
+ &parent_op_depth, iwb.original_repos_id,
original_repos_relpath, original_revision,
wcroot, local_relpath, scratch_pool));
iwb.children = children;
iwb.depth = depth;
+ iwb.moved_here = is_move && (parent_op_depth == 0 ||
+ iwb.op_depth == parent_op_depth);
iwb.work_items = work_items;
iwb.conflict = conflict;
@@ -4747,6 +4770,7 @@
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
insert_working_baton_t iwb;
+ int parent_op_depth;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(props != NULL);
@@ -4771,7 +4795,6 @@
iwb.changed_rev = changed_rev;
iwb.changed_date = changed_date;
iwb.changed_author = changed_author;
- iwb.moved_here = is_move;
if (original_root_url != NULL)
{
@@ -4784,11 +4807,13 @@
/* ### Should we do this inside the transaction? */
SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
- iwb.original_repos_id,
+ &parent_op_depth, iwb.original_repos_id,
original_repos_relpath, original_revision,
wcroot, local_relpath, scratch_pool));
iwb.checksum = checksum;
+ iwb.moved_here = is_move && (parent_op_depth == 0 ||
+ iwb.op_depth == parent_op_depth);
if (update_actual_props)
{
@@ -4826,6 +4851,7 @@
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
insert_working_baton_t iwb;
+ int parent_op_depth;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(props != NULL);
@@ -4859,7 +4885,7 @@
/* ### Should we do this inside the transaction? */
SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth,
- iwb.original_repos_id,
+ &parent_op_depth, iwb.original_repos_id,
original_repos_relpath, original_revision,
wcroot, local_relpath, scratch_pool));
@@ -4884,16 +4910,21 @@
{
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
+ const char *dir_abspath;
+ const char *name;
insert_working_baton_t iwb;
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
- local_abspath, scratch_pool, scratch_pool));
+ dir_abspath, scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
blank_iwb(&iwb);
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
iwb.presence = svn_wc__db_status_normal;
iwb.kind = svn_kind_dir;
iwb.op_depth = relpath_depth(local_relpath);
@@ -4920,15 +4951,20 @@
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
insert_working_baton_t iwb;
+ const char *dir_abspath;
+ const char *name;
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
- local_abspath, scratch_pool, scratch_pool));
+ dir_abspath, scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
blank_iwb(&iwb);
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
iwb.presence = svn_wc__db_status_normal;
iwb.kind = svn_kind_file;
iwb.op_depth = relpath_depth(local_relpath);
@@ -4953,16 +4989,23 @@
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
insert_working_baton_t iwb;
+ const char *dir_abspath;
+ const char *name;
+ /* Resolve wcroot via parent directory to avoid obstruction handling */
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(target != NULL);
+ svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool);
+
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
- local_abspath, scratch_pool, scratch_pool));
+ dir_abspath, scratch_pool, scratch_pool));
+
VERIFY_USABLE_WCROOT(wcroot);
blank_iwb(&iwb);
+ local_relpath = svn_relpath_join(local_relpath, name, scratch_pool);
iwb.presence = svn_wc__db_status_normal;
iwb.kind = svn_kind_symlink;
iwb.op_depth = relpath_depth(local_relpath);
@@ -5557,11 +5600,6 @@
svn_sqlite__stmt_t *stmt;
svn_boolean_t got_row;
svn_boolean_t is_complete;
-#if defined(SVN_DEBUG) && (SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS)
- svn_boolean_t had_text_conflict;
- svn_boolean_t had_prop_conflict;
- svn_boolean_t had_tree_conflict;
-#endif
SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict_skel));
SVN_ERR_ASSERT(is_complete);
@@ -5570,23 +5608,6 @@
STMT_SELECT_ACTUAL_NODE));
SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
SVN_ERR(svn_sqlite__step(&got_row, stmt));
-
-#if defined(SVN_DEBUG) && (SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS)
- if (got_row)
- {
- had_text_conflict = (!svn_sqlite__column_is_null(stmt, 3)
- || !svn_sqlite__column_is_null(stmt, 4)
- || !svn_sqlite__column_is_null(stmt, 5));
- had_prop_conflict = !svn_sqlite__column_is_null(stmt, 6);
- had_tree_conflict = !svn_sqlite__column_is_null(stmt, 7);
- }
- else
- {
- had_text_conflict = FALSE;
- had_prop_conflict = FALSE;
- had_tree_conflict = FALSE;
- }
-#endif
SVN_ERR(svn_sqlite__reset(stmt));
if (got_row)
@@ -5601,212 +5622,17 @@
STMT_INSERT_ACTUAL_CONFLICT));
SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
if (*local_relpath != '\0')
- SVN_ERR(svn_sqlite__bind_text(stmt, 9,
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4,
svn_relpath_dirname(local_relpath,
scratch_pool)));
}
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- {
- /* Store conflict data in the old locations */
- svn_boolean_t text_conflict;
- svn_boolean_t prop_conflict;
- svn_boolean_t tree_conflict;
- const apr_array_header_t *locations;
- svn_wc_operation_t operation;
-
- svn_wc__db_t *db;
- const char *local_abspath;
-
- /* Ugly but temporary hack: obtain a DB for transforming paths.
- ### Can't use this for a write tranaction or we get a deadlock! */
-
- SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, FALSE,
- scratch_pool, scratch_pool));
-
- local_abspath = svn_dirent_join(wcroot->abspath, local_relpath,
- scratch_pool);
-
- SVN_ERR(svn_wc__conflict_read_info(&operation, &locations,
- &text_conflict, &prop_conflict,
- &tree_conflict,
- db, local_abspath, conflict_skel,
- scratch_pool, scratch_pool));
-
-#ifdef SVN_DEBUG
- /* This function should only ADD conflicts */
- SVN_ERR_ASSERT(text_conflict || !had_text_conflict);
- SVN_ERR_ASSERT(prop_conflict || !had_prop_conflict);
- SVN_ERR_ASSERT(tree_conflict || !had_tree_conflict);
-#endif
-
- if (text_conflict)
- {
- const char *mine_path;
- const char *their_old_path;
- const char *their_path;
-
- SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_path,
- &their_old_path,
- &their_path,
- db, local_abspath,
- conflict_skel,
- scratch_pool,
- scratch_pool));
-
- if (their_old_path)
- {
- their_old_path = svn_dirent_skip_ancestor(wcroot->abspath,
- their_old_path);
- SVN_ERR(svn_sqlite__bind_text(stmt, 4, their_old_path));
- }
-
- if (their_path)
- {
- their_path = svn_dirent_skip_ancestor(wcroot->abspath,
- their_path);
- SVN_ERR(svn_sqlite__bind_text(stmt, 5, their_path));
- }
-
- if (mine_path)
- {
- mine_path = svn_dirent_skip_ancestor(wcroot->abspath, mine_path);
- SVN_ERR(svn_sqlite__bind_text(stmt, 6, mine_path));
- }
- }
-
- if (prop_conflict)
- {
- const char *prej_path;
-
- SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_path, NULL,
- NULL, NULL, NULL,
- db, local_abspath,
- conflict_skel,
- scratch_pool, scratch_pool));
-
- if (prej_path)
- {
- prej_path = svn_dirent_skip_ancestor(wcroot->abspath, prej_path);
- SVN_ERR(svn_sqlite__bind_text(stmt, 7, prej_path));
- }
- }
-
- if (tree_conflict)
- {
- svn_wc_conflict_description2_t *desc;
- svn_wc_conflict_version_t *v1;
- svn_wc_conflict_version_t *v2;
- svn_node_kind_t tc_kind;
- svn_skel_t *skel;
- svn_wc_conflict_reason_t local_change;
- svn_wc_conflict_action_t incoming_change;
-
- SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change,
- &incoming_change,
- db, local_abspath,
- conflict_skel,
- scratch_pool, scratch_pool));
-
-
- v1 = (locations && locations->nelts > 0)
- ? APR_ARRAY_IDX(locations, 0, svn_wc_conflict_version_t *)
- : NULL;
-
- v2 = (locations && locations->nelts > 1)
- ? APR_ARRAY_IDX(locations, 1, svn_wc_conflict_version_t *)
- : NULL;
-
- if (incoming_change != svn_wc_conflict_action_delete
- && (operation == svn_wc_operation_update
- || operation == svn_wc_operation_switch))
- {
- svn_wc__db_status_t status;
- svn_revnum_t revision;
- const char *repos_relpath;
- apr_int64_t repos_id;
- svn_kind_t kind;
- svn_error_t *err;
-
- /* ### Theoretically we should just fetch the BASE information
- here. This code might need tweaks until all tree conflicts
- are installed in the proper state */
-
- SVN_ERR_ASSERT(v2 == NULL); /* Not set for update and switch */
-
- /* With an update or switch we have to fetch the second location
- for a tree conflict from WORKING. (For text or prop from BASE)
- */
- err = base_get_info(&status, &kind, &revision,
- &repos_relpath, &repos_id, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- wcroot, local_relpath,
- scratch_pool, scratch_pool);
-
- if (err)
- {
- if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
- return svn_error_trace(err);
-
- svn_error_clear(err);
- /* Ignore BASE */
-
- tc_kind = svn_node_file; /* Avoid assertion */
- }
- else if (repos_relpath)
- {
- const char *repos_root_url;
- const char *repos_uuid;
-
- SVN_ERR(fetch_repos_info(&repos_root_url, &repos_uuid,
- wcroot->sdb, repos_id,
- scratch_pool));
-
- v2 = svn_wc_conflict_version_create2(repos_root_url,
- repos_uuid,
- repos_relpath,
- revision,
- svn__node_kind_from_kind(kind),
- scratch_pool);
- tc_kind = svn__node_kind_from_kind(kind);
- }
- else
- tc_kind = svn_node_file; /* Avoid assertion */
- }
- else
- {
- if (v1)
- tc_kind = v1->node_kind;
- else if (v2)
- tc_kind = v2->node_kind;
- else
- tc_kind = svn_node_file; /* Avoid assertion */
- }
-
- desc = svn_wc_conflict_description_create_tree2(local_abspath,
- tc_kind,
- operation,
- v1, v2,
- scratch_pool);
- desc->reason = local_change;
- desc->action = incoming_change;
-
- SVN_ERR(svn_wc__serialize_conflict(&skel, desc,
- scratch_pool, scratch_pool));
-
- SVN_ERR(svn_sqlite__bind_text(stmt, 8,
- svn_skel__unparse(skel, scratch_pool)->data));
- }
- SVN_ERR(svn_wc__db_close(db));
- }
-#else
- /* And in the new location */
{
svn_stringbuf_t *sb = svn_skel__unparse(conflict_skel, scratch_pool);
SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len));
}
-#endif
+
SVN_ERR(svn_sqlite__update(NULL, stmt));
return SVN_NO_ERROR;
@@ -5847,9 +5673,7 @@
svn_boolean_t resolved_props;
svn_boolean_t resolved_tree;
const svn_skel_t *work_items;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
svn_wc__db_t *db;
-#endif
};
/* Helper for svn_wc__db_op_mark_resolved */
@@ -5863,14 +5687,10 @@
svn_sqlite__stmt_t *stmt;
svn_boolean_t have_row;
int total_affected_rows = 0;
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- int affected_rows;
-#else
svn_boolean_t resolved_all;
apr_size_t conflict_len;
const void *conflict_data;
svn_skel_t *conflicts;
-#endif
/* Check if we have a conflict in ACTUAL */
SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
@@ -5901,33 +5721,6 @@
scratch_pool));
}
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- SVN_ERR(svn_sqlite__reset(stmt));
- if (rb->resolved_text)
- {
- SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
- STMT_CLEAR_TEXT_CONFLICT));
- SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
- SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
- total_affected_rows += affected_rows;
- }
- if (rb->resolved_props)
- {
- SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
- STMT_CLEAR_PROPS_CONFLICT));
- SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
- SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
- total_affected_rows += affected_rows;
- }
- if (rb->resolved_tree)
- {
- SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
- STMT_CLEAR_TREE_CONFLICT));
- SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
- SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
- total_affected_rows += affected_rows;
- }
-#else
conflict_data = svn_sqlite__column_blob(stmt, 2, &conflict_len,
scratch_pool);
conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool);
@@ -5952,7 +5745,6 @@
}
SVN_ERR(svn_sqlite__update(&total_affected_rows, stmt));
-#endif
/* Now, remove the actual node if it doesn't have any more useful
information. We only need to do this if we've remove data ourselves. */
@@ -5992,9 +5784,7 @@
rb.resolved_text = resolved_text;
rb.resolved_tree = resolved_tree;
rb.work_items = work_items;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
rb.db = db;
-#endif
SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, db_op_mark_resolved,
&rb, scratch_pool));
@@ -6319,9 +6109,7 @@
svn_boolean_t *copied_here;
svn_kind_t *kind;
apr_pool_t *result_pool;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
svn_wc__db_t *db;
-#endif
};
static svn_error_t *
@@ -6350,24 +6138,6 @@
if (is_actual)
{
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- int i;
-
- for (i = 6; i <= 9; i++)
- {
- const char *relpath = svn_sqlite__column_text(stmt, i, NULL);
-
- if (! relpath)
- continue;
-
- if (!b->marker_paths)
- b->marker_paths = apr_array_make(b->result_pool, 4,
- sizeof(const char*));
-
- APR_ARRAY_PUSH(b->marker_paths, const char *)
- = svn_dirent_join(wcroot->abspath, relpath, b->result_pool);
- }
-#else
apr_size_t conflict_len;
const void *conflict_data;
@@ -6375,17 +6145,19 @@
scratch_pool);
if (conflict_data)
{
+ const apr_array_header_t *marker_paths;
svn_skel_t *conflicts = svn_skel__parse(conflict_data,
conflict_len,
scratch_pool);
- SVN_ERR(svn_wc__conflict_read_markers(&b->marker_paths,
+ SVN_ERR(svn_wc__conflict_read_markers(&marker_paths,
b->db, wcroot->abspath,
conflicts,
b->result_pool,
scratch_pool));
+ /* De-const-ify. */
+ b->marker_paths = (apr_array_header_t *)marker_paths;
}
-#endif
if (!svn_sqlite__column_is_null(stmt, 1)) /* notify */
*(b->reverted) = TRUE;
@@ -6436,9 +6208,7 @@
b.copied_here = copied_here;
b.kind = kind;
b.result_pool = result_pool;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
b.db = db;
-#endif
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
db, local_abspath, scratch_pool, scratch_pool));
@@ -6631,11 +6401,12 @@
/* Need info for not_present node? */
if (SVN_IS_VALID_REVNUM(rnb->not_present_rev))
- SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
if (rnb->destroy_wc
&& (!rnb->destroy_changes || *local_relpath == '\0'))
@@ -6698,7 +6469,7 @@
|| child_kind != svn_kind_file)
{
/* Not interested in keeping changes */
- modified_p = FALSE;
+ modified_p = FALSE;
}
else if (child_kind == svn_kind_file
&& dirent->kind == svn_node_file
@@ -7935,15 +7706,7 @@
if (have_act)
{
*conflicted =
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- !svn_sqlite__column_is_null(stmt_act, 3) || /* old */
- !svn_sqlite__column_is_null(stmt_act, 4) || /* new */
- !svn_sqlite__column_is_null(stmt_act, 5) || /* working */
- !svn_sqlite__column_is_null(stmt_act, 6) || /* prop_reject */
- !svn_sqlite__column_is_null(stmt_act, 7); /*tree_conflict_data*/
-#else
!svn_sqlite__column_is_null(stmt_act, 2); /* conflict_data */
-#endif
}
else
*conflicted = FALSE;
@@ -7998,11 +7761,7 @@
{
/* A row in ACTUAL_NODE should never exist without a corresponding
node in BASE_NODE and/or WORKING_NODE unless it flags a tree conflict. */
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- if (svn_sqlite__column_is_null(stmt_act, 7)) /* tree_conflict_data */
-#else
if (svn_sqlite__column_is_null(stmt_act, 2)) /* conflict_data */
-#endif
err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
_("Corrupt data for '%s'"),
path_for_error_message(wcroot, local_relpath,
@@ -8449,15 +8208,7 @@
}
#endif
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- child->conflicted = !svn_sqlite__column_is_null(stmt, 4) || /* old */
- !svn_sqlite__column_is_null(stmt, 5) || /* new */
- !svn_sqlite__column_is_null(stmt, 6) || /* work */
- !svn_sqlite__column_is_null(stmt, 7) || /* prop */
- !svn_sqlite__column_is_null(stmt, 8); /* tree */
-#else
child->conflicted = !svn_sqlite__column_is_null(stmt, 3); /* conflict */
-#endif
if (child->conflicted)
apr_hash_set(conflicts, apr_pstrdup(result_pool, name),
@@ -8820,17 +8571,25 @@
const char *base_del_relpath;
const char *work_del_relpath;
- SVN_ERR(scan_deletion(&base_del_relpath, NULL, &work_del_relpath,
- NULL, wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_scan_deletion_internal(&base_del_relpath, NULL,
+ &work_del_relpath,
+ NULL, wcroot,
+ local_relpath,
+ scratch_pool,
+ scratch_pool));
if (base_del_relpath)
{
- SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath,
- &repos_id, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, base_del_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath,
+ &repos_id,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot,
+ base_del_relpath,
+ scratch_pool,
+ scratch_pool));
repos_relpath = svn_relpath_join(
repos_relpath,
@@ -9308,6 +9067,175 @@
return svn_error_trace(svn_sqlite__reset(stmt));
}
+svn_error_t *
+svn_wc__db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ const char *repos_root_url;
+ svn_revnum_t revision;
+ int op_depth;
+ const char *repos_relpath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_read_info(NULL, NULL,
+ &revision, &repos_relpath, &repos_root_url,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, db, local_abspath, result_pool,
+ scratch_pool));
+
+ if (repos_relpath && repos_relpath[0] == '\0')
+ {
+ /* LOCAL_ABSPATH reflects the root of the repository, so there is
+ no parents to inherit from. */
+ *iprops = apr_array_make(result_pool, 0,
+ sizeof(svn_prop_inherited_item_t *));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, local_abspath,
+ scratch_pool,
+ scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_IPROPS));
+ SVN_ERR(op_depth_of(&op_depth, wcroot, local_relpath));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ {
+ /* No cached iprops. */
+ *iprops = NULL;
+ }
+ else
+ {
+ SVN_ERR(svn_sqlite__column_iprops(iprops, stmt, 0, result_pool,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Recursive body of svn_wc__db_get_children_with_cached_iprops. */
+static svn_error_t *
+get_children_with_cached_iprops(apr_hash_t *iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+ local_abspath, scratch_pool,
+ scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+ if (depth == svn_depth_empty
+ || depth == svn_depth_files
+ || depth == svn_depth_immediates)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_INODES));
+ }
+ else /* Default to svn_depth_infinity. */
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_SELECT_INODES_RECURSIVE));
+ }
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ while (have_row)
+ {
+ const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0,
+ NULL);
+ const char *abspath_with_cache = svn_dirent_join(wcroot->abspath,
+ relpath_with_cache,
+ result_pool);
+ apr_hash_set(iprop_paths, abspath_with_cache, APR_HASH_KEY_STRING,
+ abspath_with_cache);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (depth == svn_depth_files || depth == svn_depth_immediates)
+ {
+ const apr_array_header_t *rel_children;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_children_of_working_node(&rel_children,
+ db, local_abspath,
+ scratch_pool,
+ scratch_pool));
+ for (i = 0; i < rel_children->nelts; i++)
+ {
+ const char *child_abspath;
+
+ svn_pool_clear(iterpool);
+ child_abspath = svn_dirent_join(
+ local_abspath, APR_ARRAY_IDX(rel_children, i, const char *),
+ iterpool);
+
+ if (depth == svn_depth_files)
+ {
+ svn_kind_t child_kind;
+
+ SVN_ERR(svn_wc__db_read_kind(&child_kind, db, child_abspath,
+ FALSE, FALSE, iterpool));
+ if (child_kind != svn_kind_file)
+ continue;
+ }
+
+ SVN_ERR(get_children_with_cached_iprops(iprop_paths,
+ svn_depth_empty,
+ child_abspath, db,
+ result_pool,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *iprop_paths = apr_hash_make(result_pool);
+ SVN_ERR(get_children_with_cached_iprops(*iprop_paths, depth,
+ local_abspath, db, result_pool,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_wc__db_read_children_of_working_node(const apr_array_header_t **children,
@@ -9633,9 +9561,12 @@
if (status == svn_wc__db_status_deleted)
{
const char *work_del_relpath;
- SVN_ERR(scan_deletion(NULL, NULL, &work_del_relpath, NULL,
- wcroot, local_dir_relpath,
- scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_scan_deletion_internal(NULL, NULL,
+ &work_del_relpath, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool,
+ scratch_pool));
if (work_del_relpath)
{
/* Deleted within a copy/move */
@@ -9655,11 +9586,12 @@
scratch_pool, scratch_pool));
}
else
- SVN_ERR(base_get_info(NULL, NULL, NULL, NULL, &rb.old_repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_dir_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL,
+ &rb.old_repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_dir_relpath,
+ scratch_pool, scratch_pool));
}
SVN_ERR(fetch_repos_info(NULL, &rb.repos_uuid,
@@ -9718,11 +9650,12 @@
svn_relpath_split(&local_parent_relpath, &name, local_relpath, scratch_pool);
/* The REPOS_ID will be the same (### until we support mixed-repos) */
- SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_parent_relpath, repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_parent_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_parent_relpath, repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_parent_relpath,
+ scratch_pool, scratch_pool));
*repos_relpath = svn_relpath_join(repos_parent_relpath, name, result_pool);
@@ -10152,19 +10085,22 @@
#endif
}
-/* Sets a base nodes revision and/or repository relative path. If
- LOCAL_ABSPATH's rev (REV) is valid, set is revision and if SET_REPOS_RELPATH
- is TRUE set its repository relative path to REPOS_RELPATH (and make sure its
- REPOS_ID is still valid).
+/* Sets a base nodes revision, repository relative path, and/or inherited
+ propertis. If LOCAL_ABSPATH's rev (REV) is valid, set its revision. If
+ SET_REPOS_RELPATH is TRUE set its repository relative path to REPOS_RELPATH
+ (and make sure its REPOS_ID is still valid). If IPROPS is not NULL set its
+ inherited properties to IPROPS, if IPROPS is NULL then clear any the iprops
+ cache for the base node.
*/
static svn_error_t *
-db_op_set_rev_and_repos_relpath(svn_wc__db_wcroot_t *wcroot,
- const char *local_relpath,
- svn_revnum_t rev,
- svn_boolean_t set_repos_relpath,
- const char *repos_relpath,
- apr_int64_t repos_id,
- apr_pool_t *scratch_pool)
+db_op_set_rev_repos_relpath_iprops(svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_array_header_t *iprops,
+ svn_revnum_t rev,
+ svn_boolean_t set_repos_relpath,
+ const char *repos_relpath,
+ apr_int64_t repos_id,
+ apr_pool_t *scratch_pool)
{
svn_sqlite__stmt_t *stmt;
@@ -10196,6 +10132,15 @@
SVN_ERR(svn_sqlite__step_done(stmt));
}
+ /* Set or clear iprops. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+ STMT_UPDATE_IPROP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is",
+ wcroot->wc_id,
+ local_relpath));
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 3, iprops, scratch_pool));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
return SVN_NO_ERROR;
}
@@ -10203,7 +10148,13 @@
*
* Tweak the information for LOCAL_RELPATH in WCROOT. If NEW_REPOS_RELPATH is
* non-NULL update the entry to the new url specified by NEW_REPOS_RELPATH,
- * NEW_REPOS_ID.. If NEW_REV is valid, make this the node's working revision.
+ * NEW_REPOS_ID. If NEW_REV is valid, make this the node's working revision.
+ *
+ * If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute
+ * working copy paths to depth-first ordered arrays of
+ * svn_prop_inherited_item_t * structures. If the absolute path equivalent
+ * of LOCAL_RELPATH exists in WCROOT_IPROPS, then set the hashed value as the
+ * node's inherited properties.
*
* Unless S_ROOT is TRUE the tweaks might cause the node for LOCAL_ABSPATH to
* be removed from the WC; if IS_ROOT is TRUE this will not happen.
@@ -10216,6 +10167,7 @@
svn_revnum_t new_rev,
svn_depth_t depth,
apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
svn_boolean_t is_root,
svn_boolean_t skip_when_dir,
svn_wc__db_t *db,
@@ -10232,16 +10184,18 @@
svn_boolean_t set_repos_relpath = FALSE;
svn_boolean_t update_root;
svn_depth_t depth_below_here = depth;
+ apr_array_header_t *iprops = NULL;
/* Skip an excluded path and its descendants. */
if (apr_hash_get(exclude_relpaths, local_relpath, APR_HASH_KEY_STRING))
return SVN_NO_ERROR;
- SVN_ERR(base_get_info(&status, &db_kind, &revision, &repos_relpath,
- &repos_id, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, &update_root,
- wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(&status, &db_kind, &revision,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &update_root,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
/* Skip file externals */
if (update_root
@@ -10277,14 +10231,23 @@
if (new_repos_relpath != NULL && strcmp(repos_relpath, new_repos_relpath))
set_repos_relpath = TRUE;
- if (set_repos_relpath
+ if (wcroot_iprops)
+ iprops = apr_hash_get(wcroot_iprops,
+ svn_dirent_join(wcroot->abspath, local_relpath,
+ scratch_pool),
+ APR_HASH_KEY_STRING);
+
+ if (iprops
+ || set_repos_relpath
|| (SVN_IS_VALID_REVNUM(new_rev) && new_rev != revision))
- SVN_ERR(db_op_set_rev_and_repos_relpath(wcroot, local_relpath,
- new_rev,
- set_repos_relpath,
- new_repos_relpath,
- new_repos_id,
- scratch_pool));
+ {
+ SVN_ERR(db_op_set_rev_repos_relpath_iprops(wcroot, local_relpath,
+ iprops, new_rev,
+ set_repos_relpath,
+ new_repos_relpath,
+ new_repos_id,
+ scratch_pool));
+ }
/* Early out */
if (depth <= svn_depth_empty
@@ -10324,7 +10287,8 @@
SVN_ERR(bump_node_revision(wcroot, child_local_relpath, new_repos_id,
child_repos_relpath, new_rev,
depth_below_here,
- exclude_relpaths, FALSE /* is_root */,
+ exclude_relpaths, wcroot_iprops,
+ FALSE /* is_root */,
(depth < svn_depth_immediates), db,
iterpool));
}
@@ -10344,6 +10308,7 @@
const char *new_repos_uuid;
svn_revnum_t new_revision;
apr_hash_t *exclude_relpaths;
+ apr_hash_t *wcroot_iprops;
svn_wc__db_t *db;
};
@@ -10359,9 +10324,11 @@
svn_error_t *err;
apr_int64_t new_repos_id = INVALID_REPOS_ID;
- err = base_get_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- wcroot, local_relpath, scratch_pool, scratch_pool);
+ err = svn_wc__db_base_get_info_internal(&status, &kind, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool);
if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
svn_error_clear(err);
@@ -10390,6 +10357,7 @@
SVN_ERR(bump_node_revision(wcroot, local_relpath, new_repos_id,
brb->new_repos_relpath, brb->new_revision,
brb->depth, brb->exclude_relpaths,
+ brb->wcroot_iprops,
TRUE /* is_root */, FALSE, brb->db,
scratch_pool));
@@ -10405,6 +10373,7 @@
const char *new_repos_uuid,
svn_revnum_t new_revision,
apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
apr_pool_t *scratch_pool)
{
const char *local_relpath;
@@ -10428,6 +10397,7 @@
brb.new_repos_uuid = new_repos_uuid;
brb.new_revision = new_revision;
brb.exclude_relpaths = exclude_relpaths;
+ brb.wcroot_iprops = wcroot_iprops;
brb.db = db;
SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath,
@@ -10447,11 +10417,12 @@
const char *repos_relpath;
apr_int64_t repos_id;
- SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_LOCK));
SVN_ERR(svn_sqlite__bindf(stmt, "iss",
@@ -10508,11 +10479,12 @@
apr_int64_t repos_id;
svn_sqlite__stmt_t *stmt;
- SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
STMT_DELETE_LOCK));
@@ -10567,10 +10539,12 @@
local_abspath, scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
- SVN_ERR(base_get_info(NULL, NULL, NULL, repos_relpath, &repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, local_relpath, result_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ result_pool, scratch_pool));
SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb,
repos_id, result_pool));
@@ -10921,11 +10895,12 @@
{
const char *base_relpath;
- SVN_ERR(base_get_info(NULL, NULL, NULL, &base_relpath, sab->repos_id,
- NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL,
- wcroot, op_root_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &base_relpath, sab->repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, op_root_relpath,
+ scratch_pool, scratch_pool));
if (sab->repos_relpath)
*sab->repos_relpath = svn_relpath_join(base_relpath, build_relpath,
@@ -11465,17 +11440,15 @@
}
-/* Like svn_wc__db_scan_deletion(), but with WCROOT+LOCAL_RELPATH instead of
- DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */
-static svn_error_t *
-scan_deletion(const char **base_del_relpath,
- const char **moved_to_relpath,
- const char **work_del_relpath,
- const char **moved_to_op_root_relpath,
- svn_wc__db_wcroot_t *wcroot,
- const char *local_relpath,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+svn_error_t *
+svn_wc__db_scan_deletion_internal(const char **base_del_relpath,
+ const char **moved_to_relpath,
+ const char **work_del_relpath,
+ const char **moved_to_op_root_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
struct scan_deletion_baton_t sd_baton;
@@ -11512,9 +11485,12 @@
local_abspath, scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
- SVN_ERR(scan_deletion(&base_del_relpath, &moved_to_relpath,
- &work_del_relpath, &moved_to_op_root_relpath, wcroot,
- local_relpath, scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_scan_deletion_internal(&base_del_relpath,
+ &moved_to_relpath,
+ &work_del_relpath,
+ &moved_to_op_root_relpath, wcroot,
+ local_relpath, scratch_pool,
+ scratch_pool));
if (base_del_abspath)
{
@@ -11562,10 +11538,13 @@
apr_pool_t *scratch_pool)
{
svn_wc__db_wcroot_t *wcroot;
+
+ /* Upgrade is inherently exclusive so specify exclusive locking. */
SVN_ERR(create_db(sdb, repos_id, wc_id, dir_abspath,
repos_root_url, repos_uuid,
SDB_FILE,
NULL, SVN_INVALID_REVNUM, svn_depth_unknown,
+ TRUE /* exclusive */,
wc_db->state_pool, scratch_pool));
/* Create the PDB. */
@@ -11998,7 +11977,7 @@
directory to not be a working copy. */
if (err)
{
- if (err && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
return svn_error_trace(err);
svn_error_clear(err);
@@ -12224,9 +12203,7 @@
{
apr_pool_t *result_pool;
apr_hash_t *marker_files;
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
svn_wc__db_t *db;
-#endif
};
/* Locked implementation for svn_wc__db_get_conflict_marker_files */
@@ -12240,66 +12217,6 @@
apr_hash_t *marker_files = mfb->marker_files;
apr_pool_t *result_pool = mfb->result_pool;
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- /* Look for property conflicts on the directory in ACTUAL.
- (A directory can't have text conflicts) */
- SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
- STMT_SELECT_CONFLICT_MARKER_FILES1));
- SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
- SVN_ERR(svn_sqlite__step(&have_row, stmt));
-
- if (have_row)
- {
- const char *marker_relpath;
-
- marker_relpath = svn_sqlite__column_text(stmt, 0, NULL);
-
- if (marker_relpath)
- {
- const char *marker_abspath;
-
- marker_abspath = svn_dirent_join(wcroot->abspath, marker_relpath,
- result_pool);
-
- apr_hash_set(marker_files, marker_abspath, APR_HASH_KEY_STRING,
- "");
- }
- }
- SVN_ERR(svn_sqlite__reset(stmt));
-
- /* Look for property and text conflicts on the direct children of
- LOCAL_RELPATH, as both directories and files can have conflict
- files in their parent directory */
- SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
- STMT_SELECT_CONFLICT_MARKER_FILES2));
- SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
- SVN_ERR(svn_sqlite__step(&have_row, stmt));
-
- while (have_row)
- {
- int i;
-
- for (i = 0; i < 4; i++)
- {
- const char *marker_relpath;
-
- marker_relpath = svn_sqlite__column_text(stmt, i, NULL);
-
- if (marker_relpath)
- {
- const char *marker_abspath;
-
- marker_abspath = svn_dirent_join(wcroot->abspath, marker_relpath,
- result_pool);
-
- apr_hash_set(marker_files, marker_abspath, APR_HASH_KEY_STRING,
- "");
- }
- }
-
- SVN_ERR(svn_sqlite__step(&have_row, stmt));
- }
-#else
SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
STMT_SELECT_ACTUAL_NODE));
SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
@@ -12361,7 +12278,6 @@
SVN_ERR(svn_sqlite__step(&have_row, stmt));
}
-#endif
return svn_error_trace(svn_sqlite__reset(stmt));
}
@@ -12384,9 +12300,7 @@
mfb.result_pool = result_pool;
mfb.marker_files = apr_hash_make(result_pool);
-#if SVN_WC__VERSION >= SVN_WC__USES_CONFLICT_SKELS
mfb.db = db;
-#endif
SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath, get_conflict_marker_files,
&mfb, scratch_pool));
@@ -12409,14 +12323,28 @@
{
svn_wc__db_wcroot_t *wcroot;
const char *local_relpath;
- svn_sqlite__stmt_t *stmt;
- svn_boolean_t have_row;
/* The parent should be a working copy directory. */
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
local_abspath, scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
+ return svn_error_trace(svn_wc__db_read_conflict_internal(conflict, wcroot,
+ local_relpath,
+ result_pool,
+ scratch_pool));
+}
+
+svn_error_t *
+svn_wc__db_read_conflict_internal(svn_skel_t **conflict,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
/* Check if we have a conflict in ACTUAL */
SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
STMT_SELECT_ACTUAL_NODE));
@@ -12462,38 +12390,6 @@
scratch_pool));
}
-#if SVN_WC__VERSION < SVN_WC__USES_CONFLICT_SKELS
- {
- const char *conflict_old = svn_sqlite__column_text(stmt, 3, NULL);
- const char *conflict_new = svn_sqlite__column_text(stmt, 4, NULL);
- const char *conflict_wrk = svn_sqlite__column_text(stmt, 5, NULL);
- const char *conflict_prj = svn_sqlite__column_text(stmt, 6, NULL);
- apr_size_t tree_conflict_len;
- const char *tree_conflict_data;
- svn_skel_t *conflict_skel = NULL;
- svn_error_t *err;
-
- tree_conflict_data = svn_sqlite__column_blob(stmt, 7, &tree_conflict_len,
- NULL);
-
- err = svn_wc__upgrade_conflict_skel_from_raw(&conflict_skel,
- db, local_abspath,
- local_relpath,
- conflict_old,
- conflict_wrk,
- conflict_new,
- conflict_prj,
- tree_conflict_data,
- tree_conflict_len,
- result_pool,
- scratch_pool);
-
- *conflict = conflict_skel;
-
- return svn_error_trace(
- svn_error_compose_create(err, svn_sqlite__reset(stmt)));
- }
-#else
{
apr_size_t cfl_len;
const void *cfl_data;
@@ -12508,7 +12404,6 @@
return svn_error_trace(svn_sqlite__reset(stmt));
}
-#endif
}
@@ -13136,9 +13031,11 @@
svn_sqlite__stmt_t *stmt;
svn_wc__db_status_t base_status;
- SVN_ERR(base_get_info(&base_status, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- wcroot, local_relpath, scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(&base_status, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
if (base_status == svn_wc__db_status_normal)
return SVN_NO_ERROR;
@@ -13611,10 +13508,12 @@
*is_switched = FALSE;
- SVN_ERR(base_get_info(NULL, NULL, NULL, &repos_relpath, &repos_id, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- wcroot, local_relpath,
- scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL,
+ &repos_relpath, &repos_id,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, local_relpath,
+ scratch_pool, scratch_pool));
/* First do the cheap check where we only need info on the origin itself */
if (trail_url != NULL)
@@ -14182,3 +14081,36 @@
SVN_ERR(verify_wcroot(wcroot, scratch_pool));
return SVN_NO_ERROR;
}
+
+svn_error_t *
+svn_wc__db_bump_format(int *result_format,
+ const char *wcroot_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool)
+{
+
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, wcroot_abspath,
+ scratch_pool, scratch_pool));
+
+ /* This function is indirectly called from the upgrade code, so we
+ can't verify the wcroot here. Just check that it is not NULL */
+ SVN_ERR_ASSERT(wcroot != NULL);
+
+ /* Reject attempts to upgrade subdirectories of a working copy. */
+ if (strcmp(wcroot_abspath, wcroot->abspath) != 0)
+ return svn_error_createf(
+ SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
+ _("Can't upgrade '%s' as it is not a working copy root,"
+ " the root is '%s'"),
+ svn_dirent_local_style(wcroot_abspath, scratch_pool),
+ svn_dirent_local_style(wcroot->abspath, scratch_pool));
+
+ SVN_ERR(svn_wc__upgrade_sdb(result_format, wcroot->abspath,
+ wcroot->sdb, wcroot->format,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h
index a191a5e..077e53d 100644
--- a/subversion/libsvn_wc/wc_db.h
+++ b/subversion/libsvn_wc/wc_db.h
@@ -255,7 +255,7 @@
*/
svn_error_t *
svn_wc__db_open(svn_wc__db_t **db,
- const svn_config_t *config,
+ svn_config_t *config,
svn_boolean_t auto_upgrade,
svn_boolean_t enforce_empty_wq,
apr_pool_t *result_pool,
@@ -370,7 +370,7 @@
In the BASE tree, each node corresponds to a particular node-rev in the
repository. It can be a mixed-revision tree. Each node holds either a
copy of the node-rev as it exists in the repository (if presence =
- 'normal'), or a place-holder (if presence = 'absent' or 'excluded' or
+ 'normal'), or a place-holder (if presence = 'server-excluded' or 'excluded' or
'not-present').
@{
@@ -409,6 +409,10 @@
when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in
ACTUAL, to mark the properties unmodified.
+ If NEW_IPROPS is not NULL, then it is a depth-first ordered array of
+ svn_prop_inherited_item_t * structures that is set as the base node's
+ inherited_properties.
+
Any work items that are necessary as part of this node construction may
be passed in WORK_ITEMS.
@@ -432,6 +436,7 @@
const svn_skel_t *conflict,
svn_boolean_t update_actual_props,
apr_hash_t *new_actual_props,
+ apr_array_header_t *new_iprops,
const svn_skel_t *work_items,
apr_pool_t *scratch_pool);
@@ -1036,6 +1041,7 @@
svn_revnum_t revision,
const apr_hash_t *props,
+ apr_array_header_t *iprops,
svn_revnum_t changed_rev,
apr_time_t changed_date,
@@ -1356,7 +1362,7 @@
apr_pool_t *scratch_pool);
-/* ### do we need svn_wc__db_op_copy_absent() ?? */
+/* ### do we need svn_wc__db_op_copy_server_excluded() ?? */
/* ### add a new versioned directory. a list of children is NOT passed
@@ -1923,7 +1929,7 @@
svn_boolean_t incomplete; /* TRUE if a working node is incomplete */
const char *moved_to_abspath; /* Only on op-roots. See svn_wc_status3_t. */
- svn_boolean_t moved_here; /* On both op-roots and children. */
+ svn_boolean_t moved_here; /* Only on op-roots. */
svn_boolean_t file_external;
};
@@ -2084,6 +2090,39 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Read a BASE node's inherited property information.
+
+ Set *IPROPS to to a depth-first ordered array of
+ svn_prop_inherited_item_t * structures representing the cached
+ inherited properties for the BASE node at LOCAL_ABSPATH.
+
+ If no cached properties are found, then set *IPROPS to NULL.
+ If LOCAL_ABSPATH represents the root of the repository, then set
+ *IPROPS to an empty array.
+
+ Allocate *IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary
+ allocations. */
+svn_error_t *
+svn_wc__db_read_cached_iprops(apr_array_header_t **iprops,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Find BASE nodes with cached inherited properties.
+
+ Set *IPROPS_PATHS to a hash mapping const char * absolute working copy
+ paths to the same for each path in the working copy at or below
+ LOCAL_ABSPATH, limited by DEPTH, that has cached inherited properties
+ for the BASE node of the path. Allocate *IPROP_PATHS in RESULT_POOL.
+ Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *
+svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths,
+ svn_depth_t depth,
+ const char *local_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/** Obtain a mapping of const char * local_abspaths to const svn_string_t*
* property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH
@@ -2396,6 +2435,12 @@
EXCLUDE_RELPATHS is a hash containing const char *local_relpath. Nodes
for pathnames contained in EXCLUDE_RELPATHS are not touched by this
function. These pathnames should be paths relative to the wcroot.
+
+ If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute
+ working copy paths to depth-first ordered arrays of
+ svn_prop_inherited_item_t * structures. If LOCAL_ABSPATH exists in
+ WCROOT_IPROPS, then set the hashed value as the node's inherited
+ properties.
*/
svn_error_t *
svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db,
@@ -2406,6 +2451,7 @@
const char *new_repos_uuid,
svn_revnum_t new_revision,
apr_hash_t *exclude_relpaths,
+ apr_hash_t *wcroot_iprops,
apr_pool_t *scratch_pool);
@@ -2747,6 +2793,23 @@
const char *repos_root_url,
apr_pool_t *scratch_pool);
+/* Upgrade the metadata concerning the WC at WCROOT_ABSPATH, in DB,
+ * to the SVN_WC__VERSION format.
+ *
+ * This function is used for upgrading wc-ng working copies to a newer
+ * wc-ng format. If a pre-1.7 working copy is found, this function
+ * returns SVN_ERR_WC_UPGRADE_REQUIRED.
+ *
+ * Upgrading subdirectories of a working copy is not supported.
+ * If WCROOT_ABSPATH is not a working copy root SVN_ERR_WC_INVALID_OP_ON_CWD
+ * is returned.
+ */
+svn_error_t *
+svn_wc__db_bump_format(int *result_format,
+ const char *wcroot_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *scratch_pool);
+
/* @} */
@@ -3136,6 +3199,21 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Update a moved-away tree conflict victim at VICTIM_ABSPATH with changes
+ * brought in by the update operation which flagged the tree conflict.
+ * Set *WORK_ITEMS to a list of work items, allocated in RESULT_POOL, that
+ * need to run as part of marking the conflict resolved. */
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_skel_t **work_items,
+ const char *victim_abspath,
+ svn_wc__db_t *db,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* @} */
diff --git a/subversion/libsvn_wc/wc_db_private.h b/subversion/libsvn_wc/wc_db_private.h
index db76e5e..6f9e64e 100644
--- a/subversion/libsvn_wc/wc_db_private.h
+++ b/subversion/libsvn_wc/wc_db_private.h
@@ -36,7 +36,7 @@
struct svn_wc__db_t {
/* We need the config whenever we run into a new WC directory, in order
to figure out where we should look for the corresponding datastore. */
- const svn_config_t *config;
+ svn_config_t *config;
/* Should we attempt to automatically upgrade the database when it is
opened, and found to be not-current? */
@@ -159,6 +159,24 @@
#define VERIFY_USABLE_WCROOT(wcroot) SVN_ERR_ASSERT( \
(wcroot) != NULL && (wcroot)->format == SVN_WC__VERSION)
+/* Calculates the depth of the relpath below "" */
+APR_INLINE static int
+relpath_depth(const char *relpath)
+{
+ int n = 1;
+ if (*relpath == '\0')
+ return 0;
+
+ do
+ {
+ if (*relpath == '/')
+ n++;
+ }
+ while (*(++relpath));
+
+ return n;
+}
+
/* */
svn_error_t *
@@ -181,6 +199,7 @@
const char *dir_abspath,
const char *sdb_fname,
svn_sqlite__mode_t smode,
+ svn_boolean_t exclusive,
const char *const *my_statements,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
@@ -218,6 +237,50 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Like svn_wc__db_scan_deletion(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */
+svn_error_t *
+svn_wc__db_scan_deletion_internal(const char **base_del_relpath,
+ const char **moved_to_relpath,
+ const char **work_del_relpath,
+ const char **moved_to_op_root_relpath,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */
+svn_error_t *
+svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status,
+ svn_kind_t *kind,
+ svn_revnum_t *revision,
+ const char **repos_relpath,
+ apr_int64_t *repos_id,
+ svn_revnum_t *changed_rev,
+ apr_time_t *changed_date,
+ const char **changed_author,
+ svn_depth_t *depth,
+ const svn_checksum_t **checksum,
+ const char **target,
+ svn_wc__db_lock_t **lock,
+ svn_boolean_t *had_props,
+ svn_boolean_t *update_root,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Like svn_wc__db_read_conflict(), but with WCROOT+LOCAL_RELPATH instead of
+ DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */
+svn_error_t *
+svn_wc__db_read_conflict_internal(svn_skel_t **conflict,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *local_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/* Transaction handling */
diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c
new file mode 100644
index 0000000..2056935
--- /dev/null
+++ b/subversion/libsvn_wc/wc_db_update_move.c
@@ -0,0 +1,739 @@
+/*
+ * wc_db_update_move.c : updating moves during tree-conflict resolution
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* This editor is used during resolution of tree conflicts.
+ *
+ * An operation such as update can produce incoming changes for a
+ * locally moved-away subtree, causing a tree-conflict to be flagged.
+ * This editor transfers these changes from the moved-away part of the
+ * working copy to the corresponding moved-here part of the working copy.
+ *
+ * Both the driver and receiver components of the editor are implemented
+ * in this file.
+ */
+
+#define SVN_WC__I_AM_WC_DB
+
+#include "svn_checksum.h"
+#include "svn_dirent_uri.h"
+#include "svn_editor.h"
+#include "svn_error.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_wc_private.h"
+
+#include "wc.h"
+#include "wc_db_private.h"
+#include "conflicts.h"
+#include "workqueue.h"
+
+/*
+ * Receiver code.
+ */
+
+struct tc_editor_baton {
+ const char *src_relpath;
+ const char *dst_relpath;
+ svn_wc__db_t *db;
+ svn_wc__db_wcroot_t *wcroot;
+ svn_skel_t **work_items;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+tc_editor_add_directory(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_add_file(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_add_symlink(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_add_absent(void *baton,
+ const char *relpath,
+ svn_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_alter_directory(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_alter_file(void *baton,
+ const char *dst_relpath,
+ svn_revnum_t expected_moved_here_revision,
+ apr_hash_t *props,
+ const svn_checksum_t *moved_away_checksum,
+ svn_stream_t *post_update_contents,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ const svn_checksum_t *moved_here_checksum;
+ const char *original_repos_relpath;
+ svn_revnum_t original_revision;
+ svn_kind_t kind;
+
+ /* Get kind, revision, and checksum of the moved-here node. */
+ /*
+ * ### Currently doesn't work right if the moved-away node has been replaced.
+ * ### Need to read info from the move op-root's op-depth, not WORKING, to
+ * ### properly update shadowed nodes within multi-layer move destinations.
+ */
+ SVN_ERR(svn_wc__db_read_info_internal(NULL, &kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &moved_here_checksum,
+ NULL, &original_repos_relpath, NULL,
+ &original_revision, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, b->wcroot, dst_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR_ASSERT(original_revision == expected_moved_here_revision);
+
+ /* ### check original revision against moved-here op-root revision? */
+ if (kind != svn_kind_file)
+ return SVN_NO_ERROR;
+
+ /* ### what if checksum kind differs?*/
+ if (!svn_checksum_match(moved_away_checksum, moved_here_checksum))
+ {
+ const char *moved_to_abspath = svn_dirent_join(b->wcroot->abspath,
+ dst_relpath,
+ scratch_pool);
+ const char *pre_update_pristine_abspath;
+ const char *post_update_pristine_abspath;
+ svn_skel_t *conflict_skel;
+ enum svn_wc_merge_outcome_t merge_outcome;
+ svn_wc_notify_state_t content_state;
+ svn_wc_notify_t *notify;
+
+ /*
+ * Run a 3-way merge to update the file, using the pre-update
+ * pristine text as the merge base, the post-update pristine
+ * text as the merge-left version, and the current content of the
+ * moved-here working file as the merge-right version.
+ */
+ SVN_ERR(svn_wc__db_pristine_get_path(&pre_update_pristine_abspath,
+ b->db, moved_to_abspath,
+ moved_here_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_get_path(&post_update_pristine_abspath,
+ b->db, moved_to_abspath,
+ moved_away_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__internal_merge(b->work_items, &conflict_skel,
+ &merge_outcome, b->db,
+ pre_update_pristine_abspath,
+ post_update_pristine_abspath,
+ moved_to_abspath,
+ moved_to_abspath,
+ NULL, NULL, NULL, /* diff labels */
+ NULL, /* actual props */
+ FALSE, /* dry-run */
+ NULL, /* diff3-cmd */
+ NULL, /* merge options */
+ NULL, /* prop_diff */
+ NULL, NULL, /* cancel_func + baton */
+ b->result_pool, scratch_pool));
+
+ if (merge_outcome == svn_wc_merge_conflict)
+ {
+ svn_skel_t *work_item;
+ svn_wc_conflict_version_t *original_version;
+
+ if (conflict_skel)
+ {
+ original_version = svn_wc_conflict_version_dup(b->old_version,
+ scratch_pool);
+ original_version->path_in_repos = original_repos_relpath;
+ original_version->node_kind = svn_node_file;
+ SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel,
+ original_version,
+ scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item, b->db,
+ moved_to_abspath,
+ conflict_skel,
+ scratch_pool,
+ scratch_pool));
+ *b->work_items = svn_wc__wq_merge(*b->work_items, work_item,
+ b->result_pool);
+ }
+ content_state = svn_wc_notify_state_conflicted;
+ }
+ else
+ {
+ svn_boolean_t is_locally_modified;
+
+ SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
+ b->db, moved_to_abspath,
+ FALSE /* exact_comparison */,
+ scratch_pool));
+ if (is_locally_modified)
+ content_state = svn_wc_notify_state_merged;
+ else
+ content_state = svn_wc_notify_state_changed;
+ }
+
+ notify = svn_wc_create_notify(moved_to_abspath,
+ svn_wc_notify_update_update,
+ scratch_pool);
+ notify->kind = svn_node_file;
+ notify->content_state = content_state;
+ notify->prop_state = svn_wc_notify_state_unknown; /* ### TODO */
+ notify->old_revision = b->old_version->peg_rev;
+ notify->revision = b->new_version->peg_rev;
+ b->notify_func(b->notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_alter_symlink(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_delete(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_copy(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_move(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_rotate(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
+}
+
+static svn_error_t *
+tc_editor_complete(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct tc_editor_baton *b = baton;
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(svn_dirent_join(b->wcroot->abspath,
+ b->dst_relpath,
+ scratch_pool),
+ svn_wc_notify_update_completed,
+ scratch_pool);
+ notify->kind = svn_node_none;
+ notify->content_state = svn_wc_notify_state_inapplicable;
+ notify->prop_state = svn_wc_notify_state_inapplicable;
+ notify->revision = b->new_version->peg_rev;
+ b->notify_func(b->notify_baton, notify, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+tc_editor_abort(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static const svn_editor_cb_many_t editor_ops = {
+ tc_editor_add_directory,
+ tc_editor_add_file,
+ tc_editor_add_symlink,
+ tc_editor_add_absent,
+ tc_editor_alter_directory,
+ tc_editor_alter_file,
+ tc_editor_alter_symlink,
+ tc_editor_delete,
+ tc_editor_copy,
+ tc_editor_move,
+ tc_editor_rotate,
+ tc_editor_complete,
+ tc_editor_abort
+};
+
+
+/*
+ * Driver code.
+ */
+
+static svn_error_t *
+get_tc_info(svn_wc_operation_t *operation,
+ svn_wc_conflict_reason_t *local_change,
+ svn_wc_conflict_action_t *incoming_change,
+ svn_wc_conflict_version_t **old_version,
+ svn_wc_conflict_version_t **new_version,
+ const char *src_abspath,
+ svn_wc__db_t *db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *locations;
+ svn_boolean_t tree_conflicted;
+ svn_skel_t *conflict_skel;
+ svn_kind_t kind;
+
+ /* ### Check for mixed-rev src or dst? */
+
+ /* Check for tree conflict on src. */
+ SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db,
+ src_abspath,
+ scratch_pool, scratch_pool));
+ if (!conflict_skel)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not in conflict"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(operation, &locations,
+ NULL, NULL, &tree_conflicted,
+ db, src_abspath,
+ conflict_skel, result_pool,
+ scratch_pool));
+ if (!tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("'%s' is not a tree-conflict victim"),
+ svn_dirent_local_style(src_abspath,
+ scratch_pool));
+ if (locations)
+ {
+ *old_version = APR_ARRAY_IDX(locations, 0,
+ svn_wc_conflict_version_t *);
+ if (locations->nelts > 1)
+ *new_version = APR_ARRAY_IDX(locations, 1,
+ svn_wc_conflict_version_t *);
+ else
+ {
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *repos_relpath;
+ svn_revnum_t revision;
+ svn_node_kind_t node_kind;
+
+ /* Construct b->new_version from BASE info. */
+ SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &revision,
+ &repos_relpath, &repos_root_url,
+ &repos_uuid, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ db, src_abspath, result_pool,
+ scratch_pool));
+ node_kind = svn__node_kind_from_kind(kind);
+ *new_version = svn_wc_conflict_version_create2(repos_root_url,
+ repos_uuid,
+ repos_relpath,
+ revision,
+ node_kind,
+ scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_wc__conflict_read_tree_conflict(local_change,
+ incoming_change,
+ db, src_abspath,
+ conflict_skel, scratch_pool,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_moved_away_file(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ const char *move_dst_op_root_relpath,
+ svn_revnum_t move_dst_op_root_revision,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ svn_kind_t kind;
+ svn_stream_t *post_update_contents;
+ const svn_checksum_t *moved_away_checksum;
+
+ /* Read post-update contents from the updated moved-away file and tell
+ * the editor to merge them into the moved-here file. */
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, &kind, NULL, NULL, NULL, NULL,
+ &moved_away_checksum, NULL, NULL, db,
+ svn_dirent_join(wcroot->abspath,
+ src_relpath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_read(&post_update_contents, NULL, db,
+ wcroot->abspath, moved_away_checksum,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_alter_file(tc_editor, dst_relpath,
+ move_dst_op_root_revision,
+ NULL, /* ### TODO props */
+ moved_away_checksum,
+ post_update_contents));
+ SVN_ERR(svn_stream_close(post_update_contents));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_moved_away_dir(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ const char *move_dst_op_root_relpath,
+ svn_revnum_t move_dst_op_root_revision,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ /* ### notify */
+
+ /* ### update prop content if changed */
+
+ /* ### update list of children if changed */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+update_moved_away_subtree(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ const char *move_dst_op_root_relpath,
+ svn_revnum_t move_dst_op_root_revision,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(update_moved_away_dir(tc_editor, src_relpath, dst_relpath,
+ move_dst_op_root_relpath,
+ move_dst_op_root_revision,
+ db, wcroot, scratch_pool));
+
+ SVN_ERR(svn_wc__db_base_get_children(&children, db,
+ svn_dirent_join(wcroot->abspath,
+ src_relpath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_src_relpath;
+ svn_kind_t child_kind;
+ const char *child_dst_op_root_relpath;
+ const char *child_dst_relpath;
+
+ svn_pool_clear(iterpool);
+
+ child_src_relpath = svn_relpath_join(src_relpath,
+ APR_ARRAY_IDX(children, i,
+ const char *),
+ iterpool);
+
+ /* Is this child part of our move operation? */
+ /* ### need to handle children which were newly added or removed
+ * ### during the update */
+ SVN_ERR(svn_wc__db_scan_deletion_internal(NULL, &child_dst_relpath, NULL,
+ &child_dst_op_root_relpath,
+ wcroot, child_src_relpath,
+ iterpool, iterpool));
+ if (child_dst_op_root_relpath == NULL ||
+ strcmp(child_dst_op_root_relpath, move_dst_op_root_relpath) != 0)
+ continue;
+
+ SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &child_kind,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ wcroot, child_src_relpath,
+ iterpool, iterpool));
+
+ if (child_kind == svn_kind_file || child_kind == svn_kind_symlink)
+ SVN_ERR(update_moved_away_file(tc_editor, child_src_relpath,
+ child_dst_relpath,
+ move_dst_op_root_relpath,
+ move_dst_op_root_revision,
+ db, wcroot, iterpool));
+ else if (child_kind == svn_kind_dir)
+ SVN_ERR(update_moved_away_subtree(tc_editor, child_src_relpath,
+ child_dst_relpath,
+ move_dst_op_root_relpath,
+ move_dst_op_root_revision,
+ db, wcroot, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+drive_tree_conflict_editor(svn_editor_t *tc_editor,
+ const char *src_relpath,
+ const char *dst_relpath,
+ svn_wc_operation_t operation,
+ svn_wc_conflict_reason_t local_change,
+ svn_wc_conflict_action_t incoming_change,
+ svn_wc_conflict_version_t *old_version,
+ svn_wc_conflict_version_t *new_version,
+ svn_wc__db_t *db,
+ svn_wc__db_wcroot_t *wcroot,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ /*
+ * Refuse to auto-resolve unsupported tree conflicts.
+ */
+ /* ### Only handle conflicts created by update/switch operations for now. */
+ if (operation != svn_wc_operation_update &&
+ operation != svn_wc_operation_switch)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot auto-resolve tree-conflict on '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ src_relpath, scratch_pool),
+ scratch_pool));
+
+ /*
+ * Drive the TC editor to transfer incoming changes from the move source
+ * to the move destination.
+ *
+ * The pre-update tree is within dst at the op-depth of the move's op-root.
+ * The post-update base tree is within src at op-depth zero.
+ *
+ * We walk the move source (i.e. the post-update tree), comparing each node
+ * with the equivalent node at the move destination and applying the update
+ * to nodes at the move destination.
+ */
+ if (old_version->node_kind == svn_node_file)
+ SVN_ERR(update_moved_away_file(tc_editor, src_relpath, dst_relpath,
+ dst_relpath, old_version->peg_rev,
+ db, wcroot, scratch_pool));
+ else if (old_version->node_kind == svn_node_dir)
+ SVN_ERR(update_moved_away_subtree(tc_editor, src_relpath, dst_relpath,
+ dst_relpath, old_version->peg_rev,
+ db, wcroot, scratch_pool));
+
+ SVN_ERR(svn_editor_complete(tc_editor));
+
+ return SVN_NO_ERROR;
+}
+
+struct update_moved_away_conflict_victim_baton {
+ svn_skel_t **work_items;
+ const char *victim_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+ svn_wc__db_t *db;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+ apr_pool_t *result_pool;
+};
+
+/* An implementation of svn_wc__db_txn_callback_t. */
+static svn_error_t *
+update_moved_away_conflict_victim(void *baton,
+ svn_wc__db_wcroot_t *wcroot,
+ const char *victim_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct update_moved_away_conflict_victim_baton *b = baton;
+ svn_editor_t *tc_editor;
+ struct tc_editor_baton *tc_editor_baton;
+
+ /* ### assumes wc write lock already held */
+
+ /* Construct editor baton. */
+ tc_editor_baton = apr_pcalloc(scratch_pool, sizeof(*tc_editor_baton));
+ tc_editor_baton->src_relpath = b->victim_relpath;
+ SVN_ERR(svn_wc__db_scan_deletion_internal(NULL,
+ &tc_editor_baton->dst_relpath,
+ NULL, NULL, wcroot,
+ tc_editor_baton->src_relpath,
+ scratch_pool, scratch_pool));
+ if (tc_editor_baton->dst_relpath == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The node '%s' has not been moved away"),
+ svn_dirent_local_style(
+ svn_dirent_join(wcroot->abspath,
+ b->victim_relpath,
+ scratch_pool),
+ scratch_pool));
+ tc_editor_baton->old_version= b->old_version;
+ tc_editor_baton->new_version= b->new_version;
+ tc_editor_baton->db = b->db;
+ tc_editor_baton->wcroot = wcroot;
+ tc_editor_baton->work_items = b->work_items;
+ tc_editor_baton->notify_func = b->notify_func;
+ tc_editor_baton->notify_baton = b->notify_baton;
+ tc_editor_baton->result_pool = b->result_pool;
+
+ /* Create the editor... */
+ SVN_ERR(svn_editor_create(&tc_editor, tc_editor_baton,
+ b->cancel_func, b->cancel_baton,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(tc_editor, &editor_ops, scratch_pool));
+
+ /* ... and drive it. */
+ SVN_ERR(drive_tree_conflict_editor(tc_editor,
+ tc_editor_baton->src_relpath,
+ tc_editor_baton->dst_relpath,
+ b->operation,
+ b->local_change, b->incoming_change,
+ tc_editor_baton->old_version,
+ tc_editor_baton->new_version,
+ b->db, wcroot,
+ b->cancel_func, b->cancel_baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_update_moved_away_conflict_victim(svn_skel_t **work_items,
+ const char *victim_abspath,
+ svn_wc__db_t *db,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct update_moved_away_conflict_victim_baton b;
+ svn_wc__db_wcroot_t *wcroot;
+ const char *local_relpath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t local_change;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_version_t *old_version;
+ svn_wc_conflict_version_t *new_version;
+
+ SVN_ERR(get_tc_info(&operation, &local_change, &incoming_change,
+ &old_version, &new_version,
+ victim_abspath, db, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+ db, victim_abspath,
+ scratch_pool, scratch_pool));
+ VERIFY_USABLE_WCROOT(wcroot);
+
+ b.work_items = work_items;
+ b.victim_relpath = local_relpath;
+ b.operation = operation;
+ b.local_change = local_change;
+ b.incoming_change = incoming_change;
+ b.old_version = old_version;
+ b.new_version = new_version;
+ b.db = db;
+ b.notify_func = notify_func;
+ b.notify_baton = notify_baton;
+ b.cancel_func = cancel_func;
+ b.cancel_baton = cancel_baton;
+ b.result_pool = result_pool;
+
+ SVN_ERR(svn_wc__db_with_txn(wcroot, local_relpath,
+ update_moved_away_conflict_victim, &b,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_wc/wc_db_util.c b/subversion/libsvn_wc/wc_db_util.c
index 05b8445..77b1a24 100644
--- a/subversion/libsvn_wc/wc_db_util.c
+++ b/subversion/libsvn_wc/wc_db_util.c
@@ -80,10 +80,10 @@
/* An SQLite application defined function that allows SQL queries to
use "relpath_depth(local_relpath)". */
static svn_error_t *
-relpath_depth(svn_sqlite__context_t *sctx,
- int argc,
- svn_sqlite__value_t *values[],
- apr_pool_t *scratch_pool)
+relpath_depth_sqlite(svn_sqlite__context_t *sctx,
+ int argc,
+ svn_sqlite__value_t *values[],
+ apr_pool_t *scratch_pool)
{
const char *path = NULL;
apr_int64_t depth;
@@ -114,6 +114,7 @@
const char *dir_abspath,
const char *sdb_fname,
svn_sqlite__mode_t smode,
+ svn_boolean_t exclusive,
const char *const *my_statements,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
@@ -140,8 +141,11 @@
my_statements ? my_statements : statements,
0, NULL, result_pool, scratch_pool));
+ if (exclusive)
+ SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_PRAGMA_LOCKING_MODE));
+
SVN_ERR(svn_sqlite__create_scalar_function(*sdb, "relpath_depth", 1,
- relpath_depth, NULL));
+ relpath_depth_sqlite, NULL));
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_wc/wc_db_wcroot.c b/subversion/libsvn_wc/wc_db_wcroot.c
index 79ea96a..b6f0f96 100644
--- a/subversion/libsvn_wc/wc_db_wcroot.c
+++ b/subversion/libsvn_wc/wc_db_wcroot.c
@@ -26,6 +26,8 @@
#include <assert.h>
#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_version.h"
#include "wc.h"
#include "adm_files.h"
@@ -182,7 +184,7 @@
svn_error_clear(err_pdb);
return result;
}
-
+
if (err_sdb)
{
apr_status_t result = err_sdb->apr_err;
@@ -196,7 +198,7 @@
svn_error_t *
svn_wc__db_open(svn_wc__db_t **db,
- const svn_config_t *config,
+ svn_config_t *config,
svn_boolean_t auto_upgrade,
svn_boolean_t enforce_empty_wq,
apr_pool_t *result_pool,
@@ -305,9 +307,24 @@
}
/* Auto-upgrade the SDB if possible. */
- if (format < SVN_WC__VERSION && auto_upgrade)
- SVN_ERR(svn_wc__upgrade_sdb(&format, wcroot_abspath, sdb, format,
- scratch_pool));
+ if (format < SVN_WC__VERSION)
+ {
+ if (auto_upgrade)
+ {
+ if (format >= SVN_WC__WC_NG_VERSION)
+ SVN_ERR(svn_wc__upgrade_sdb(&format, wcroot_abspath, sdb, format,
+ scratch_pool));
+ }
+ else
+ return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL,
+ _("The working copy at '%s'\nis too old "
+ "(format %d) to work with client version "
+ "'%s' (expects format %d). You need to "
+ "upgrade the working copy first.\n"),
+ svn_dirent_local_style(wcroot_abspath,
+ scratch_pool), format, SVN_VERSION,
+ SVN_WC__VERSION);
+ }
*wcroot = apr_palloc(result_pool, sizeof(**wcroot));
@@ -366,6 +383,40 @@
}
+/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at
+ * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */
+static svn_error_t *
+read_link_target(const char **link_target_abspath,
+ const char *local_abspath,
+ apr_pool_t *pool)
+{
+ svn_string_t *link_target;
+ const char *canon_link_target;
+
+ SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool));
+ if (link_target->len == 0)
+ return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL,
+ _("The symlink at '%s' points nowhere"),
+ svn_dirent_local_style(local_abspath, pool));
+
+ canon_link_target = svn_dirent_canonicalize(link_target->data, pool);
+
+ /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */
+ if (!svn_dirent_is_absolute(canon_link_target))
+ canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath,
+ pool),
+ canon_link_target, pool);
+
+ /* Collapse any .. in the symlink part of the path. */
+ if (svn_path_is_backpath_present(canon_link_target))
+ SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target,
+ pool));
+ else
+ *link_target_abspath = canon_link_target;
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot,
const char **local_relpath,
@@ -487,6 +538,18 @@
if (adm_subdir_kind == svn_node_dir)
{
+ svn_boolean_t sqlite_exclusive = FALSE;
+
+ err = svn_config_get_bool(db->config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ FALSE);
+ if (err)
+ {
+ svn_error_clear(err);
+ sqlite_exclusive = FALSE;
+ }
+
/* We always open the database in read/write mode. If the database
isn't writable in the filesystem, SQLite will internally open
it as read-only, and we'll get an error if we try to do a write
@@ -496,7 +559,8 @@
we're caching database handles, it make sense to be as permissive
as the filesystem allows. */
err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE,
- svn_sqlite__mode_readwrite, NULL,
+ svn_sqlite__mode_readwrite,
+ sqlite_exclusive, NULL,
db->state_pool, scratch_pool);
if (err == NULL)
{
@@ -563,6 +627,8 @@
if (found_wcroot)
break;
+ SVN_ERR(read_link_target(&local_abspath, local_abspath,
+ scratch_pool));
try_symlink_as_dir:
kind = svn_kind_dir;
moved_upwards = FALSE;
@@ -619,32 +685,45 @@
inside the wcroot, but we know the abspath is this directory
(ie. where we found it). */
- SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
+ err = svn_wc__db_pdh_create_wcroot(wcroot,
apr_pstrdup(db->state_pool, local_abspath),
sdb, pdb, wc_id, FORMAT_FROM_SDB,
db->auto_upgrade, db->enforce_empty_wq,
- db->state_pool, scratch_pool));
+ db->state_pool, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT ||
+ err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) &&
+ kind == svn_kind_symlink)
+ {
+ /* We found an unsupported WC after traversing upwards from a
+ * symlink. Fall through to code below to check if the symlink
+ * points at a supported WC. */
+ svn_error_clear(err);
+ *wcroot = NULL;
+ }
+ else
+ SVN_ERR(err);
}
else
{
/* We found a wc-1 working copy directory. */
SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot,
apr_pstrdup(db->state_pool, local_abspath),
- NULL, NULL, UNKNOWN_WC_ID, wc_format,
+ NULL, UNKNOWN_WC_ID, wc_format,
db->auto_upgrade, db->enforce_empty_wq,
db->state_pool, scratch_pool));
}
- {
- const char *dir_relpath;
+ if (*wcroot)
+ {
+ const char *dir_relpath;
- /* The subdirectory's relpath is easily computed relative to the
- wcroot that we just found. */
- dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
+ /* The subdirectory's relpath is easily computed relative to the
+ wcroot that we just found. */
+ dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL);
- /* And the result local_relpath may include a filename. */
- *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
- }
+ /* And the result local_relpath may include a filename. */
+ *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool);
+ }
if (kind == svn_kind_symlink)
{
@@ -658,33 +737,38 @@
* points to a directory, try to find a wcroot in that directory
* instead. */
- err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, &conflicted, NULL, NULL, NULL,
- NULL, NULL, NULL,
- *wcroot, *local_relpath,
- scratch_pool, scratch_pool);
- if (err)
+ if (*wcroot)
{
- if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
- && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
- return svn_error_trace(err);
+ err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &conflicted,
+ NULL, NULL, NULL, NULL, NULL,
+ NULL, *wcroot, *local_relpath,
+ scratch_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
+ return svn_error_trace(err);
- svn_error_clear(err);
- retry_if_dir = TRUE; /* The symlink is unversioned. */
+ svn_error_clear(err);
+ retry_if_dir = TRUE; /* The symlink is unversioned. */
+ }
+ else
+ {
+ /* The symlink is versioned, or obstructs a versioned node.
+ * Ignore non-conflicted not-present/excluded nodes.
+ * This allows the symlink to redirect the wcroot query to a
+ * directory, regardless of 'invisible' nodes in this WC. */
+ retry_if_dir = ((status == svn_wc__db_status_not_present ||
+ status == svn_wc__db_status_excluded ||
+ status == svn_wc__db_status_server_excluded)
+ && !conflicted);
+ }
}
else
- {
- /* The symlink is versioned, or obstructs a versioned node.
- * Ignore non-conflicted not-present/excluded nodes.
- * This allows the symlink to redirect the wcroot query to a
- * directory, regardless of 'invisible' nodes in this WC. */
- retry_if_dir = ((status == svn_wc__db_status_not_present ||
- status == svn_wc__db_status_excluded ||
- status == svn_wc__db_status_server_excluded)
- && !conflicted);
- }
+ retry_if_dir = TRUE;
if (retry_if_dir)
{
@@ -695,7 +779,11 @@
scratch_pool));
if (resolved_kind == svn_node_dir)
{
- local_abspath = original_abspath;
+ SVN_ERR(read_link_target(&local_abspath, original_abspath,
+ scratch_pool));
+ /* This handle was opened in this function but is not going
+ to be used further so close it. */
+ SVN_ERR(svn_sqlite__close(sdb));
goto try_symlink_as_dir;
}
}
diff --git a/subversion/mod_authz_svn/mod_authz_svn.c b/subversion/mod_authz_svn/mod_authz_svn.c
index 62c6bd9..4c05793 100644
--- a/subversion/mod_authz_svn/mod_authz_svn.c
+++ b/subversion/mod_authz_svn/mod_authz_svn.c
@@ -44,6 +44,7 @@
#include "svn_config.h"
#include "svn_string.h"
#include "svn_repos.h"
+#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "private/svn_fspath.h"
@@ -164,7 +165,8 @@
* Get the, possibly cached, svn_authz_t for this request.
*/
static svn_authz_t *
-get_access_conf(request_rec *r, authz_svn_config_rec *conf)
+get_access_conf(request_rec *r, authz_svn_config_rec *conf,
+ apr_pool_t *scratch_pool)
{
const char *cache_key = NULL;
const char *access_file;
@@ -182,7 +184,7 @@
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "%s", dav_err->desc);
return NULL;
}
- access_file = svn_dirent_join_many(r->pool, repos_path, "conf",
+ access_file = svn_dirent_join_many(scratch_pool, repos_path, "conf",
conf->repo_relative_access_file,
NULL);
}
@@ -194,7 +196,7 @@
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Path to authz file is %s", access_file);
- cache_key = apr_pstrcat(r->pool, "mod_authz_svn:",
+ cache_key = apr_pstrcat(scratch_pool, "mod_authz_svn:",
access_file, (char *)NULL);
apr_pool_userdata_get(&user_data, cache_key, r->connection->pool);
access_conf = user_data;
@@ -243,12 +245,13 @@
/* Return the username to authorize, with case-conversion performed if
CONF->force_username_case is set. */
static char *
-get_username_to_authorize(request_rec *r, authz_svn_config_rec *conf)
+get_username_to_authorize(request_rec *r, authz_svn_config_rec *conf,
+ apr_pool_t *pool)
{
char *username_to_authorize = r->user;
if (username_to_authorize && conf->force_username_case)
{
- username_to_authorize = apr_pstrdup(r->pool, r->user);
+ username_to_authorize = apr_pstrdup(pool, r->user);
convert_case(username_to_authorize,
strcasecmp(conf->force_username_case, "upper") == 0);
}
@@ -283,7 +286,8 @@
svn_authz_t *access_conf = NULL;
svn_error_t *svn_err;
char errbuf[256];
- const char *username_to_authorize = get_username_to_authorize(r, conf);
+ const char *username_to_authorize = get_username_to_authorize(r, conf,
+ r->pool);
switch (r->method_number)
{
@@ -419,7 +423,7 @@
}
/* Retrieve/cache authorization file */
- access_conf = get_access_conf(r,conf);
+ access_conf = get_access_conf(r,conf, r->pool);
if (access_conf == NULL)
return DECLINED;
@@ -577,14 +581,13 @@
}
/*
- * This function is used as a provider to allow mod_dav_svn to bypass the
- * generation of an apache request when checking GET access from
- * "mod_dav_svn/authz.c" .
+ * Implementation of subreq_bypass with scratch_pool parameter.
*/
static int
-subreq_bypass(request_rec *r,
- const char *repos_path,
- const char *repos_name)
+subreq_bypass2(request_rec *r,
+ const char *repos_path,
+ const char *repos_name,
+ apr_pool_t *scratch_pool)
{
svn_error_t *svn_err = NULL;
svn_authz_t *access_conf = NULL;
@@ -595,7 +598,7 @@
conf = ap_get_module_config(r->per_dir_config,
&authz_svn_module);
- username_to_authorize = get_username_to_authorize(r, conf);
+ username_to_authorize = get_username_to_authorize(r, conf, scratch_pool);
/* If configured properly, this should never be true, but just in case. */
if (!conf->anonymous
@@ -606,7 +609,7 @@
}
/* Retrieve authorization file */
- access_conf = get_access_conf(r, conf);
+ access_conf = get_access_conf(r, conf, scratch_pool);
if (access_conf == NULL)
return HTTP_FORBIDDEN;
@@ -620,7 +623,7 @@
username_to_authorize,
svn_authz_none|svn_authz_read,
&authz_access_granted,
- r->pool);
+ scratch_pool);
if (svn_err)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR,
@@ -650,6 +653,26 @@
}
/*
+ * This function is used as a provider to allow mod_dav_svn to bypass the
+ * generation of an apache request when checking GET access from
+ * "mod_dav_svn/authz.c" .
+ */
+static int
+subreq_bypass(request_rec *r,
+ const char *repos_path,
+ const char *repos_name)
+{
+ int status;
+ apr_pool_t *scratch_pool;
+
+ scratch_pool = svn_pool_create(r->pool);
+ status = subreq_bypass2(r, repos_path, repos_name, scratch_pool);
+ svn_pool_destroy(scratch_pool);
+
+ return status;
+}
+
+/*
* Hooks
*/
diff --git a/subversion/mod_dav_svn/activity.c b/subversion/mod_dav_svn/activity.c
index 3f6a5d6..9a5b444 100644
--- a/subversion/mod_dav_svn/activity.c
+++ b/subversion/mod_dav_svn/activity.c
@@ -240,16 +240,21 @@
dav_error *
dav_svn__create_txn(const dav_svn_repos *repos,
const char **ptxn_name,
+ apr_hash_t *revprops,
apr_pool_t *pool)
{
svn_revnum_t rev;
svn_fs_txn_t *txn;
svn_error_t *serr;
- apr_hash_t *revprop_table = apr_hash_make(pool);
+
+ if (! revprops)
+ {
+ revprops = apr_hash_make(pool);
+ }
if (repos->username)
{
- apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
+ apr_hash_set(revprops, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
svn_string_create(repos->username, pool));
}
@@ -262,8 +267,7 @@
}
serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
- revprop_table,
- repos->pool);
+ revprops, repos->pool);
if (serr != NULL)
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
diff --git a/subversion/mod_dav_svn/dav_svn.h b/subversion/mod_dav_svn/dav_svn.h
index 266de82..c6d75a7 100644
--- a/subversion/mod_dav_svn/dav_svn.h
+++ b/subversion/mod_dav_svn/dav_svn.h
@@ -304,9 +304,6 @@
/* for the repository referred to by this request, are bulk updates allowed? */
svn_boolean_t dav_svn__get_bulk_updates_flag(request_rec *r);
-/* for the repository referred to by this request, should httpv2 be advertised? */
-svn_boolean_t dav_svn__get_v2_protocol_flag(request_rec *r);
-
/* for the repository referred to by this request, are subrequests active? */
svn_boolean_t dav_svn__get_pathauthz_flag(request_rec *r);
@@ -328,6 +325,17 @@
SVNParentPath allowed? */
svn_boolean_t dav_svn__get_list_parentpath_flag(request_rec *r);
+/* For the repository referred to by this request, should HTTPv2
+ protocol support be advertised? Note that this also takes into
+ account the support level expected of based on the specified
+ master server version (if provided via SVNMasterVersion). */
+svn_boolean_t dav_svn__check_httpv2_support(request_rec *r);
+
+/* For the repository referred to by this request, should ephemeral
+ txnprop support be advertised? */
+svn_boolean_t dav_svn__check_ephemeral_txnprops_support(request_rec *r);
+
+
/* SPECIAL URI
@@ -376,6 +384,11 @@
/* ### Is this assumed to be URI-encoded? */
const char *dav_svn__get_master_uri(request_rec *r);
+/* Return the version of the master server (used for mirroring) iff a
+ master URI is in place for this location; otherwise, return NULL.
+ Comes from the <SVNMasterVersion> directive. */
+svn_version_t *dav_svn__get_master_version(request_rec *r);
+
/* Return the disk path to the activities db.
Comes from the <SVNActivitiesDB> directive. */
const char *dav_svn__get_activities_db(request_rec *r);
@@ -386,10 +399,10 @@
const char *dav_svn__get_root_dir(request_rec *r);
/* Return the data compression level to be used over the wire. */
-int dav_svn__get_compression_level(void);
+int dav_svn__get_compression_level(request_rec *r);
/* Return the hook script environment parsed from the configuration. */
-apr_hash_t *dav_svn__get_hooks_env(request_rec *r);
+const char *dav_svn__get_hooks_env(request_rec *r);
/** For HTTP protocol v2, these are the new URIs and URI stubs
returned to the client in our OPTIONS response. They all depend
@@ -420,10 +433,17 @@
/*** activity.c ***/
/* Create a new transaction based on HEAD in REPOS, setting *PTXN_NAME
- to the name of that transaction. Use POOL for allocations. */
+ to the name of that transaction. REVPROPS is an optional hash of
+ const char * property names and const svn_string_t * values which
+ will be set as transactions properties on the transaction this
+ function creates. Use POOL for allocations.
+
+ NOTE: This function will overwrite the svn:author property, if
+ any, found in REVPROPS. */
dav_error *
dav_svn__create_txn(const dav_svn_repos *repos,
const char **ptxn_name,
+ apr_hash_t *revprops,
apr_pool_t *pool);
/* If it exists, abort the transaction named TXN_NAME from REPOS. Use
@@ -620,6 +640,7 @@
{ SVN_XML_NAMESPACE, "replay-report" },
{ SVN_XML_NAMESPACE, "get-deleted-rev-report" },
{ SVN_XML_NAMESPACE, SVN_DAV__MERGEINFO_REPORT },
+ { SVN_XML_NAMESPACE, SVN_DAV__INHERITED_PROPS_REPORT },
{ NULL, NULL },
};
@@ -667,23 +688,22 @@
const apr_xml_doc *doc,
ap_filter_t *output);
+dav_error *
+dav_svn__get_inherited_props_report(const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output);
/*** posts/ ***/
-/* The list of Subversion's custom POSTs. */
-/* ### TODO: Populate this list and transmit its contents in the
- ### OPTIONS response.
-static const char * dav_svn__posts_list[] = {
- "create-txn",
- NULL
-};
-*/
-
/* The various POST handlers, defined in posts/, and used by repos.c. */
dav_error *
dav_svn__post_create_txn(const dav_resource *resource,
svn_skel_t *request_skel,
ap_filter_t *output);
+dav_error *
+dav_svn__post_create_txn_with_props(const dav_resource *resource,
+ svn_skel_t *request_skel,
+ ap_filter_t *output);
/*** authz.c ***/
@@ -739,7 +759,11 @@
processing. See dav_new_error_tag for parameter documentation.
Note that DESC may be null (it's hard to track this down from
dav_new_error_tag()'s documentation, but see the dav_error type,
- which says that its desc field may be NULL). */
+ which says that its desc field may be NULL).
+
+ If ERROR_ID is 0, SVN_ERR_RA_DAV_REQUEST_FAILED will be used as a
+ default value for the error code.
+*/
dav_error *
dav_svn__new_error_tag(apr_pool_t *pool,
int status,
@@ -754,7 +778,11 @@
processing. See dav_new_error for parameter documentation.
Note that DESC may be null (it's hard to track this down from
dav_new_error()'s documentation, but see the dav_error type,
- which says that its desc field may be NULL). */
+ which says that its desc field may be NULL).
+
+ If ERROR_ID is 0, SVN_ERR_RA_DAV_REQUEST_FAILED will be used as a
+ default value for the error code.
+*/
dav_error *
dav_svn__new_error(apr_pool_t *pool,
int status,
diff --git a/subversion/mod_dav_svn/deadprops.c b/subversion/mod_dav_svn/deadprops.c
index faf51b9..5beb102 100644
--- a/subversion/mod_dav_svn/deadprops.c
+++ b/subversion/mod_dav_svn/deadprops.c
@@ -1,5 +1,7 @@
/*
- * deadprops.c: mod_dav_svn dead property provider functions for Subversion
+ * deadprops.c: mod_dav_svn provider functions for "dead properties"
+ * (properties implemented by Subversion or its users,
+ * not as part of the WebDAV specification).
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -512,10 +514,6 @@
/* ### namespace check? */
if (elem->first_child && !strcmp(elem->first_child->name, SVN_DAV__OLD_VALUE))
{
- const char *propname;
-
- get_repos_propname(db, name, &propname);
-
/* Parse OLD_PROPVAL. */
old_propval = svn_string_create(dav_xml_get_cdata(elem->first_child, pool,
0 /* strip_white */),
diff --git a/subversion/mod_dav_svn/liveprops.c b/subversion/mod_dav_svn/liveprops.c
index e6a1f88..95c5c7a 100644
--- a/subversion/mod_dav_svn/liveprops.c
+++ b/subversion/mod_dav_svn/liveprops.c
@@ -1,5 +1,7 @@
/*
- * liveprops.c: mod_dav_svn live property provider functions for Subversion
+ * liveprops.c: mod_dav_svn provider functions for "live properties"
+ * (properties implemented by the WebDAV specification
+ * itself, not unique to Subversion or its users).
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -275,8 +277,8 @@
int propid,
dav_prop_insert what,
apr_text_header *phdr,
- apr_pool_t *scratch_pool,
- apr_pool_t *result_pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
const char *value = NULL;
const char *s;
@@ -820,7 +822,7 @@
scratch_pool = svn_pool_create(result_pool);
rv = insert_prop_internal(resource, propid, what, phdr,
- scratch_pool, result_pool);
+ result_pool, scratch_pool);
svn_pool_destroy(scratch_pool);
return rv;
@@ -958,7 +960,7 @@
{
svn_pool_clear(iterpool);
(void) insert_prop_internal(resource, spec->propid, what, phdr,
- iterpool, resource->pool);
+ resource->pool, iterpool);
}
svn_pool_destroy(iterpool);
diff --git a/subversion/mod_dav_svn/mod_dav_svn.c b/subversion/mod_dav_svn/mod_dav_svn.c
index a7064c4..e7b8cd4 100644
--- a/subversion/mod_dav_svn/mod_dav_svn.c
+++ b/subversion/mod_dav_svn/mod_dav_svn.c
@@ -42,6 +42,7 @@
#include "mod_dav_svn.h"
#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
#include "dav_svn.h"
#include "mod_authz_svn.h"
@@ -59,6 +60,12 @@
typedef struct server_conf_t {
const char *special_uri;
svn_boolean_t use_utf8;
+
+ /* The compression level we will pass to svn_txdelta_to_svndiff3()
+ * for wire-compression. Negative value used to specify default
+ compression level. */
+ int compression_level;
+
} server_conf_t;
@@ -92,11 +99,12 @@
enum conf_flag list_parentpath; /* whether to allow GET of parentpath */
const char *root_dir; /* our top-level directory */
const char *master_uri; /* URI to the master SVN repos */
+ svn_version_t *master_version; /* version of master server */
const char *activities_db; /* path to activities database(s) */
enum conf_flag txdelta_cache; /* whether to enable txdelta caching */
enum conf_flag fulltext_cache; /* whether to enable fulltext caching */
enum conf_flag revprop_cache; /* whether to enable revprop caching */
- apr_hash_t *hooks_env; /* environment for hook scripts */
+ const char *hooks_env; /* path to hook script env config file */
} dir_conf_t;
@@ -109,10 +117,6 @@
/* The authz_svn provider for bypassing path authz. */
static authz_svn__subreq_bypass_func_t pathauthz_bypass_func = NULL;
-/* The compression level we will pass to svn_txdelta_to_svndiff3()
- * for wire-compression */
-static int svn__compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
-
static int
init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
@@ -163,7 +167,11 @@
static void *
create_server_config(apr_pool_t *p, server_rec *s)
{
- return apr_pcalloc(p, sizeof(server_conf_t));
+ server_conf_t *conf = apr_pcalloc(p, sizeof(server_conf_t));
+
+ conf->compression_level = -1;
+
+ return conf;
}
@@ -179,6 +187,17 @@
newconf->special_uri = INHERIT_VALUE(parent, child, special_uri);
+ if (child->compression_level < 0)
+ {
+ /* Inherit compression level from parent if not configured for this
+ VirtualHost. */
+ newconf->compression_level = parent->compression_level;
+ }
+ else
+ {
+ newconf->compression_level = child->compression_level;
+ }
+
return newconf;
}
@@ -214,6 +233,7 @@
newconf->fs_path = INHERIT_VALUE(parent, child, fs_path);
newconf->master_uri = INHERIT_VALUE(parent, child, master_uri);
+ newconf->master_version = INHERIT_VALUE(parent, child, master_version);
newconf->activities_db = INHERIT_VALUE(parent, child, activities_db);
newconf->repo_name = INHERIT_VALUE(parent, child, repo_name);
newconf->xslt_uri = INHERIT_VALUE(parent, child, xslt_uri);
@@ -282,6 +302,25 @@
static const char *
+SVNMasterVersion_cmd(cmd_parms *cmd, void *config, const char *arg1)
+{
+ dir_conf_t *conf = config;
+ svn_error_t *err;
+ svn_version_t *version;
+
+ err = svn_version__parse_version_string(&version, arg1, cmd->pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ return "Malformed master server version string.";
+ }
+
+ conf->master_version = version;
+ return NULL;
+}
+
+
+static const char *
SVNActivitiesDB_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
dir_conf_t *conf = config;
@@ -513,6 +552,7 @@
static const char *
SVNCompressionLevel_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
+ server_conf_t *conf;
int value = 0;
svn_error_t *err = svn_cstring_atoi(&value, arg1);
if (err)
@@ -530,7 +570,9 @@
(int)SVN_DELTA_COMPRESSION_LEVEL_NONE,
(int)SVN_DELTA_COMPRESSION_LEVEL_MAX);
- svn__compression_level = value;
+ conf = ap_get_module_config(cmd->server->module_config,
+ &dav_svn_module);
+ conf->compression_level = value;
return NULL;
}
@@ -550,43 +592,9 @@
static const char *
SVNHooksEnv_cmd(cmd_parms *cmd, void *config, const char *arg1)
{
- apr_array_header_t *var;
+ dir_conf_t *conf = config;
- var = svn_cstring_split(arg1, "=", TRUE, cmd->pool);
- if (var && var->nelts >= 2)
- {
- dir_conf_t *conf = config;
- const char *name;
- const char *val;
-
- if (! conf->hooks_env)
- conf->hooks_env = apr_hash_make(cmd->pool);
-
- name = apr_pstrdup(apr_hash_pool_get(conf->hooks_env),
- APR_ARRAY_IDX(var, 0, const char *));
-
- /* Special case for values which contain '='. */
- if (var->nelts > 2)
- {
- svn_stringbuf_t *buf;
- int i;
-
- buf = svn_stringbuf_create(APR_ARRAY_IDX(var, 1, const char *),
- cmd->pool);
- for (i = 2; i < var->nelts; i++)
- {
- svn_stringbuf_appendbyte(buf, '=');
- svn_stringbuf_appendcstr(buf, APR_ARRAY_IDX(var, i, const char *));
- }
-
- val = apr_pstrdup(apr_hash_pool_get(conf->hooks_env), buf->data);
- }
- else
- val = apr_pstrdup(apr_hash_pool_get(conf->hooks_env),
- APR_ARRAY_IDX(var, 1, const char *));
-
- apr_hash_set(conf->hooks_env, name, APR_HASH_KEY_STRING, val);
- }
+ conf->hooks_env = svn_dirent_internal_style(arg1, cmd->pool);
return NULL;
}
@@ -689,6 +697,16 @@
}
+svn_version_t *
+dav_svn__get_master_version(request_rec *r)
+{
+ dir_conf_t *conf;
+
+ conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
+ return conf->master_uri ? conf->master_version : NULL;
+}
+
+
const char *
dav_svn__get_xslt_uri(request_rec *r)
{
@@ -786,12 +804,39 @@
svn_boolean_t
-dav_svn__get_v2_protocol_flag(request_rec *r)
+dav_svn__check_httpv2_support(request_rec *r)
{
dir_conf_t *conf;
+ svn_boolean_t available;
conf = ap_get_module_config(r->per_dir_config, &dav_svn_module);
- return conf->v2_protocol == CONF_FLAG_ON;
+ available = conf->v2_protocol == CONF_FLAG_ON;
+
+ /* If our configuration says that HTTPv2 is available, but we are
+ proxying requests to a master Subversion server which lacks
+ support for HTTPv2, we dumb ourselves down. */
+ if (available)
+ {
+ svn_version_t *version = dav_svn__get_master_version(r);
+ if (version && (! svn_version__at_least(version, 1, 7, 0)))
+ available = FALSE;
+ }
+ return available;
+}
+
+
+svn_boolean_t
+dav_svn__check_ephemeral_txnprops_support(request_rec *r)
+{
+ svn_version_t *version = dav_svn__get_master_version(r);
+
+ /* We know this server supports ephemeral txnprops. But if we're
+ proxying requests to a master server, we need to see if it
+ supports them, too. */
+ if (version && (! svn_version__at_least(version, 1, 8, 0)))
+ return FALSE;
+
+ return TRUE;
}
@@ -873,12 +918,24 @@
int
-dav_svn__get_compression_level(void)
+dav_svn__get_compression_level(request_rec *r)
{
- return svn__compression_level;
+ server_conf_t *conf;
+
+ conf = ap_get_module_config(r->server->module_config,
+ &dav_svn_module);
+
+ if (conf->compression_level < 0)
+ {
+ return SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ }
+ else
+ {
+ return conf->compression_level;
+ }
}
-apr_hash_t *
+const char *
dav_svn__get_hooks_env(request_rec *r)
{
dir_conf_t *conf;
@@ -1077,6 +1134,11 @@
"specifies a URI to access a master Subversion repository"),
/* per directory/location */
+ AP_INIT_TAKE1("SVNMasterVersion", SVNMasterVersion_cmd, NULL, ACCESS_CONF,
+ "specifies the Subversion release version of a master "
+ "Subversion server "),
+
+ /* per directory/location */
AP_INIT_TAKE1("SVNActivitiesDB", SVNActivitiesDB_cmd, NULL, ACCESS_CONF,
"specifies the location in the filesystem in which the "
"activities database(s) should be stored"),
@@ -1136,10 +1198,12 @@
"use UTF-8 as native character encoding (default is ASCII)."),
/* per directory/location */
- AP_INIT_ITERATE("SVNHooksEnv", SVNHooksEnv_cmd, NULL,
- ACCESS_CONF|RSRC_CONF,
- "Set the environment of hook scripts via any number of "
- "VAR=VAL arguments (the default hook environment is empty)."),
+ AP_INIT_TAKE1("SVNHooksEnv", SVNHooksEnv_cmd, NULL,
+ ACCESS_CONF|RSRC_CONF,
+ "Sets the path to the configuration file for the environment "
+ "of hook scripts. If not absolute, the path is relative to "
+ "the repository's conf directory (by default the hooks-env "
+ "file in the repository is used)."),
{ NULL }
};
diff --git a/subversion/mod_dav_svn/posts/create_txn.c b/subversion/mod_dav_svn/posts/create_txn.c
index c19b123..a8aff54 100644
--- a/subversion/mod_dav_svn/posts/create_txn.c
+++ b/subversion/mod_dav_svn/posts/create_txn.c
@@ -42,13 +42,64 @@
request_rec *r = resource->info->r;
/* Create a Subversion repository transaction based on HEAD. */
- if ((derr = dav_svn__create_txn(resource->info->repos, &txn_name,
+ if ((derr = dav_svn__create_txn(resource->info->repos, &txn_name, NULL,
resource->pool)))
return derr;
/* Build a "201 Created" response with header that tells the
client our new transaction's name. */
- vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER);
+ vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER);
+ if (vtxn_name && vtxn_name[0])
+ {
+ /* If the client supplied a vtxn name then store a mapping from
+ the client name to the FS transaction name in the activity
+ database. */
+ if ((derr = dav_svn__store_activity(resource->info->repos,
+ vtxn_name, txn_name)))
+ return derr;
+ apr_table_set(r->headers_out, SVN_DAV_VTXN_NAME_HEADER, vtxn_name);
+ }
+ else
+ apr_table_set(r->headers_out, SVN_DAV_TXN_NAME_HEADER, txn_name);
+
+ r->status = HTTP_CREATED;
+
+ return NULL;
+}
+
+
+/* Respond to a "create-txn-with-props" POST request.
+ *
+ * Syntax: ( create-txn-with-props (PROPNAME PROPVAL [PROPNAME PROPVAL ...])
+ */
+dav_error *
+dav_svn__post_create_txn_with_props(const dav_resource *resource,
+ svn_skel_t *request_skel,
+ ap_filter_t *output)
+{
+ const char *txn_name;
+ const char *vtxn_name;
+ dav_error *derr;
+ svn_error_t *err;
+ request_rec *r = resource->info->r;
+ apr_hash_t *revprops;
+ svn_skel_t *proplist_skel = request_skel->children->next;
+
+ if ((err = svn_skel__parse_proplist(&revprops, proplist_skel,
+ resource->pool)))
+ {
+ return dav_svn__convert_err(err, HTTP_BAD_REQUEST,
+ "Malformatted request skel", resource->pool);
+ }
+
+ /* Create a Subversion repository transaction based on HEAD. */
+ if ((derr = dav_svn__create_txn(resource->info->repos, &txn_name,
+ revprops, resource->pool)))
+ return derr;
+
+ /* Build a "201 Created" response with header that tells the
+ client our new transaction's name. */
+ vtxn_name = apr_table_get(r->headers_in, SVN_DAV_VTXN_NAME_HEADER);
if (vtxn_name && vtxn_name[0])
{
/* If the client supplied a vtxn name then store a mapping from
diff --git a/subversion/mod_dav_svn/reports/file-revs.c b/subversion/mod_dav_svn/reports/file-revs.c
index f8a15c1..4856a95 100644
--- a/subversion/mod_dav_svn/reports/file-revs.c
+++ b/subversion/mod_dav_svn/reports/file-revs.c
@@ -52,6 +52,9 @@
/* SVNDIFF version to use when sending to client. */
int svndiff_version;
+ /* Compression level to use for SVNDIFF. */
+ int compression_level;
+
/* Used by the delta iwndow handler. */
svn_txdelta_window_handler_t window_handler;
void *window_baton;
@@ -208,7 +211,7 @@
pool);
svn_txdelta_to_svndiff3(&frb->window_handler, &frb->window_baton,
base64_stream, frb->svndiff_version,
- dav_svn__get_compression_level(), pool);
+ frb->compression_level, pool);
*window_handler = delta_window_handler;
*window_baton = frb;
/* Start the txdelta element wich will be terminated by the window
@@ -306,6 +309,7 @@
frb.output = output;
frb.needs_header = TRUE;
frb.svndiff_version = resource->info->svndiff_version;
+ frb.compression_level = dav_svn__get_compression_level(resource->info->r);
/* file_rev_handler will send header first time it is called. */
diff --git a/subversion/mod_dav_svn/reports/inherited-props.c b/subversion/mod_dav_svn/reports/inherited-props.c
new file mode 100644
index 0000000..052318f
--- /dev/null
+++ b/subversion/mod_dav_svn/reports/inherited-props.c
@@ -0,0 +1,233 @@
+/*
+ * inherited-props.c: mod_dav_svn REPORT handler for querying inherited props.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_xml.h>
+
+#include <http_request.h>
+#include <http_log.h>
+#include <mod_dav.h>
+
+#include "svn_pools.h"
+#include "svn_repos.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_dav.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_fspath.h"
+#include "private/svn_dav_protocol.h"
+#include "private/svn_log.h"
+#include "private/svn_mergeinfo_private.h"
+
+#include "../dav_svn.h"
+
+dav_error *
+dav_svn__get_inherited_props_report(const dav_resource *resource,
+ const apr_xml_doc *doc,
+ ap_filter_t *output)
+{
+ svn_error_t *serr;
+ dav_error *derr = NULL;
+ apr_xml_elem *child;
+ apr_array_header_t *inherited_props;
+ dav_svn__authz_read_baton arb;
+ int ns;
+ apr_bucket_brigade *bb;
+ const char *path = "/";
+ svn_fs_root_t *root;
+ int i;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ apr_pool_t *iterpool;
+
+ /* Sanity check. */
+ ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
+ if (ns == -1)
+ {
+ return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
+ "The request does not contain the 'svn:' "
+ "namespace, so it is not going to have "
+ "certain required elements.",
+ SVN_DAV_ERROR_NAMESPACE,
+ SVN_DAV_ERROR_TAG);
+ }
+
+ iterpool = svn_pool_create(resource->pool);
+
+ for (child = doc->root->first_child;
+ child != NULL;
+ child = child->next)
+ {
+ /* if this element isn't one of ours, then skip it */
+ if (child->ns != ns)
+ continue;
+
+ if (strcmp(child->name, SVN_DAV__REVISION) == 0)
+ {
+ rev = SVN_STR_TO_REV(dav_xml_get_cdata(child, iterpool, 1));
+ }
+ else if (strcmp(child->name, SVN_DAV__PATH) == 0)
+ {
+ path = dav_xml_get_cdata(child, resource->pool, 0);
+ if ((derr = dav_svn__test_canonical(path, iterpool)))
+ return derr;
+ path = svn_fspath__join(resource->info->repos_path, path,
+ resource->pool);
+ }
+ /* else unknown element; skip it */
+ }
+
+ /* Build authz read baton */
+ arb.r = resource->info->r;
+ arb.repos = resource->info->repos;
+
+ /* Build inherited property brigade */
+ bb = apr_brigade_create(resource->pool, output->c->bucket_alloc);
+
+ serr = svn_fs_revision_root(&root, resource->info->repos->fs,
+ rev, resource->pool);
+ if (serr != NULL)
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "couldn't retrieve revision root",
+ resource->pool);
+
+ serr = svn_repos_fs_get_inherited_props(&inherited_props, root, path,
+ dav_svn__authz_read_func(&arb),
+ &arb, resource->pool, iterpool);
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
+ resource->pool);
+ goto cleanup;
+ }
+
+ serr = dav_svn__brigade_puts(bb, output,
+ DAV_XML_HEADER DEBUG_CR
+ "<S:" SVN_DAV__INHERITED_PROPS_REPORT " "
+ "xmlns:S=\"" SVN_XML_NAMESPACE "\" "
+ "xmlns:D=\"DAV:\">" DEBUG_CR);
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
+ resource->pool);
+ goto cleanup;
+ }
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_ITEM ">"
+ DEBUG_CR
+ "<S:" SVN_DAV__IPROP_PATH ">%s</S:" SVN_DAV__IPROP_PATH ">"
+ DEBUG_CR,
+ apr_xml_quote_string(resource->pool, elt->path_or_url, 0));
+
+ if (!serr)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(resource->pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ svn_string_t *propval = svn__apr_hash_index_val(hi);
+ const char *xml_safe;
+
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPNAME ">%s</S:"
+ SVN_DAV__IPROP_PROPNAME ">" DEBUG_CR,
+ apr_xml_quote_string(iterpool, propname, 0));
+
+ if (!serr)
+ {
+ if (svn_xml_is_xml_safe(propval->data, propval->len))
+ {
+ svn_stringbuf_t *tmp = NULL;
+ svn_xml_escape_cdata_string(&tmp, propval,
+ iterpool);
+ xml_safe = tmp->data;
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPVAL ">%s</S:"
+ SVN_DAV__IPROP_PROPVAL ">" DEBUG_CR, xml_safe);
+ }
+ else
+ {
+ xml_safe = svn_base64_encode_string2(
+ propval, TRUE, iterpool)->data;
+ serr = dav_svn__brigade_printf(
+ bb, output,
+ "<S:" SVN_DAV__IPROP_PROPVAL
+ " encoding=\"base64\"" ">%s</S:"
+ SVN_DAV__IPROP_PROPVAL ">" DEBUG_CR, xml_safe);
+ }
+ }
+
+ if (serr)
+ break;
+ }
+ if (!serr)
+ serr = dav_svn__brigade_printf(bb, output,
+ "</S:" SVN_DAV__IPROP_ITEM ">"
+ DEBUG_CR);
+ }
+
+ if (serr)
+ {
+ derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error ending REPORT response.",
+ resource->pool);
+ goto cleanup;
+ }
+ }
+
+ if ((serr = dav_svn__brigade_puts(bb, output,
+ "</S:" SVN_DAV__INHERITED_PROPS_REPORT ">"
+ DEBUG_CR)))
+ {
+ derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Error ending REPORT response.",
+ resource->pool);
+ goto cleanup;
+ }
+
+ cleanup:
+
+ /* Log this 'high level' svn action. */
+ dav_svn__operational_log(resource->info,
+ svn_log__get_inherited_props(path, rev,
+ resource->pool));
+ svn_pool_destroy(iterpool);
+ return dav_svn__final_flush_or_error(resource->info->r, bb, output,
+ derr, resource->pool);
+}
diff --git a/subversion/mod_dav_svn/reports/replay.c b/subversion/mod_dav_svn/reports/replay.c
index 7679ee3..2873346 100644
--- a/subversion/mod_dav_svn/reports/replay.c
+++ b/subversion/mod_dav_svn/reports/replay.c
@@ -47,6 +47,7 @@
ap_filter_t *output;
svn_boolean_t started;
svn_boolean_t sending_textdelta;
+ int compression_level;
} edit_baton_t;
@@ -326,7 +327,7 @@
eb->output,
pool),
0,
- dav_svn__get_compression_level(),
+ eb->compression_level,
pool);
eb->sending_textdelta = TRUE;
@@ -367,6 +368,7 @@
void **edit_baton,
apr_bucket_brigade *bb,
ap_filter_t *output,
+ int compression_level,
apr_pool_t *pool)
{
edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
@@ -376,6 +378,7 @@
eb->output = output;
eb->started = FALSE;
eb->sending_textdelta = FALSE;
+ eb->compression_level = compression_level;
e->set_target_revision = set_target_revision;
e->open_root = open_root;
@@ -506,7 +509,9 @@
goto cleanup;
}
- make_editor(&editor, &edit_baton, bb, output, resource->pool);
+ make_editor(&editor, &edit_baton, bb, output,
+ dav_svn__get_compression_level(resource->info->r),
+ resource->pool);
if ((err = svn_repos_replay2(root, base_dir, low_water_mark,
send_deltas, editor, edit_baton,
diff --git a/subversion/mod_dav_svn/reports/update.c b/subversion/mod_dav_svn/reports/update.c
index 9e55e78..f06a043 100644
--- a/subversion/mod_dav_svn/reports/update.c
+++ b/subversion/mod_dav_svn/reports/update.c
@@ -81,9 +81,16 @@
/* True iff client requested all data inline in the report. */
svn_boolean_t send_all;
+ /* True iff client requested that properties be transmitted
+ inline. (This is implied when "send_all" is set.) */
+ svn_boolean_t include_props;
+
/* SVNDIFF version to send to client. */
int svndiff_version;
+ /* Compression level of SVNDIFF deltas. */
+ int compression_level;
+
/* Did the client submit this REPORT request via the HTTPv2 "me
resource" and are we advertising support for as much? */
svn_boolean_t enable_v2_response;
@@ -119,6 +126,10 @@
/* File/dir copied? */
svn_boolean_t copyfrom;
+ /* Does the client need to fetch additional properties for this
+ item? */
+ svn_boolean_t fetch_props;
+
/* Array of const char * names of removed properties. (Used only
for copied files/dirs in skelta mode.) */
apr_array_header_t *removed_props;
@@ -432,7 +443,7 @@
static svn_error_t *
-close_helper(svn_boolean_t is_dir, item_baton_t *baton)
+close_helper(svn_boolean_t is_dir, item_baton_t *baton, apr_pool_t *pool)
{
if (baton->uc->resource_walk)
return SVN_NO_ERROR;
@@ -446,14 +457,21 @@
for (i = 0; i < baton->removed_props->nelts; i++)
{
- /* We already XML-escaped the property name in change_xxx_prop. */
qname = APR_ARRAY_IDX(baton->removed_props, i, const char *);
+ qname = apr_xml_quote_string(pool, qname, 1);
SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"<S:remove-prop name=\"%s\"/>"
DEBUG_CR, qname));
}
}
+ /* If our client need to fetch properties, let it know. */
+ if (baton->fetch_props)
+ SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
+ "<S:fetch-props/>" DEBUG_CR));
+
+
+ /* Let's tie it off, nurse. */
if (baton->added)
SVN_ERR(dav_svn__brigade_printf(baton->uc->bb, baton->uc->output,
"</S:add-%s>" DEBUG_CR,
@@ -473,13 +491,13 @@
{
if ((! uc->resource_walk) && (! uc->started_update))
{
- SVN_ERR(dav_svn__brigade_printf(uc->bb, uc->output,
- DAV_XML_HEADER DEBUG_CR
- "<S:update-report xmlns:S=\""
- SVN_XML_NAMESPACE "\" "
- "xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
- "xmlns:D=\"DAV:\" %s>" DEBUG_CR,
- uc->send_all ? "send-all=\"true\"" : ""));
+ SVN_ERR(dav_svn__brigade_printf(
+ uc->bb, uc->output,
+ DAV_XML_HEADER DEBUG_CR "<S:update-report xmlns:S=\""
+ SVN_XML_NAMESPACE "\" xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\" "
+ "xmlns:D=\"DAV:\" %s %s>" DEBUG_CR,
+ uc->send_all ? "send-all=\"true\"" : "",
+ uc->include_props ? "inline-props=\"true\"" : ""));
uc->started_update = TRUE;
}
@@ -593,87 +611,117 @@
static svn_error_t *
+send_propchange(item_baton_t *b,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ const char *qname;
+
+ /* Ensure that the property name is XML-safe.
+ apr_xml_quote_string() doesn't realloc if there is nothing to
+ quote, so dup the name, but only if necessary. */
+ qname = apr_xml_quote_string(b->pool, name, 1);
+ if (qname == name)
+ qname = apr_pstrdup(b->pool, name);
+
+ if (value)
+ {
+ const char *qval;
+
+ if (svn_xml_is_xml_safe(value->data, value->len))
+ {
+ svn_stringbuf_t *tmp = NULL;
+ svn_xml_escape_cdata_string(&tmp, value, pool);
+ qval = tmp->data;
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:set-prop name=\"%s\">",
+ qname));
+ }
+ else
+ {
+ qval = svn_base64_encode_string2(value, TRUE, pool)->data;
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:set-prop name=\"%s\" "
+ "encoding=\"base64\">" DEBUG_CR,
+ qname));
+ }
+
+ SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output, qval));
+ SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output,
+ "</S:set-prop>" DEBUG_CR));
+ }
+ else /* value is null, so this is a prop removal */
+ {
+ SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
+ "<S:remove-prop name=\"%s\"/>"
+ DEBUG_CR,
+ qname));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
upd_change_xxx_prop(void *baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
item_baton_t *b = baton;
- const char *qname;
/* Resource walks say nothing about props. */
if (b->uc->resource_walk)
return SVN_NO_ERROR;
- /* Else this not a resource walk, so either send props or cache them
- to send later, depending on whether this is a modern report
- response or not. */
-
- qname = apr_xml_quote_string(b->pool, name, 1);
-
- /* apr_xml_quote_string doesn't realloc if there is nothing to
- quote, so dup the name, but only if necessary. */
- if (qname == name)
- qname = apr_pstrdup(b->pool, name);
+ /* If we get here, this not a resource walk, so either send props or
+ cache them to send later, depending on whether this is a modern
+ report response or not. */
/* Even if we are not in send-all mode we have the prop changes already,
so send them to the client now instead of telling the client to fetch
them later. */
- if (b->uc->send_all || !b->added)
+ if (b->uc->send_all)
{
- if (value)
- {
- const char *qval;
-
- if (svn_xml_is_xml_safe(value->data, value->len))
- {
- svn_stringbuf_t *tmp = NULL;
- svn_xml_escape_cdata_string(&tmp, value, pool);
- qval = tmp->data;
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:set-prop name=\"%s\">",
- qname));
- }
- else
- {
- qval = svn_base64_encode_string2(value, TRUE, pool)->data;
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:set-prop name=\"%s\" "
- "encoding=\"base64\">" DEBUG_CR,
- qname));
- }
-
- SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output, qval));
- SVN_ERR(dav_svn__brigade_puts(b->uc->bb, b->uc->output,
- "</S:set-prop>" DEBUG_CR));
- }
- else /* value is null, so this is a prop removal */
- {
- SVN_ERR(dav_svn__brigade_printf(b->uc->bb, b->uc->output,
- "<S:remove-prop name=\"%s\"/>"
- DEBUG_CR,
- qname));
- }
+ SVN_ERR(send_propchange(b, name, value, pool));
}
- else if (!value)
+ else
{
- /* This is an addition in "skelta" (that is, "not send-all")
- mode so there is no strict need for an inline response.
- Clients will assume that added objects need all to have all
- their properties explicitly fetched from the server. */
-
- /* Now, if the object is actually a copy, we'll still need to
- cache (and later transmit) property removals, because
- fetching the object's current property set alone isn't
- sufficient to communicate the fact that additional properties
- were, in fact, removed from the copied base object in order
- to arrive at that set. */
- if (b->copyfrom)
+ if (b->added)
{
- if (! b->removed_props)
- b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
+ /* This is an addition in "skelta" (that is, "not send-all")
+ mode so there is no strict need for an inline response.
+ Clients will assume that added objects need all to have
+ all their properties explicitly fetched from the
+ server. */
- APR_ARRAY_PUSH(b->removed_props, const char *) = qname;
+ /* That said, beginning in Subversion 1.8, clients might
+ request even in skelta mode that we transmit properties
+ on newly added files explicitly. */
+ if ((! b->copyfrom) && value && b->uc->include_props)
+ {
+ SVN_ERR(send_propchange(b, name, value, pool));
+ }
+
+ /* Now, if the object is actually a copy and this is a
+ property removal, we'll still need to cache (and later
+ transmit) property removals, because fetching the
+ object's current property set alone isn't sufficient to
+ communicate the fact that additional properties were, in
+ fact, removed from the copied base object in order to
+ arrive at that set. */
+ if (b->copyfrom && (! value))
+ {
+ if (! b->removed_props)
+ b->removed_props = apr_array_make(b->pool, 1, sizeof(name));
+
+ APR_ARRAY_PUSH(b->removed_props, const char *) = name;
+ }
+ }
+ else
+ {
+ /* "skelta" mode non-addition. Just send the change. */
+ SVN_ERR(send_propchange(b, name, value, pool));
}
}
@@ -684,7 +732,7 @@
static svn_error_t *
upd_close_directory(void *dir_baton, apr_pool_t *pool)
{
- return close_helper(TRUE /* is_dir */, dir_baton);
+ return close_helper(TRUE /* is_dir */, dir_baton, pool);
}
@@ -795,7 +843,7 @@
svn_txdelta_to_svndiff3(&(wb->handler), &(wb->handler_baton),
base64_stream, file->uc->svndiff_version,
- dav_svn__get_compression_level(), file->pool);
+ file->uc->compression_level, file->pool);
*handler = window_handler;
*handler_baton = wb;
@@ -845,7 +893,7 @@
text_checksum));
}
- return close_helper(FALSE /* is_dir */, file);
+ return close_helper(FALSE /* is_dir */, file, pool);
}
@@ -874,6 +922,49 @@
}
+/* Validate that REVISION is a valid revision number for repository in
+ which YOUNGEST is the latest revision. Use RESOURCE as a
+ convenient way to access the request record and a pool for error
+ messaging. (It's okay if REVISION is SVN_INVALID_REVNUM, as in
+ the related contexts that just means "the youngest revision".)
+
+ REVTYPE is just a string describing the type/purpose of REVISION,
+ used in the generated error string. */
+static dav_error *
+validate_input_revision(svn_revnum_t revision,
+ svn_revnum_t youngest,
+ const char *revtype,
+ const dav_resource *resource)
+{
+ if (! SVN_IS_VALID_REVNUM(revision))
+ return SVN_NO_ERROR;
+
+ if (revision > youngest)
+ {
+ svn_error_t *serr;
+
+ if (dav_svn__get_master_uri(resource->info->r))
+ {
+ serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ "No such %s '%ld' found in the repository. "
+ "Perhaps the repository is out of date with "
+ "respect to the master repository?",
+ revtype, revision);
+ }
+ else
+ {
+ serr = svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ "No such %s '%ld' found in the repository.",
+ revtype, revision);
+ }
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Invalid revision found in update report "
+ "request.", resource->pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+
dav_error *
dav_svn__update_report(const dav_resource *resource,
const apr_xml_doc *doc,
@@ -883,8 +974,7 @@
apr_xml_elem *child;
void *rbaton = NULL;
update_ctx_t uc = { 0 };
- svn_revnum_t revnum = SVN_INVALID_REVNUM;
- svn_boolean_t revnum_is_head = FALSE;
+ svn_revnum_t youngest, revnum = SVN_INVALID_REVNUM;
svn_revnum_t from_revnum = SVN_INVALID_REVNUM;
int ns;
/* entry_counter and entry_is_empty are for operational logging. */
@@ -944,11 +1034,20 @@
&& (strcmp(this_attr->value, "true") == 0))
{
uc.send_all = TRUE;
+ uc.include_props = TRUE;
break;
}
}
}
+ /* Ask the repository about its youngest revision (which we'll need
+ for some input validation later). */
+ if ((serr = svn_fs_youngest_rev(&youngest, repos->fs, resource->pool)))
+ return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
+ "Could not determine the youngest "
+ "revision for the update process.",
+ resource->pool);
+
for (child = doc->root->first_child; child != NULL; child = child->next)
{
/* Note that child->name might not match any of the cases below.
@@ -1066,6 +1165,31 @@
if (strcmp(cdata, "no") == 0)
text_deltas = FALSE;
}
+ if (child->ns == ns && strcmp(child->name, "include-props") == 0)
+ {
+ cdata = dav_xml_get_cdata(child, resource->pool, 1);
+ if (! *cdata)
+ return malformed_element_error(child->name, resource->pool);
+ if (strcmp(cdata, "no") != 0)
+ uc.include_props = TRUE;
+ }
+ }
+
+ /* If a target revision wasn't requested, or the requested target
+ revision was invalid, just update to HEAD as of the moment we
+ queried the youngest revision. Otherwise, at least make sure the
+ request makes sense in light of that youngest revision
+ number. */
+ if (! SVN_IS_VALID_REVNUM(revnum))
+ {
+ revnum = youngest;
+ }
+ else
+ {
+ derr = validate_input_revision(revnum, youngest, "target revision",
+ resource);
+ if (derr)
+ return derr;
}
if (!saw_depth && !saw_recursive && (requested_depth == svn_depth_unknown))
@@ -1083,19 +1207,8 @@
SVN_DAV_ERROR_TAG);
}
- /* If a revision for this operation was not dictated to us, this
- means "update to whatever the current HEAD is now". */
- if (revnum == SVN_INVALID_REVNUM)
- {
- if ((serr = svn_fs_youngest_rev(&revnum, repos->fs, resource->pool)))
- return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
- "Could not determine the youngest "
- "revision for the update process.",
- resource->pool);
- revnum_is_head = TRUE;
- }
-
uc.svndiff_version = resource->info->svndiff_version;
+ uc.compression_level = dav_svn__get_compression_level(resource->info->r);
uc.resource = resource;
uc.output = output;
uc.anchor = src_path;
@@ -1165,7 +1278,7 @@
editor->close_file = upd_close_file;
editor->absent_file = upd_absent_file;
editor->close_edit = upd_close_edit;
- if ((serr = svn_repos_begin_report2(&rbaton, revnum,
+ if ((serr = svn_repos_begin_report3(&rbaton, revnum,
repos->repos,
src_path, target,
dst_path,
@@ -1176,6 +1289,7 @@
editor, &uc,
dav_svn__authz_read_func(&arb),
&arb,
+ 0, /* disable zero-copy for now */
resource->pool)))
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
@@ -1211,27 +1325,10 @@
{
rev = SVN_STR_TO_REV(this_attr->value);
saw_rev = TRUE;
- if (revnum_is_head && rev > revnum)
- {
- if (dav_svn__get_master_uri(resource->info->r))
- return dav_svn__new_error_tag(
- resource->pool,
- HTTP_INTERNAL_SERVER_ERROR, 0,
- "A reported revision is higher than the "
- "current repository HEAD revision. "
- "Perhaps the repository is out of date "
- "with respect to the master repository?",
- SVN_DAV_ERROR_NAMESPACE,
- SVN_DAV_ERROR_TAG);
- else
- return dav_svn__new_error_tag(
- resource->pool,
- HTTP_INTERNAL_SERVER_ERROR, 0,
- "A reported revision is higher than the "
- "current repository HEAD revision.",
- SVN_DAV_ERROR_NAMESPACE,
- SVN_DAV_ERROR_TAG);
- }
+ if ((derr = validate_input_revision(rev, youngest,
+ "reported revision",
+ resource)))
+ return derr;
}
else if (strcmp(this_attr->name, "depth") == 0)
depth = svn_depth_from_word(this_attr->value);
diff --git a/subversion/mod_dav_svn/repos.c b/subversion/mod_dav_svn/repos.c
index edaa70a..b2dd028 100644
--- a/subversion/mod_dav_svn/repos.c
+++ b/subversion/mod_dav_svn/repos.c
@@ -1149,8 +1149,11 @@
comb->res.collection = TRUE; /* ### always true? */
/* versioned = baselined = working = FALSE */
- comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
- path->data, (char *)NULL);
+ if (base->info->repos->root_path[1])
+ comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path,
+ path->data, (char *)NULL);
+ else
+ comb->res.uri = path->data;
comb->res.info = &comb->priv;
comb->res.hooks = &dav_svn__hooks_repository;
comb->res.pool = base->pool;
@@ -1499,7 +1502,7 @@
repos->xslt_uri = dav_svn__get_xslt_uri(r);
repos->autoversioning = dav_svn__get_autoversioning_flag(r);
repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
- repos->v2_protocol = dav_svn__get_v2_protocol_flag(r);
+ repos->v2_protocol = dav_svn__check_httpv2_support(r);
repos->base_url = ap_construct_url(r->pool, "", r);
repos->special_uri = dav_svn__get_special_uri(r);
repos->username = r->user;
@@ -1908,9 +1911,11 @@
only use a temporary redirect. */
apr_table_setn(r->headers_out, "Location",
ap_construct_url(r->pool,
- apr_psprintf(r->pool, "%s%s?p=%ld",
- comb->priv.repos->root_path,
- newpath, working_rev),
+ apr_psprintf(r->pool, "%s%s?p=%ld",
+ (comb->priv.repos->root_path[1]
+ ? comb->priv.repos->root_path
+ : ""),
+ newpath, working_rev),
r));
return dav_svn__new_error(r->pool,
prevstr ? HTTP_MOVED_PERMANENTLY
@@ -2077,7 +2082,7 @@
repos->bulk_updates = dav_svn__get_bulk_updates_flag(r);
/* Are we advertising HTTP v2 protocol support? */
- repos->v2_protocol = dav_svn__get_v2_protocol_flag(r);
+ repos->v2_protocol = dav_svn__check_httpv2_support(r);
/* Path to activities database */
repos->activities_db = dav_svn__get_activities_db(r);
@@ -2217,8 +2222,13 @@
HTTP_INTERNAL_SERVER_ERROR, r);
}
- /* Configure the hooks environment, if not empty. */
- svn_repos_hooks_setenv(repos->repos, dav_svn__get_hooks_env(r));
+ /* Configure hook script environment variables. */
+ serr = svn_repos_hooks_setenv(repos->repos, dav_svn__get_hooks_env(r),
+ r->connection->pool, r->pool);
+ if (serr)
+ return dav_svn__sanitize_error(serr,
+ "Error settings hooks environment",
+ HTTP_INTERNAL_SERVER_ERROR, r);
}
/* cache the filesystem object */
@@ -3077,6 +3087,13 @@
if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM))
{
mimetype = SVN_SVNDIFF_MIME_TYPE;
+
+ /* Note the base that this svndiff is based on, and tell any
+ intermediate caching proxies that this header is
+ significant. */
+ apr_table_setn(r->headers_out, "Vary", SVN_DAV_DELTA_BASE_HEADER);
+ apr_table_setn(r->headers_out, SVN_DAV_DELTA_BASE_HEADER,
+ resource->info->delta_base);
}
svn_error_clear(serr);
}
@@ -3158,7 +3175,7 @@
} diff_ctx_t;
-static svn_error_t *
+static svn_error_t * __attribute__((warn_unused_result))
write_to_filter(void *baton, const char *buffer, apr_size_t *len)
{
diff_ctx_t *dc = baton;
@@ -3179,7 +3196,7 @@
}
-static svn_error_t *
+static svn_error_t * __attribute__((warn_unused_result))
close_filter(void *baton)
{
diff_ctx_t *dc = baton;
@@ -3629,7 +3646,7 @@
/* get a handler/baton for writing into the output stream */
svn_txdelta_to_svndiff3(&handler, &h_baton,
o_stream, resource->info->svndiff_version,
- dav_svn__get_compression_level(),
+ dav_svn__get_compression_level(resource->info->r),
resource->pool);
/* got everything set up. read in delta windows and shove them into
@@ -4345,8 +4362,11 @@
res->baselined = base->baselined;
/* collection = FALSE. ### not necessarily correct */
- res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
- path, (char *)NULL);
+ if (base->info->repos->root_path[1])
+ res->uri = apr_pstrcat(base->pool, base->info->repos->root_path,
+ path, (char *)NULL);
+ else
+ res->uri = path;
res->hooks = &dav_svn__hooks_repository;
res->pool = base->pool;
@@ -4444,7 +4464,7 @@
dav_resource *resource,
ap_filter_t *output)
{
- svn_skel_t *request_skel;
+ svn_skel_t *request_skel, *post_skel;
int status;
apr_pool_t *pool = resource->pool;
@@ -4461,16 +4481,23 @@
return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
"Unable to identify skel POST request flavor.");
- if (svn_skel__matches_atom(request_skel->children, "create-txn"))
+ post_skel = request_skel->children;
+
+ /* NOTE: If you add POST handlers here, you'll want to advertise
+ that the server supports them, too. See version.c:get_option(). */
+
+ if (svn_skel__matches_atom(post_skel, "create-txn"))
{
return dav_svn__post_create_txn(resource, request_skel, output);
}
- else
+ else if (svn_skel__matches_atom(post_skel, "create-txn-with-props"))
{
- return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
- "Unsupported skel POST request flavor.");
+ return dav_svn__post_create_txn_with_props(resource,
+ request_skel, output);
}
- /* NOTREACHED */
+
+ return dav_svn__new_error(pool, HTTP_BAD_REQUEST, 0,
+ "Unsupported skel POST request flavor.");
}
int dav_svn__method_post(request_rec *r)
diff --git a/subversion/mod_dav_svn/util.c b/subversion/mod_dav_svn/util.c
index f6eaef5..d3d5995 100644
--- a/subversion/mod_dav_svn/util.c
+++ b/subversion/mod_dav_svn/util.c
@@ -45,6 +45,9 @@
int error_id,
const char *desc)
{
+ if (error_id == 0)
+ error_id = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
/*
* Note: dav_new_error() in httpd 2.0/2.2 always treated
* the errno field in dav_error as an apr_status_t when
@@ -70,6 +73,9 @@
const char *namespace,
const char *tagname)
{
+ if (error_id == 0)
+ error_id = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
#if AP_MODULE_MAGIC_AT_LEAST(20091119,0)
return dav_new_error_tag(pool, status, error_id, 0,
desc, namespace, tagname);
diff --git a/subversion/mod_dav_svn/version.c b/subversion/mod_dav_svn/version.c
index e6904c5..3a5f17f 100644
--- a/subversion/mod_dav_svn/version.c
+++ b/subversion/mod_dav_svn/version.c
@@ -37,7 +37,9 @@
#include "svn_props.h"
#include "svn_dav.h"
#include "svn_base64.h"
+#include "svn_version.h"
#include "private/svn_repos_private.h"
+#include "private/svn_subr_private.h"
#include "private/svn_dav_protocol.h"
#include "private/svn_log.h"
#include "private/svn_fspath.h"
@@ -146,6 +148,7 @@
apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS);
apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY);
+ apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INHERITED_PROPS);
/* Mergeinfo is a special case: here we merely say that the server
* knows how to handle mergeinfo -- whether the repository does too
* is a separate matter.
@@ -192,6 +195,14 @@
}
}
+ /* If we're allowed (by configuration) to do so, advertise support
+ for ephemeral transaction properties. */
+ if (dav_svn__check_ephemeral_txnprops_support(r))
+ {
+ apr_table_addn(r->headers_out, "DAV",
+ SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS);
+ }
+
if (resource->info->repos->fs)
{
svn_error_t *serr;
@@ -234,6 +245,25 @@
DeltaV-free! If we're configured to advise this support, do so. */
if (resource->info->repos->v2_protocol)
{
+ int i;
+ svn_version_t *master_version = dav_svn__get_master_version(r);
+
+ /* The list of Subversion's custom POSTs and which versions of
+ Subversion support them. We need this latter information
+ when acting as a WebDAV slave -- we don't want to claim
+ support for a POST type if the master server which will
+ actually have to handle it won't recognize it.
+
+ Keep this in sync with what's handled in handle_post_request().
+ */
+ struct posts_versions_t {
+ const char *post_name;
+ svn_version_t min_version;
+ } posts_versions[] = {
+ { "create-txn", { 1, 7, 0, "" } },
+ { "create-txn-with-props", { 1, 8, 0, "" } },
+ };
+
apr_table_set(r->headers_out, SVN_DAV_ROOT_URI_HEADER, repos_root_uri);
apr_table_set(r->headers_out, SVN_DAV_ME_RESOURCE_HEADER,
apr_pstrcat(resource->pool, repos_root_uri, "/",
@@ -256,6 +286,23 @@
apr_table_set(r->headers_out, SVN_DAV_VTXN_STUB_HEADER,
apr_pstrcat(resource->pool, repos_root_uri, "/",
dav_svn__get_vtxn_stub(r), (char *)NULL));
+
+ /* Report the supported POST types. */
+ for (i = 0; i < sizeof(posts_versions)/sizeof(posts_versions[0]); ++i)
+ {
+ /* If we're proxying to a master server and its version
+ number is declared, we can selectively filter out POST
+ types that it doesn't support. */
+ if (master_version
+ && (! svn_version__at_least(master_version,
+ posts_versions[i].min_version.major,
+ posts_versions[i].min_version.minor,
+ posts_versions[i].min_version.patch)))
+ continue;
+
+ apr_table_addn(r->headers_out, SVN_DAV_SUPPORTED_POSTS_HEADER,
+ apr_pstrdup(resource->pool, posts_versions[i].post_name));
+ }
}
return NULL;
@@ -398,7 +445,7 @@
shared_activity = apr_pstrdup(resource->info->r->pool, uuid_buf);
derr = dav_svn__create_txn(resource->info->repos, &shared_txn_name,
- resource->info->r->pool);
+ NULL, resource->info->r->pool);
if (derr) return derr;
derr = dav_svn__store_activity(resource->info->repos,
@@ -1093,6 +1140,10 @@
{
return dav_svn__get_deleted_rev_report(resource, doc, output);
}
+ else if (strcmp(doc->root->name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ return dav_svn__get_inherited_props_report(resource, doc, output);
+ }
/* NOTE: if you add a report, don't forget to add it to the
* dav_svn__reports_list[] array.
*/
@@ -1138,7 +1189,8 @@
SVN_DAV_ERROR_NAMESPACE,
SVN_DAV_ERROR_TAG);
- err = dav_svn__create_txn(resource->info->repos, &txn_name, resource->pool);
+ err = dav_svn__create_txn(resource->info->repos, &txn_name,
+ NULL, resource->pool);
if (err != NULL)
return err;
diff --git a/subversion/po/es.po b/subversion/po/es.po
index c67fce8..a3161da 100644
--- a/subversion/po/es.po
+++ b/subversion/po/es.po
@@ -13565,7 +13565,7 @@
#~ " 'HEAD' lo último del repositorio\n"
#~ " 'BASE' rev base del ítem de la c. de trab.\n"
#~ " 'COMMITTED' último commit en o antes de BASE\n"
-#~ " 'PREV' revisión justo antes de COMMITED"
+#~ " 'PREV' revisión justo antes de COMMITTED"
#~ msgid "Can't open file '%s' for reading"
#~ msgstr "No se pudo abrir el archivo '%s' para leer"
diff --git a/subversion/po/pl.po b/subversion/po/pl.po
index 227b7d0..62035ae 100644
--- a/subversion/po/pl.po
+++ b/subversion/po/pl.po
@@ -2225,7 +2225,7 @@
#: ../libsvn_fs/fs-loader.c:313
#, c-format
msgid "Can't allocate FS mutex"
-msgstr "Nie udało się utworzyć semefora FS"
+msgstr "Nie udało się utworzyć semafora FS"
#: ../libsvn_fs/fs-loader.c:348
#, c-format
@@ -3675,7 +3675,7 @@
#: ../libsvn_ra_neon/get_locks.c:425 ../libsvn_ra_neon/get_locks.c:429
#: ../libsvn_ra_serf/locks.c:566
msgid "Server does not support locking features"
-msgstr "Serwer nie obsługuje blokowania zatwiedzeń"
+msgstr "Serwer nie obsługuje blokowania zatwierdzeń"
#: ../libsvn_ra_neon/lock.c:196
msgid "Invalid creation date header value in response."
@@ -3703,7 +3703,7 @@
#: ../libsvn_ra_neon/lock.c:553
msgid "Failed to fetch lock information"
-msgstr "Nieudało się pobrać informacji o blokadzie"
+msgstr "Nie udało się pobrać informacji o blokadzie"
#: ../libsvn_ra_neon/log.c:169 ../libsvn_ra_serf/log.c:228
#, c-format
@@ -4751,7 +4751,7 @@
#: ../libsvn_repos/hooks.c:379
#, c-format
msgid "Failed to run '%s' hook; broken symlink"
-msgstr "Niepowiodło się uruchomienie skryptu hook '%s'; uszkodzone dowiązanie symboliczne"
+msgstr "Nie powiodło się uruchomienie skryptu hook '%s'; uszkodzone dowiązanie symboliczne"
#: ../libsvn_repos/hooks.c:589
msgid ""
@@ -4863,7 +4863,7 @@
#: ../libsvn_repos/reporter.c:1234
#, c-format
msgid "Target path '%s' does not exist"
-msgstr "Docelowa śieżka '%s' nie istnieje"
+msgstr "Docelowa ścieżka '%s' nie istnieje"
#: ../libsvn_repos/reporter.c:1242
msgid "Cannot replace a directory from within"
@@ -5215,7 +5215,7 @@
#: ../libsvn_subr/error.c:602
#, c-format
msgid "In file '%s' line %d: internal malfunction"
-msgstr "W pliku '%s' w linii %d: wewnątrzne niepoprawne funkcjonowanie"
+msgstr "W pliku '%s' w linii %d: wewnętrzne niepoprawne funkcjonowanie"
#: ../libsvn_subr/io.c:169
#, c-format
@@ -5230,7 +5230,7 @@
#: ../libsvn_subr/io.c:457 ../libsvn_subr/io.c:3771
#, c-format
msgid "Can't open '%s'"
-msgstr "Nie mozna otworzyć '%s'"
+msgstr "Nie można otworzyć '%s'"
#: ../libsvn_subr/io.c:483 ../libsvn_subr/io.c:569
#, c-format
@@ -6280,7 +6280,7 @@
#: ../libsvn_wc/conflicts.c:299
msgid "Invalid 'conflict_result' argument"
-msgstr "Błądny argument 'conflict_result'"
+msgstr "Błędny argument 'conflict_result'"
#: ../libsvn_wc/conflicts.c:409
#, c-format
@@ -6731,7 +6731,7 @@
#: ../libsvn_wc/props.c:2643
#, c-format
msgid "Invalid %s property on '%s': target '%s' is an absolute path or involves '..'"
-msgstr "Błędny atrybut %s dla '%s': cel '%s' jest ścieżką bezwględną albo wykorzystuje '..'"
+msgstr "Błędny atrybut %s dla '%s': cel '%s' jest ścieżką bezwzględną albo wykorzystuje '..'"
#: ../libsvn_wc/questions.c:203
#, fuzzy, c-format
@@ -8245,7 +8245,7 @@
"\n"
"OSTRZEŻENIE: Dla kompatybilności z poprzednimi wersjami Subversion kopiowania\n"
"wykonywane pomiędzy dwoma ścieżkami kopii roboczej (KR -> KR) nie kontaktują\n"
-"się z reporytorium. W związku z tym nie mogą domyślnie propagować informacji\n"
+"się z repozytorium. W związku z tym nie mogą domyślnie propagować informacji\n"
"o łączeniach zmian ze źródła kopii do celu.\n"
#: ../svn/main.c:505
@@ -8673,7 +8673,7 @@
" E Istniały (Existed)\n"
" R Zastąpiony (Replaced)\n"
"\n"
-" Znaki w pierwszej kolumnia informują o samym obiekcie. Znaki w drugiej\n"
+" Znaki w pierwszej kolumnie informują o samym obiekcie. Znaki w drugiej\n"
" kolumnie informują o atrybutach obiektu. 'C' w trzeciej kolumnie wskazuje\n"
" na konflikt drzewny, podczas gdy 'C' w pierwszej i drugiej kolumnie\n"
" wskazuje odpowiednio na konflikt tekstowy w plikach i w atrybutach plików.\n"
@@ -9663,7 +9663,7 @@
#: ../svn/main.c:2065
msgid "The lock comment is a pathname (was -F intended?); use '--force-log' to override"
-msgstr "Opis blokady jest ścieżką (chciano użyć -F?); użyj --force-log, bywymusić użycie takiego opisu"
+msgstr "Opis blokady jest ścieżką (chciano użyć -F?); użyj --force-log, by wymusić użycie takiego opisu"
#: ../svn/main.c:2079
msgid "--relocate and --depth are mutually exclusive"
diff --git a/subversion/po/pt_BR.po b/subversion/po/pt_BR.po
index 5452a7d..0ff478e 100644
--- a/subversion/po/pt_BR.po
+++ b/subversion/po/pt_BR.po
@@ -12663,8 +12663,8 @@
#~ " '{' DATA '}' revisão no início da data\n"
#~ " 'HEAD' último no repositório\n"
#~ " 'BASE' revisão base do item da cópia de trabalho\n"
-#~ " 'COMMITED' último commit em ou antes de BASE\n"
-#~ " 'PREV' revisão exatamente antes de COMMITED"
+#~ " 'COMMITTED' último commit em ou antes de BASE\n"
+#~ " 'PREV' revisão exatamente antes de COMMITTED"
#~ msgid "Can't get user name"
#~ msgstr "Não foi possível obter o nome do usuário"
diff --git a/subversion/po/zh_CN.po b/subversion/po/zh_CN.po
index 65701af..60b8cf7 100644
--- a/subversion/po/zh_CN.po
+++ b/subversion/po/zh_CN.po
@@ -55,8 +55,8 @@
msgstr ""
"Project-Id-Version: subversion 1.7\n"
"Report-Msgid-Bugs-To: dev@subversion.apache.org\n"
-"POT-Creation-Date: 2012-02-27 09:24+0000\n"
-"PO-Revision-Date: 2012-02-27 09:24+0000\n"
+"POT-Creation-Date: 2012-08-18 03:48+0000\n"
+"PO-Revision-Date: 2012-08-18 03:49+0000\n"
"Last-Translator: Subversion Developers <dev@subversion.apache.org>\n"
"Language-Team: Simplified Chinese <dev@subversion.apache.org>\n"
"Language: \n"
@@ -96,39 +96,39 @@
msgid "File is not mutable: filesystem '%s', revision %ld, path '%s'"
msgstr "文件不是可变的: 文件系统“%s”,版本“%ld”,路径 “%s”"
-#. FS is of type "svn fs_t *".
+#. FS is of type "svn_fs_t *".
#, c-format
msgid "'%s' is not a directory in filesystem '%s'"
msgstr "“%s”在文件系统“%s”中不是目录"
-#. FS is of type "svn fs_t *".
+#. FS is of type "svn_fs_t *".
#, c-format
msgid "'%s' is not a file in filesystem '%s'"
msgstr "“%s”在文件系统“%s”中不是文件"
-#. FS is of type "svn fs_t *", LOCK is of type "svn_lock_t *".
+#. FS is of type "svn_fs_t *", LOCK is of type "svn_lock_t *".
#, c-format
msgid "Path '%s' is already locked by user '%s' in filesystem '%s'"
msgstr "路径“%s”已经被用户“%s”锁定,其文件系统是“%s”"
-#. FS is of type "svn fs_t *".
+#. FS is of type "svn_fs_t *".
#, c-format
msgid "No lock on path '%s' in filesystem '%s'"
msgstr "路径“%s”没有被锁定,其文件系统是“%s”"
-#. FS is of type "svn fs_t *".
+#. FS is of type "svn_fs_t *".
#, c-format
msgid "Lock has expired: lock-token '%s' in filesystem '%s'"
msgstr "锁已经过期:令牌 “%s”,文件系统 “%s”"
-#. FS is of type "svn fs_t *".
+#. FS is of type "svn_fs_t *".
#, c-format
msgid "No username is currently associated with filesystem '%s'"
msgstr "当前没有用户名称与文件系统“%s”关联"
#. SVN_FS__ERR_LOCK_OWNER_MISMATCH: trying to use a lock whose
#. LOCK_OWNER doesn't match the USERNAME associated with FS.
-#. FS is of type "svn fs_t *".
+#. FS is of type "svn_fs_t *".
#, c-format
msgid "User '%s' is trying to use a lock owned by '%s' in filesystem '%s'"
msgstr "用户“%s”试图使用“%s”的锁,其文件系统是“%s”"
@@ -178,6 +178,9 @@
msgid "Invalid changelist name"
msgstr "无效的修改列表名称"
+msgid "Invalid atomic"
+msgstr "无效原子"
+
msgid "No such XML tag attribute"
msgstr "没有这种 XML 标签属性"
@@ -524,6 +527,12 @@
msgid "Property value in filesystem differs from the provided base value"
msgstr "文件系统中的属性值与提供的基础值不同"
+msgid "The filesystem editor completion process was not followed"
+msgstr ""
+
+msgid "A packed revprop could not be read"
+msgstr "不能读取打包的 revprop"
+
msgid "The repository is locked, perhaps for db recovery"
msgstr "版本库被锁,可能正在恢复"
@@ -908,6 +917,9 @@
msgid "Constraint error in SQLite db"
msgstr "在 SQLite 数据库中的约束出错"
+msgid "too many memcached servers configured"
+msgstr ""
+
msgid "Error parsing arguments"
msgstr "解析参数出错"
@@ -1053,10 +1065,6 @@
msgstr "“%s” 不存在"
#, c-format
-msgid "Path '%s' does not exist"
-msgstr "路径 “%s” 不存在"
-
-#, c-format
msgid "Path '%s' already exists"
msgstr "路径 “%s” 已经存在"
@@ -1124,20 +1132,16 @@
msgstr "服务器禁止修改文件“%s”"
#, c-format
-msgid "Aborting commit: '%s' remains in conflict"
-msgstr "提交终止: “%s” 处于冲突状态"
-
-#, c-format
msgid "Aborting commit: '%s' remains in tree-conflict"
msgstr "提交终止: “%s” 仍处于树冲突状态"
#, c-format
-msgid "Unknown entry kind for '%s'"
-msgstr "“%s” 有未知的条目类型"
+msgid "Aborting commit: '%s' remains in conflict"
+msgstr "提交终止: “%s” 处于冲突状态"
#, c-format
-msgid "Entry '%s' has unexpectedly changed special status"
-msgstr "条目 “%s” 意外改变状态"
+msgid "Node '%s' has unexpectedly changed kind"
+msgstr "节点 “%s” 的类型意外改变"
#, c-format
msgid "'%s' is scheduled for addition, but is missing"
@@ -1163,6 +1167,10 @@
msgstr "标准属性不能设置为版本属性"
#, c-format
+msgid "Path '%s' does not exist"
+msgstr "路径 “%s” 不存在"
+
+#, c-format
msgid "Path '%s' is not a directory"
msgstr "路径 “%s” 不是目录"
@@ -1279,8 +1287,8 @@
msgid "Not all required revisions are specified"
msgstr "没有全部提供需要的版本"
-msgid "At least one revision must be non-local for a pegged diff"
-msgstr "对于铆钉差异,必须至少有一个非本地版本"
+msgid "At least one revision must be something other than BASE or WORKING when diffing a URL"
+msgstr "要比较 URL 的时候,必须有一个版本,既不是基础版本,又不是工作版本"
#, c-format
msgid "Diff target '%s' was not found in the repository at revision '%ld'"
@@ -1301,16 +1309,27 @@
msgid "Sorry, svn_client_diff6 was called in a way that is not yet supported"
msgstr "抱歉,svn_client_diff6 尚不支持这种调用方式"
-msgid "Only diffs between a path's text-base and its working files are supported at this time"
-msgstr "当前 diff 只支持参考文件与其工作文件比较"
+#, c-format
+msgid "'%s' is not the same node kind as '%s'"
+msgstr "'%s' 与 '%s' 的节点类型不同"
+
+#, c-format
+msgid "'%s' is not a file or directory"
+msgstr "“%s”不是文件或目录"
#, c-format
msgid "Directory '%s' has no URL"
msgstr "目录 “%s” 没有 URL"
+msgid "Summarized diffs are only supported between a path's text-base and its working files at this time"
+msgstr "当前概要差异只支持参考文件与其工作文件比较"
+
msgid "Summarizing diff cannot compare repository to WC"
msgstr "版本库和工作副本不能比较差异概要"
+msgid "Cannot ignore properties and show only properties at the same time"
+msgstr ""
+
#, c-format
msgid "'%s' is not a valid EOL value"
msgstr "“%s” 不是一个有效的 EOL 值"
@@ -1319,6 +1338,10 @@
msgstr "目的目录已存在,除非强迫为之,否则不会覆盖"
#, c-format
+msgid "The node '%s' was not found."
+msgstr "找不到节点 '%s'。"
+
+#, c-format
msgid "Destination file '%s' exists, and will not be overwritten unless forced"
msgstr "目标文件“%s”已存在,除非强迫为之,否则不会被覆盖"
@@ -1363,6 +1386,10 @@
msgstr "不支持的外部对象:文件外部对象“%s”的 URL 不在版本库“%s”中"
#, c-format
+msgid "Path '%s' is not in the working copy"
+msgstr "路径 “%s” 不在工作副本中"
+
+#, c-format
msgid "Traversal of '%s' found no ambient depth"
msgstr "在 “%s” 中找不到深度"
@@ -1504,6 +1531,19 @@
"复兴只能用于版本 %ld 到 %ld 以前曾经从 %s 合并到复兴源的情况,但是现在不是这种情况:\n"
"%s"
+#, c-format
+msgid "Can't reintegrate into '%s' because it is locally added and therefore not related to the merge source"
+msgstr ""
+
+msgid "The required merge is reintegrate-like, and the record-only option cannot be used with this kind of merge"
+msgstr ""
+
+msgid "The required merge is reintegrate-like, and the depth option cannot be used with this kind of merge"
+msgstr ""
+
+msgid "The required merge is reintegrate-like, and the force option cannot be used with this kind of merge"
+msgstr ""
+
msgid "Only depths 'infinity' and 'empty' are currently supported"
msgstr "当前仅支持深度 'infinity' 和 'empty'"
@@ -1641,14 +1681,6 @@
msgstr "“%s”与“%s”没有共同的祖先"
#, c-format
-msgid "URL '%s' is not a child of repository root URL '%s'"
-msgstr "URL “%s” 不是版本库根 URL “%s” 的子节点"
-
-#, c-format
-msgid "URL '%s' is not inside repository"
-msgstr "URL“%s”不在版本库中"
-
-#, c-format
msgid "Cannot mix repository and working copy targets"
msgstr "不能混合版本库和工作副本目标"
@@ -1735,6 +1767,29 @@
msgstr "删除 mmap“%s”失败"
#, c-format
+msgid "Revision for modifying '%s' is required"
+msgstr ""
+
+#, c-format
+msgid "'%s' is out of date; try updating"
+msgstr "“%s” 已经过时;请先更新"
+
+#, c-format
+msgid "'%s' has been modified since the commit began (restart the commit)"
+msgstr "自提交开始,“%s” 已有本地修改 (重新提交)"
+
+#, c-format
+msgid "'%s' already exists, so may be out of date; try updating"
+msgstr "“%s” 已经存在,因此可能过时;请先更新"
+
+msgid "The filesystem does not support 'absent' nodes"
+msgstr "此文件系统不支持 'absent' 节点"
+
+#, c-format
+msgid "Invalid name for FS type '%s'"
+msgstr "文件类型“%s”的名称无效"
+
+#, c-format
msgid "'%s' does not define '%s()'"
msgstr "“%s” 未定义 “%s()”"
@@ -1837,6 +1892,9 @@
msgid "storing checksum-reps record"
msgstr "正在存储校验和展现记录"
+msgid "deleting entry from 'checksum-reps' table"
+msgstr "正在从 “checksum-reps” 表中删除条目"
+
msgid "allocating new representation reuse ID (getting 'next-key')"
msgstr "正在分配新展现的复用 ID(正在获取 “next-key”)"
@@ -1858,6 +1916,42 @@
msgid "reading copy"
msgstr "正在读取副本"
+msgid "storing lock token record"
+msgstr "正在存储加锁令牌记录"
+
+msgid "deleting entry from 'lock-tokens' table"
+msgstr "正在从 “lock-tokens” 表中删除条目"
+
+msgid "reading lock token"
+msgstr "正在读取加锁令牌"
+
+msgid "storing lock record"
+msgstr "正在存储加锁记录"
+
+msgid "deleting lock from 'locks' table"
+msgstr "正在从“locks”表删除锁"
+
+msgid "reading lock"
+msgstr "正在读取锁"
+
+msgid "creating cursor for reading lock tokens"
+msgstr "正在为读取加锁令牌创建游标"
+
+msgid "fetching lock tokens"
+msgstr "正在取出加锁令牌"
+
+msgid "fetching lock tokens (closing cursor)"
+msgstr "正在取出加锁令牌 (正在关闭游标)"
+
+msgid "deleting record from 'miscellaneous' table"
+msgstr "正在从“miscellaneous”表删除条目"
+
+msgid "storing miscellaneous record"
+msgstr "正在存储杂项记录"
+
+msgid "fetching miscellaneous record"
+msgstr "正在取出杂项记录"
+
#, c-format
msgid "Node origin for '%s' exists in filesystem '%s' with a different value (%s) than what we were about to store (%s)"
msgstr "“%s” 的原始节点在文件系统 “%s” 中,取值(%s)与我们要存储的值(%s)不同"
@@ -1865,6 +1959,9 @@
msgid "storing node-origins record"
msgstr "正在存储节点原始记录"
+msgid "deleting entry from 'node-origins' table"
+msgstr "正在从“node-origins”表删除条目"
+
msgid "allocating new node ID (getting 'next-key')"
msgstr "正在分配新的节点 ID (正在获取“next-key”)"
@@ -1910,12 +2007,98 @@
msgid "reading filesystem revision"
msgstr "正在读取文件系统版本"
+msgid "updating filesystem revision"
+msgstr "正在更新文件系统版本"
+
+msgid "storing filesystem revision"
+msgstr "正在存储文件系统版本"
+
+msgid "getting youngest revision (creating cursor)"
+msgstr ""
+
+msgid "getting youngest revision (finding last entry)"
+msgstr ""
+
+#. You can't commit a transaction with open cursors, because:
+#. 1) key/value pairs don't get deleted until the cursors referring
+#. to them are closed, so closing a cursor can fail for various
+#. reasons, and txn_commit shouldn't fail that way, and
+#. 2) using a cursor after committing its transaction can cause
+#. undetectable database corruption.
+msgid "getting youngest revision (closing cursor)"
+msgstr ""
+
msgid "creating cursor for reading a string"
msgstr "正在为读取字符串创建指针"
+msgid "moving cursor"
+msgstr "正在移动指针"
+
+msgid "rerunning cursor move"
+msgstr ""
+
+msgid "reading string"
+msgstr "正在读取字符串"
+
+#. Done with the cursor.
+msgid "closing string-reading cursor"
+msgstr "正在关闭读取字符串的游标"
+
+msgid "getting next-key value"
+msgstr "正在取出 next-key 的值"
+
+#. ignore the error, the original is
+#. more important.
+msgid "bumping next string key"
+msgstr "正在跳到下一个字符串键"
+
+msgid "appending string"
+msgstr "正在追加字符串"
+
+#. Handle any other error conditions.
+msgid "clearing string"
+msgstr "正在清理字符串"
+
+msgid "storing empty contents"
+msgstr "正在存储空内容"
+
+msgid "fetching string length"
+msgstr "正在获取字符串的长度"
+
+#. Handle any other error conditions.
+msgid "deleting string"
+msgstr "正在删除字符串"
+
+msgid "writing copied data"
+msgstr "正在写入已复制的数据"
+
+msgid "fetching string data for a copy"
+msgstr ""
+
msgid "storing transaction record"
msgstr "正在存储事务记录"
+msgid "allocating new transaction ID (getting 'next-key')"
+msgstr "正在分配新事务 ID(正在获取 “next-key”)"
+
+msgid "bumping next transaction key"
+msgstr "正在跳到下一个事务键"
+
+msgid "deleting entry from 'transactions' table"
+msgstr "正在从“transactions”表删除条目"
+
+msgid "reading transaction"
+msgstr "正在读取事务"
+
+msgid "reading transaction list (opening cursor)"
+msgstr "正在读取事务列表(游标已打开)"
+
+msgid "reading transaction list (listing keys)"
+msgstr "正在读取事务列表(正在枚举键)"
+
+msgid "reading transaction list (closing cursor)"
+msgstr "正在读取事务列表(游标已关闭)"
+
msgid "get repository uuid"
msgstr "获取版本库 UUID"
@@ -2102,6 +2285,84 @@
msgid "Berkeley DB error for filesystem '%s' while opening environment:\n"
msgstr "打开文件系统“%s”的BDB环境句柄时出错: \n"
+msgid "creating 'nodes' table"
+msgstr "正在创建“nodes”表"
+
+msgid "opening 'nodes' table"
+msgstr "正在打开“nodes”表"
+
+msgid "creating 'revisions' table"
+msgstr "正在创建“revisions”表"
+
+msgid "opening 'revisions' table"
+msgstr "正在打开“revisions”表"
+
+msgid "creating 'transactions' table"
+msgstr "正在创建“transactions”表"
+
+msgid "opening 'transactions' table"
+msgstr "正在打开“transactions”表"
+
+msgid "creating 'copies' table"
+msgstr "正在创建“copies”表"
+
+msgid "opening 'copies' table"
+msgstr "正在打开“copies”表"
+
+msgid "creating 'changes' table"
+msgstr "正在创建“changes”表"
+
+msgid "opening 'changes' table"
+msgstr "正在打开“changes”表"
+
+msgid "creating 'representations' table"
+msgstr "正在创建“representations”表"
+
+msgid "opening 'representations' table"
+msgstr "正在打开“representations”表"
+
+msgid "creating 'strings' table"
+msgstr "正在创建“strings”表"
+
+msgid "opening 'strings' table"
+msgstr "正在打开“strings”表"
+
+msgid "creating 'uuids' table"
+msgstr "正在创建“uuids”表"
+
+msgid "opening 'uuids' table"
+msgstr "正在打开“uuids”表"
+
+msgid "creating 'locks' table"
+msgstr "正在创建“locks”表"
+
+msgid "opening 'locks' table"
+msgstr "正在打开“locks”表"
+
+msgid "creating 'lock-tokens' table"
+msgstr "正在创建“lock-tokens”表"
+
+msgid "opening 'lock-tokens' table"
+msgstr "正在打开“lock-tokens”表"
+
+msgid "creating 'node-origins' table"
+msgstr "正在创建“node-origins”表"
+
+msgid "opening 'node-origins' table"
+msgstr "正在打开“node-origins”表"
+
+msgid "creating 'miscellaneous' table"
+msgstr "正在创建“miscellaneous”表"
+
+msgid "opening 'miscellaneous' table"
+msgstr "正在打开“miscellaneous”表"
+
+msgid "creating 'checksum-reps' table"
+msgstr "正在创建“checksum-reps”表"
+
+msgid "opening 'checksum-reps' table"
+msgstr "正在打开“checksum-reps”表"
+
#, c-format
msgid "The '%s' feature requires version %d of the filesystem schema; filesystem '%s' uses only version %d"
msgstr "“%s”特性需要文件系统方案版本 %d;文件系统“%s”版本仅为 %d"
@@ -2228,6 +2489,15 @@
msgid "Transaction aborted, but cleanup failed"
msgstr "事务终止,但清除失败"
+msgid "beginning Berkeley DB transaction"
+msgstr ""
+
+msgid "aborting Berkeley DB transaction"
+msgstr ""
+
+msgid "committing Berkeley DB transaction"
+msgstr ""
+
#, c-format
msgid "Failure opening '%s'"
msgstr "打开“%s”失败"
@@ -2301,6 +2571,10 @@
msgstr "缓存中 noderev 为空"
#, c-format
+msgid "Attempted to update ancestry of non-mutable node"
+msgstr "试图更新非可变节点的前驱"
+
+#, c-format
msgid "Can't fetch FSFS shared data"
msgstr "不能获取文件系统互斥体"
@@ -2344,8 +2618,8 @@
msgstr "不能获取文件“%s”的排它锁"
#, c-format
-msgid "Format file '%s' contains unexpected non-digit '%c' within '%s'"
-msgstr "格式文件“%s”包含意外的非数字字符 '%c' 在 '%s' 中"
+msgid "%s file '%s' contains unexpected non-digit '%c' within '%s'"
+msgstr "%s 文件“%s”包含意外的非数字字符 '%c' 在 '%s' 中"
#, c-format
msgid "Can't read first line of format file '%s'"
@@ -2368,6 +2642,10 @@
msgstr "'%s' 不是普通文件。请移除后重试"
#, c-format
+msgid "Can't read '%s'"
+msgstr "不能读取“%s”"
+
+#, c-format
msgid "Found malformed header '%s' in revision file"
msgstr "在版本文件中找到非法文件头“%s”"
@@ -2379,9 +2657,12 @@
msgid "No such revision %ld"
msgstr "没有版本 %ld"
+msgid "Unexpected EOF"
+msgstr "流意外结束"
+
#, c-format
-msgid "Manifest offset '%s' too large"
-msgstr "清单偏移“%s”太大"
+msgid "Number '%s' invalid or too large"
+msgstr ""
msgid "Malformed text representation offset line in node-rev"
msgstr "node-rev 中有非法的文本修订版偏移行"
@@ -2437,6 +2718,36 @@
msgid "Final line in revision file r%ld missing space"
msgstr "版本文件 (r%ld) 的最后一行缺少空白字符"
+#, c-format
+msgid "Packed revprop manifest for rev %ld too small"
+msgstr ""
+
+msgid "Header end not found"
+msgstr "找不到 Header 的结束标记"
+
+msgid "Packed revprop size exceeds pack file size"
+msgstr ""
+
+#, c-format
+msgid "No such packed revision %ld"
+msgstr "没有此打包版本 %ld"
+
+#, c-format
+msgid "Failed to read revprop pack file for rev %ld"
+msgstr "无法找到版本 %ld 打包的版本属性"
+
+#, c-format
+msgid "Revprop pack file for rev %ld is corrupt"
+msgstr ""
+
+#, c-format
+msgid "Could not read revprops for revision %ld"
+msgstr "无法找到版本 %ld 的版本属性"
+
+#, c-format
+msgid "Packed file '%s' misses a tag"
+msgstr ""
+
msgid "Malformed svndiff data in representation"
msgstr "修订版的 svndiff 数据非法"
@@ -2493,8 +2804,8 @@
msgstr "文件 “current” 损坏"
#, c-format
-msgid "predecessor count for the root node-revision is wrong: found %d, committing r%ld"
-msgstr "根节点版本的前驱计数错误:发现 %d,正在提交 r%ld"
+msgid "predecessor count for the root node-revision is wrong: found (%d+%ld != %d), committing r%ld"
+msgstr "根节点版本的前任计数错误: 发现 (%d+%ld != %d), 正在提交 r%ld"
msgid "Truncated protorev file detected"
msgstr "检测到文件 protorev 被截断"
@@ -2520,6 +2831,10 @@
msgstr "版本 %ld 有版本文件,但是没有版本属性文件"
#, c-format
+msgid "Revision %ld has a revs file but the revprops file is inaccessible"
+msgstr "版本 %ld 有版本文件,但是版本属性文件不可访问"
+
+#, c-format
msgid "Revision %ld has a non-file where its revprops file should be"
msgstr "版本 %ld 有非文件项目,与其属性文件的记录矛盾"
@@ -2601,10 +2916,6 @@
msgid "Lock failed: newer version of '%s' exists"
msgstr "加锁失败: “%s”的新版本已经存在"
-#, c-format
-msgid "Youngest revision is r%ld, but rep-cache contains r%ld"
-msgstr "最小的版本是 r%ld,但是缓存中包含的是 r%ld"
-
msgid "Couldn't open rep-cache database"
msgstr "无法打开缓存数据库"
@@ -2747,413 +3058,6 @@
msgid "Unable to open repository '%s'"
msgstr "无法打开版本库“%s”"
-msgid "Could not fetch the Version Resource URL (needed during an import or when it is missing from the local, cached props)"
-msgstr "不能获取版本资源URL(导入时需要或本地缓存属性中缺少)"
-
-#, c-format
-msgid "File or directory '%s' is out of date; try updating"
-msgstr "文件或目录 “%s” 已经过时;请先更新"
-
-msgid "The CHECKOUT response did not contain a 'Location:' header"
-msgstr "检出(CHECKOUT)响应没有包含“Location:”头信息"
-
-#, c-format
-msgid "Unable to parse URL '%s'"
-msgstr "不能解析 URL “%s”"
-
-#, c-format
-msgid "POST request did not return transaction information"
-msgstr "POST 请求没有返回事务的信息"
-
-#, c-format
-msgid "File '%s' already exists"
-msgstr "文件“%s”已存在"
-
-#, c-format
-msgid "Could not write svndiff to temp file"
-msgstr "无法将 svndiff 写入临时文件"
-
-#, c-format
-msgid "No lock on path '%s' (Status %d on PUT Request)"
-msgstr "路径“%s”没有被锁定 (PUT 请求的状态码是 %d)"
-
-msgid "Could not save the URL of the version resource"
-msgstr "无法储存版本资源的URL"
-
-msgid "Could not get content-type from response"
-msgstr "不能从响应中获得内容类型"
-
-msgid "Could not save file"
-msgstr "无法保存文件"
-
-msgid "Can't get text contents of a directory"
-msgstr "无法获得目录的文本内容"
-
-#, c-format
-msgid " expected: %s"
-msgstr " 期望: %s"
-
-#, c-format
-msgid " actual: %s"
-msgstr " 实际: %s"
-
-msgid "Server response missing the expected deadprop-count property"
-msgstr "服务器响应丢失了期望的 deadprop-count 属性"
-
-msgid "DAV request failed; it's possible that the repository's pre-revprop-change hook either failed or is non-existent"
-msgstr "DAV 请求失败;可能是版本库的 pre-revprop-change 钩子执行失败或者不存在"
-
-#, c-format
-msgid "Missing rev attr in target-revision element"
-msgstr "元素 target-revision 没有 rev 属性"
-
-#, c-format
-msgid "Missing name attr in absent-directory element"
-msgstr "元素 absent-directory 没有 name 属性"
-
-#, c-format
-msgid "Missing name attr in absent-file element"
-msgstr "元素 absent-file 没有 name 属性"
-
-#, c-format
-msgid "Missing path attr in resource element"
-msgstr "元素 resource 没有 path 属性"
-
-#, c-format
-msgid "Missing rev attr in open-directory element"
-msgstr "元素 open-directory 没有 rev 属性"
-
-#, c-format
-msgid "Missing name attr in open-directory element"
-msgstr "元素 open-directory 没有 name 属性"
-
-#, c-format
-msgid "Missing name attr in add-directory element"
-msgstr "元素 add-directory 没有 name 属性"
-
-#, c-format
-msgid "Missing copyfrom-rev attr in add-directory element"
-msgstr "元素 add-directory 没有 copyfrom-rev 属性"
-
-#, c-format
-msgid "Missing rev attr in open-file element"
-msgstr "元素 open-file 没有 rev 属性"
-
-#, c-format
-msgid "Missing name attr in open-file element"
-msgstr "元素 open-file 没有 name 属性"
-
-#, c-format
-msgid "Missing name attr in add-file element"
-msgstr "元素 add-file 没有 name 属性"
-
-#, c-format
-msgid "Missing copyfrom-rev attr in add-file element"
-msgstr "元素 add-file 没有 copyfrom-rev 属性"
-
-#, c-format
-msgid "Missing name attr in set-prop element"
-msgstr "元素 set-pro 没有 name 属性"
-
-#, c-format
-msgid "Missing name attr in remove-prop element"
-msgstr "元素 remove-prop 没有 name 属性"
-
-#, c-format
-msgid "Missing name attr in delete-entry element"
-msgstr "元素 delete-entry 没有 attr 属性"
-
-#, c-format
-msgid "Error writing to '%s': unexpected EOF"
-msgstr "写入 “%s” 时出错: 意外的 EOF"
-
-#, c-format
-msgid "Unknown XML encoding: '%s'"
-msgstr "未知的 XML 编码: “%s”"
-
-#, c-format
-msgid "REPORT response handling failed to complete the editor drive"
-msgstr "处理报告响应失败,不能完成编辑"
-
-msgid "Failed to write full amount to stream"
-msgstr "无法将全部数据写入流"
-
-#, c-format
-msgid "'%s' REPORT not implemented"
-msgstr "未实现 “%s” REPORT "
-
-msgid "The file-revs report didn't contain any revisions"
-msgstr "file-revs 报告中没有包含任何版本"
-
-msgid "Server does not support date-based operations"
-msgstr "服务器不支持基于日期的操作"
-
-msgid "Invalid server response to dated-rev request"
-msgstr "服务器请求 dated-rev 的响应无效"
-
-msgid "Expected valid revision range"
-msgstr "期望合法的版本范围"
-
-msgid "Expected a valid revnum and path"
-msgstr "期望合法的版本号和路径"
-
-msgid "Incomplete lock data returned"
-msgstr "返回了不完全的锁数据"
-
-#, c-format
-msgid "Got unrecognized encoding '%s'"
-msgstr "得到不能识别的编码“%s”"
-
-msgid "Server does not support locking features"
-msgstr "服务器不支持加锁"
-
-msgid "Invalid creation date header value in response."
-msgstr "在响应头中有非法创建日期"
-
-msgid "Invalid timeout value"
-msgstr "非法超时取值"
-
-#, c-format
-msgid "Failed to parse URI '%s'"
-msgstr "解析 URI “%s” 失败"
-
-#, c-format
-msgid "Lock request failed: %d %s"
-msgstr "加锁请求失败: %d %s"
-
-#, c-format
-msgid "'%s' is not locked in the repository"
-msgstr "版本库中的“%s”没有被锁定"
-
-#, c-format
-msgid "Unlock failed on '%s' (%d Forbidden)"
-msgstr "对 '%s' 解锁失败 (%d 禁止)"
-
-#, c-format
-msgid "No lock on path '%s' (%d Bad Request)"
-msgstr "路径“%s”没有被锁定 (%d 错误的请求)"
-
-msgid "Failed to fetch lock information"
-msgstr "获取锁信息失败"
-
-#, c-format
-msgid "Missing name attr in revprop element"
-msgstr "元素 revprop 没有 name 属性"
-
-msgid "Server does not support custom revprops via log"
-msgstr "服务器不支持通过日志自定义 revprops"
-
-#, c-format
-msgid "Protocol error: we told the server not to auto-merge any resources, but it said that '%s' was merged"
-msgstr "通讯协议错误: 我们让服务器不要自动合并任何资源,但是它报告“%s”已被合并"
-
-#, c-format
-msgid "Internal error: there is an unknown parent (%d) for the 'DAV:response' element within the MERGE response"
-msgstr "内部错误: 在 MERGE 响应中,“DAV:response”元素有未知的父项 (%d)"
-
-#, c-format
-msgid "Protocol error: the MERGE response for the '%s' resource did not return all of the properties that we asked for (and need to complete the commit)"
-msgstr "通讯协议错误: 对“%s”的 MERGE 响应并未返回所有我们请求的属性 (这些是完成提交所需要的)"
-
-#, c-format
-msgid "A MERGE response for '%s' is not a child of the destination ('%s')"
-msgstr "“%s”的 MERGE 响应并不是目标 (“%s”) 的子项"
-
-msgid "The MERGE property response had an error status"
-msgstr "MERGE 属性响应包含错误状态"
-
-msgid "The OPTIONS response did not include the requested activity-collection-set; this often means that the URL is not WebDAV-enabled"
-msgstr "OPTIONS 响应没有包含请求的 activity-collection-set;通常是此 URL 没有启用 WebDAV。"
-
-#, c-format
-msgid "Don't know how to handle '%s' for capability '%s'"
-msgstr "不知道如何处理“%s”,对于特性“%s”"
-
-#, c-format
-msgid "Attempt to fetch capability '%s' resulted in '%s'"
-msgstr "试图取得特性 “%s” 导致 “%s”"
-
-#, c-format
-msgid "Failed to find label '%s' for URL '%s'"
-msgstr "无法找到标签 “%s” 于 URL “%s”"
-
-#, c-format
-msgid "'%s' was not present on the resource '%s'"
-msgstr "'%s' 不在资源 '%s' 中"
-
-#, c-format
-msgid "Neon was unable to parse URL '%s'"
-msgstr "Neon 不能解析URL“%s”"
-
-msgid "The path was not part of a repository"
-msgstr "路径不是版本库的一部分"
-
-#, c-format
-msgid "No part of path '%s' was found in repository HEAD"
-msgstr "路径“%s”的任何部分都不在版本库的 HEAD 中"
-
-msgid "The VCC property was not found on the resource"
-msgstr "资源没有 VCC 属性"
-
-msgid "The relative-path property was not found on the resource"
-msgstr "资源没有 relative-path 属性"
-
-msgid "The OPTIONS response did not include the youngest revision"
-msgstr "OPTIONS 响应中没有包含最新的版本"
-
-#, c-format
-msgid "Url '%s' is not in repository '%s'"
-msgstr "URL '%s' 不在版本库 '%s' 中"
-
-msgid "'DAV:baseline-collection' was not present on the baseline resource"
-msgstr "基线资源没有“DAV:baseline-collection”"
-
-#, c-format
-msgid "'%s' was not present on the baseline resource"
-msgstr "“%s” 不在基线资源中"
-
-#, c-format
-msgid "No lock on path '%s'; repository is unchanged"
-msgstr "路径 '%s' 上没有锁;版本库未改变"
-
-msgid "At least one property change failed; repository is unchanged"
-msgstr "至少有一个属性变更失败;版本库未改变"
-
-msgid "Got apply-textdelta element without preceding add-file or open-file"
-msgstr "元素 apply-textdelta 之前没有 add-file 或者 open-file 元素"
-
-msgid "Got close-file element without preceding add-file or open-file"
-msgstr "元素close-file之前没有add-file或者open-file元素"
-
-msgid "Got close-directory element without ever opening a directory"
-msgstr "在没有打开目录的情况下发现了close-directory元素"
-
-#, c-format
-msgid "Error writing stream: unexpected EOF"
-msgstr "写入流出错: 意外的 EOF"
-
-#, c-format
-msgid "Got cdata content for a prop delete"
-msgstr "删除属性时遇到cdata内容"
-
-#, c-format
-msgid "PIN for token \"%s\" in slot \"%s\""
-msgstr "钉住令牌“%s”于通道“%s”"
-
-msgid "Invalid URL: illegal character in proxy port number"
-msgstr "无效URL: 代理端口中有非法字符"
-
-msgid "Invalid URL: negative proxy port number"
-msgstr "无效URL: 负的代理端口"
-
-msgid "Invalid URL: proxy port number greater than maximum TCP port number 65535"
-msgstr "无效URL: 代理端口大于TCP通信端口最大值65535"
-
-msgid "Invalid config: illegal character in timeout value"
-msgstr "无效配置: 超时数值中有非法字符"
-
-msgid "Invalid config: negative timeout value"
-msgstr "无效配置: 负的超时"
-
-msgid "Invalid config: illegal character in debug mask value"
-msgstr "无效配置: 调试掩码中有非法字符"
-
-#, c-format
-msgid "Invalid config: unknown http authtype '%s'"
-msgstr "无效配置: 未知的 http 认证类型“%s”"
-
-msgid "Module for accessing a repository via WebDAV protocol using Neon."
-msgstr "通过 WebDAV 协议使用 neon 访问版本库的模块。"
-
-#, c-format
-msgid "URL '%s' is malformed or the scheme or host or path is missing"
-msgstr "非法 URL “%s”,缺少方案,主机或路径"
-
-msgid "Network socket initialization failed"
-msgstr "创建网络套接字失败"
-
-msgid "SSL is not supported"
-msgstr "不支持 SSL"
-
-#, c-format
-msgid "Invalid config: unable to load certificate file '%s'"
-msgstr "无效的配置: 无法加载证书文件“%s”"
-
-#, c-format
-msgid "Invalid config: unable to load PKCS#11 provider '%s'"
-msgstr "无效的配置: 无法加载 PKCS#11 提供者“%s”"
-
-msgid "The UUID property was not found on the resource or any of its parents"
-msgstr "无法在本资源或其任何父项目中找到UUID属性"
-
-#, c-format
-msgid "Unsupported RA loader version (%d) for ra_neon"
-msgstr "不支持的 ra_neon RA 加载器版本(%d)"
-
-msgid "The request response contained at least one error"
-msgstr "请求的响应至少有一个错误"
-
-msgid "The response contains a non-conforming HTTP status line"
-msgstr "响应包含了不一致的HTTP状态行"
-
-#, c-format
-msgid "Error setting property '%s': "
-msgstr "设置属性 “%s” 出错: "
-
-#, c-format
-msgid "%s of '%s'"
-msgstr "方法 %s 失败于 “%s”"
-
-#, c-format
-msgid "Access to '%s' forbidden"
-msgstr "禁止访问 '%s'"
-
-#, c-format
-msgid "Repository moved permanently to '%s'; please relocate"
-msgstr "版本库永久移到 “%s”;请重新定位。"
-
-#, c-format
-msgid "Repository moved temporarily to '%s'; please relocate"
-msgstr "版本库临时移到‘%s’;请重新定位。"
-
-#, c-format
-msgid "Server sent unexpected return value (%d %s) in response to %s request for '%s'"
-msgstr "服务器发送了意外的返回值(%d %s),在响应 “%s” 的请求 “%s” 中"
-
-#, c-format
-msgid "authorization failed: %s"
-msgstr "认证失败: %s"
-
-msgid "authorization failed"
-msgstr "认证失败"
-
-msgid "could not connect to server"
-msgstr "无法连接到服务器"
-
-msgid "timed out waiting for server"
-msgstr "等待服务器超时"
-
-#. ### This is a translation nightmare. Make sure to compose full strings
-#. and mark those for translation.
-#, c-format
-msgid "%s: %s (%s://%s)"
-msgstr "%s: %s (%s://%s)"
-
-msgid "General svn error from server"
-msgstr "来自服务器的一般 svn 错误"
-
-#, c-format
-msgid "Can't calculate the request body size"
-msgstr "无法计算请求主体大小"
-
-#, c-format
-msgid "The %s request returned invalid XML in the response: %s (%s)"
-msgstr "%s 请求的响应返回无效 XML: %s (%s)"
-
-#, c-format
-msgid "Error reading spooled %s request response"
-msgstr "读取请求 %s 响应的脱机数据时出现错误"
-
#, c-format
msgid "%s of '%s': %d %s"
msgstr "%s 于 '%s': %d %s"
@@ -3173,6 +3077,9 @@
msgid "File '%s' is out of date; try updating"
msgstr "文件 “%s” 已经过时;请先更新"
+msgid "At least one property change failed; repository is unchanged"
+msgstr "至少有一个属性变更失败;版本库未改变"
+
#, c-format
msgid "Failed writing updated file"
msgstr "更新文件失败"
@@ -3181,20 +3088,54 @@
msgid "%s of '%s': %d %s (%s://%s)"
msgstr "%s 于 '%s': %d %s (%s://%s)"
+#, c-format
+msgid "POST request did not return transaction information"
+msgstr "POST 请求没有返回事务的信息"
+
msgid "The OPTIONS response did not include the requested activity-collection-set value"
msgstr "OPTIONS 响应中没有包含请求的 activity-collection-set 值。"
#, c-format
+msgid "Unable to parse URL '%s'"
+msgstr "不能解析 URL “%s”"
+
+#, c-format
+msgid "Access to '%s' forbidden"
+msgstr "禁止访问 '%s'"
+
+#, c-format
msgid "Adding directory failed: %s on %s (%d %s)"
msgstr "增加目录失败: “%s” 于 “%s”(%d %s)"
#, c-format
-msgid "Location segment report failed on '%s'@'%ld'"
-msgstr "位置分段报告失败于“%s”@“%ld”"
+msgid "File '%s' already exists"
+msgstr "文件“%s”已存在"
+
+#, c-format
+msgid "MERGE request failed: returned %d (during commit)"
+msgstr ""
+
+msgid "DAV request failed; it's possible that the repository's pre-revprop-change hook either failed or is non-existent"
+msgstr "DAV 请求失败;可能是版本库的 pre-revprop-change 钩子执行失败或者不存在"
+
+#, c-format
+msgid "'%s' REPORT not implemented"
+msgstr "未实现 “%s” REPORT "
+
+#, c-format
+msgid "Lock request failed: %d %s"
+msgstr "加锁请求失败: %d %s"
msgid "Malformed URL for repository"
msgstr "版本库的 URL 非法"
+msgid "Server does not support locking features"
+msgstr "服务器不支持加锁"
+
+#, c-format
+msgid "'%s' is not locked in the repository"
+msgstr "版本库中的“%s”没有被锁定"
+
#, c-format
msgid "Unlock request failed: %d %s"
msgstr "解锁请求失败: %d %s"
@@ -3203,6 +3144,28 @@
msgid "Unsupported encoding '%s'"
msgstr "不支持的编码“%s”"
+msgid "Server does not support custom revprops via log"
+msgstr "服务器不支持通过日志自定义 revprops"
+
+#, c-format
+msgid "A MERGE response for '%s' is not a child of the destination ('%s')"
+msgstr "“%s”的 MERGE 响应并不是目标 (“%s”) 的子项"
+
+#, c-format
+msgid "Don't know how to handle '%s' for capability '%s'"
+msgstr "不知道如何处理“%s”,对于特性“%s”"
+
+#, c-format
+msgid "Attempt to fetch capability '%s' resulted in '%s'"
+msgstr "试图取得特性 “%s” 导致 “%s”"
+
+#, c-format
+msgid "Got unrecognized encoding '%s'"
+msgstr "得到不能识别的编码“%s”"
+
+msgid "The PROPFIND response did not include the requested properties"
+msgstr "PROPFIND 响应中没有包含请求的属性"
+
msgid "The PROPFIND response did not include the requested baseline-collection value"
msgstr "PROPFIND 响应中没有包含请求的 baseline-collection 值。"
@@ -3212,40 +3175,94 @@
msgid "The OPTIONS response did not include the requested checked-in value"
msgstr "OPTIONS 响应中没有包含请求的 checked-in 值。"
+msgid "The OPTIONS response did not include the youngest revision"
+msgstr "OPTIONS 响应中没有包含最新的版本"
+
msgid "The PROPFIND response did not include the requested resourcetype value"
msgstr "PROPFIND 响应中没有包含请求的 resourcetype 值"
+msgid "The PROPFIND response did not include the requested 'DAV:' properties"
+msgstr "PROPFIND 响应中没有包含请求的 'DAV:' 属性"
+
msgid "Missing revision attr in target-revision element"
msgstr "元素“target-revision没有revision属性"
msgid "Missing revision attr in open-root element"
msgstr "元素“open-root没有revisioin属性"
+msgid "Missing name attr in delete-entry element"
+msgstr "元素 delete-entry 没有 attr 属性"
+
msgid "Missing revision attr in delete-entry element"
msgstr "delete-entry元素没有revision属性"
+msgid "Missing name attr in open-directory element"
+msgstr "元素 open-directory 没有 name 属性"
+
msgid "Missing revision attr in open-directory element"
msgstr "open-directory元素没有revision属性"
+msgid "Missing name attr in add-directory element"
+msgstr "元素 add-directory 没有 name 属性"
+
+msgid "Missing name attr in open-file element"
+msgstr "元素 open-file 没有 name 属性"
+
msgid "Missing revision attr in open-file element"
msgstr "open-file元素没有revision属性"
+msgid "Missing name attr in add-file element"
+msgstr "元素 add-file 没有 name 属性"
+
#, c-format
msgid "Missing name attr in %s element"
msgstr "元素“%s”没有name属性"
+msgid "Error writing stream: unexpected EOF"
+msgstr "写入流出错: 意外的 EOF"
+
#, c-format
msgid "Error retrieving replay REPORT (%d)"
msgstr "获取重放报告出错(%d)"
+#, c-format
+msgid "Failed to read the request"
+msgstr "读取请求失败"
+
msgid "Module for accessing a repository via WebDAV protocol using serf."
msgstr "通过 WebDAV 协议使用 serf 访问版本库的模块。"
#, c-format
+msgid "Invalid config: unknown http authtype '%s'"
+msgstr "无效配置: 未知的 http 认证类型“%s”"
+
+msgid "Invalid config: illegal character in timeout value"
+msgstr "无效配置: 超时数值中有非法字符"
+
+msgid "Invalid config: negative timeout value"
+msgstr "无效配置: 负的超时"
+
+msgid "Invalid URL: illegal character in proxy port number"
+msgstr "无效URL: 代理端口中有非法字符"
+
+msgid "Invalid URL: negative proxy port number"
+msgstr "无效URL: 负的代理端口"
+
+msgid "Invalid URL: proxy port number greater than maximum TCP port number 65535"
+msgstr "无效URL: 代理端口大于TCP通信端口最大值65535"
+
+#, c-format
msgid "Could not resolve proxy server '%s'"
msgstr "不能解析代理服务器“%s”"
#, c-format
+msgid "Illegal URL '%s'"
+msgstr "非法 URL “%s”"
+
+msgid "The UUID property was not found on the resource or any of its parents"
+msgstr "无法在本资源或其任何父项目中找到UUID属性"
+
+#, c-format
msgid "Unsupported RA loader version (%d) for ra_serf"
msgstr "不支持的 ra_serf RA 加载器版本(%d)"
@@ -3257,6 +3274,12 @@
msgid "GET request failed: %d %s"
msgstr "GET 请求失败: %d %s"
+msgid "Missing name attr in absent-directory element"
+msgstr "元素 absent-directory 没有 name 属性"
+
+msgid "Missing name attr in absent-file element"
+msgstr "元素 absent-file 没有 name 属性"
+
#, c-format
msgid "Unknown tag '%s' while at state %d"
msgstr "未知标签“%s”,上下文状态 %d"
@@ -3265,6 +3288,13 @@
msgid "Error retrieving REPORT (%d)"
msgstr "获取报告出错(%d)"
+msgid "Can't get text contents of a directory"
+msgstr "无法获得目录的文本内容"
+
+#, c-format
+msgid "Invalid config: unable to load certificate file '%s'"
+msgstr "无效的配置: 无法加载证书文件“%s”"
+
#, c-format
msgid "Error running context"
msgstr "执行上下文错误"
@@ -3282,7 +3312,7 @@
msgstr "XML 解析失败: (%d %s)"
msgid ""
-"No more credentials or we tried too many times.\n"
+"No more credentials or we tried too manytimes.\n"
"Authentication failed"
msgstr ""
"没有凭证或已经试了太多次。\n"
@@ -3303,10 +3333,29 @@
msgstr "PROPFIND 响应中没有包含请求的 version-controlled-configuration 值。"
#, c-format
+msgid "Repository moved permanently to '%s'; please relocate"
+msgstr "版本库永久移到 “%s”;请重新定位。"
+
+#, c-format
+msgid "Repository moved temporarily to '%s'; please relocate"
+msgstr "版本库临时移到‘%s’;请重新定位。"
+
+#, c-format
msgid "'%s': no lock token available"
msgstr "“%s”:没有可用的锁定令牌"
#, c-format
+msgid "The %s response contains invalid XML (%d %s)"
+msgstr "响应 %s 包含非法 XML (%d %s)"
+
+#, c-format
+msgid "Missing XML attribute: '%s'"
+msgstr ""
+
+msgid "The response contains invalid XML"
+msgstr "此响应包含非法 XML"
+
+#, c-format
msgid "Unknown hostname '%s'"
msgstr "未知的主机“%s”"
@@ -3402,6 +3451,9 @@
msgid "Location segment entry not a list"
msgstr "位置分段入口不是列表"
+msgid "Expected valid revision range"
+msgstr "期望合法的版本范围"
+
msgid "'get-file-revs' not implemented"
msgstr "未实现“get-file-revs”"
@@ -3594,6 +3646,9 @@
"提交后的文件系统进程出错:\n"
"%s"
+msgid "Commit succeeded, but post-commit hook failed"
+msgstr "提交成功,但是后置提交钩子(post-commit)失败"
+
msgid "Unable to open root of edit"
msgstr "无法打开根来编辑"
@@ -3691,9 +3746,6 @@
msgid "Unexpected node kind %d for '%s'"
msgstr "意外发现节点种类 %d 于“%s”"
-msgid "Commit succeeded, but post-commit hook failed"
-msgstr "提交成功,但是后置提交钩子(post-commit)失败"
-
#, c-format
msgid "Storage of non-regular property '%s' is disallowed through the repository interface, and could indicate a bug in your client"
msgstr "不允许通过版本库接口存储非正规属性“%s”,它说明您的客户端有漏洞"
@@ -3774,10 +3826,6 @@
msgstr "启动钩子“%s”失败"
#, c-format
-msgid "Error closing read end of stderr pipe"
-msgstr "关闭读标准错误管道出错"
-
-#, c-format
msgid "Error closing null file"
msgstr "关闭空文件出错"
@@ -3981,12 +4029,28 @@
msgid "Invalid config: unknown password store '%s'"
msgstr "无效配置: 未知的密码存储 “%s”"
+#, c-format
+msgid "Can't lock cache mutex"
+msgstr "不能锁定缓存互斥体"
+
+#, c-format
+msgid "Can't write-lock cache mutex"
+msgstr "不能获得缓存互斥体的写入锁"
+
+#, c-format
+msgid "Can't unlock cache mutex"
+msgstr "不能解锁缓存互斥体"
+
#. We are OOM. There is no need to proceed with "half a cache".
#.
#, c-format
msgid "OOM"
msgstr "内存不足"
+#, c-format
+msgid "Can't create cache mutex"
+msgstr "无法创建缓存互斥体"
+
msgid "Can't iterate a membuffer-based cache"
msgstr "不能迭代基于内存的缓存"
@@ -4076,6 +4140,61 @@
msgstr "写哈希数据至“%s”时出错"
#, c-format
+msgid "Failed to initialize cryptography subsystem"
+msgstr ""
+
+#, c-format
+msgid "code (%d), reason (\"%s\"), msg (\"%s\")"
+msgstr ""
+
+#, c-format
+msgid "Fetching error from APR"
+msgstr ""
+
+#, c-format
+msgid "Error obtaining random data"
+msgstr "获得随机数据失败"
+
+msgid "OpenSSL crypto driver error"
+msgstr ""
+
+msgid "Bad return value while loading crypto driver"
+msgstr ""
+
+msgid "Error creating OpenSSL crypto context"
+msgstr "创建 OpenSSL 加密上下文失败"
+
+msgid "Error creating derived key"
+msgstr "创建衍生密钥失败"
+
+msgid "Unexpected IV length returned"
+msgstr "返回了意外的 IV 长度"
+
+msgid "Error initializing block encryption"
+msgstr "初始化块加密失败"
+
+msgid "Error fetching result length"
+msgstr "获得结果的长度失败"
+
+msgid "Error during block encryption"
+msgstr "在块加密期间出错"
+
+msgid "Error finalizing block encryption"
+msgstr "在终结块加密时出错"
+
+msgid "Provided IV has incorrect length"
+msgstr "提供的 IV 的长度不正确"
+
+msgid "Error initializing block decryption"
+msgstr "初始化块解密失败"
+
+msgid "Error during block decryption"
+msgstr "在块解密期间出错"
+
+msgid "Error finalizing block decryption"
+msgstr "在终结块解密时出错"
+
+#, c-format
msgid "Can't manipulate current date"
msgstr "不能获得当前日期"
@@ -4249,6 +4368,10 @@
msgstr "不能获取文件 “%s” 的共享锁"
#, c-format
+msgid "Can't unlock file '%s'"
+msgstr "无法解锁文件“%s”"
+
+#, c-format
msgid "Can't flush file '%s'"
msgstr "不能刷新文件 “%s”"
@@ -4534,6 +4657,34 @@
msgstr "空合并信息\n"
#, c-format
+msgid "Can't create mutex"
+msgstr "无法创建互斥体"
+
+#, c-format
+msgid "Can't lock mutex"
+msgstr "不能锁定互斥体"
+
+#, c-format
+msgid "Can't unlock mutex"
+msgstr "不能解锁互斥体"
+
+msgid "Not a valid atomic"
+msgstr ""
+
+#, c-format
+msgid "Can't get shared memory for named atomics"
+msgstr "不能为命名原子获得共享内存"
+
+msgid "Atomic's name is too long."
+msgstr "原子的名称太长"
+
+msgid "Namespace has not been initialized."
+msgstr "命名空间没有初始化"
+
+msgid "Out of slots for named atomic."
+msgstr "命名原子的槽位已满"
+
+#, c-format
msgid "Can't convert string to UCS-2: '%s'"
msgstr "不能把字符串转换为 UCS-2: “%s”。"
@@ -4584,11 +4735,11 @@
#, c-format
msgid ""
"%s, version %s\n"
-" compiled %s, %s\n"
+" compiled %s, %s on %s\n"
"\n"
msgstr ""
"%s,版本 %s\n"
-" 编译于 %s,%s\n"
+" 编译于 %s,%s 在 %s\n"
"\n"
msgid ""
@@ -4603,6 +4754,28 @@
"Subversion 是开放源代码软件,请参阅 http://subversion.apache.org/ 站点。\n"
"\n"
+msgid ""
+"System information:\n"
+"\n"
+msgstr ""
+"系统信息:\n"
+"\n"
+
+#, c-format
+msgid ""
+"* running on %s\n"
+" - %s\n"
+msgstr ""
+"* 运行于 %s\n"
+" - %s\n"
+
+#, fuzzy, c-format
+msgid "* running on %s\n"
+msgstr "* 运行于 %s\n"
+
+msgid "* loaded shared libraries:\n"
+msgstr "* 已加载的共享库:\n"
+
#, c-format
msgid "Can't determine the native path encoding"
msgstr "不能确定本地路径编码"
@@ -4787,32 +4960,12 @@
msgstr "SQLite 由于 %s 导致热复制失败"
#, c-format
-msgid "File '%s' has inconsistent newlines"
-msgstr "文件 “%s” 内的换行符不一致"
-
-#, c-format
-msgid "Can't create mutex"
-msgstr "无法创建互斥体"
-
-#, c-format
-msgid "Can't lock mutex"
-msgstr "不能锁定互斥体"
-
-#, c-format
-msgid "Can't unlock mutex"
-msgstr "不能解锁互斥体"
-
-#, c-format
msgid "Could not convert '%s' into a number"
msgstr "无法转换 '%s' 到数字"
#, c-format
-msgid "Invalid revision number found parsing '%s'"
-msgstr "解析 “%s” 时发现无效的版本号"
-
-#, c-format
-msgid "Negative revision number found parsing '%s'"
-msgstr "解析 “%s” 时发现负的版本号"
+msgid "File '%s' has inconsistent newlines"
+msgstr "文件 “%s” 内的换行符不一致"
#. Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000"
msgid " (%a, %d %b %Y)"
@@ -4823,6 +4976,14 @@
msgstr "无法识别令牌 “%s”"
#, c-format
+msgid "Invalid revision number found parsing '%s'"
+msgstr "解析 “%s” 时发现无效的版本号"
+
+#, c-format
+msgid "Negative revision number found parsing '%s'"
+msgstr "解析 “%s” 时发现负的版本号"
+
+#, c-format
msgid "Can't create a character converter from native encoding to '%s'"
msgstr "不能创建从本地编码到“%s”的字符转换器"
@@ -4988,9 +5149,33 @@
msgid "Log format too old, please use Subversion 1.6 or earlier"
msgstr "日志格式太旧,请使用 Subversion 1.6 或更新的版本"
+#, fuzzy
+msgid "Not a conflict skel"
+msgstr "解析树冲突骨架出错"
+
+#, fuzzy
+msgid "Not a completed conflict skel"
+msgstr "解析树冲突骨架出错"
+
+#, fuzzy
+msgid "Conflict not set"
+msgstr "在“%s”冲突"
+
+msgid "Conflict callback violated API: returned no results."
+msgstr "冲突的回调函数违反 API:没有返回结果。"
+
+msgid "Conflict callback violated API: returned no merged file."
+msgstr "冲突的回调函数违反 API:没有返回合并文件。"
+
+msgid "Conflict callback violated API: returned no results"
+msgstr "冲突的回调函数违反 API:没有返回结果。"
+
msgid "Invalid 'conflict_result' argument"
msgstr "无效的 “conflict_result” 参数"
+msgid "No conflict-callback and no pre-defined conflict-choice provided"
+msgstr ""
+
#, c-format
msgid "Tree conflicts can only be resolved to 'working' state; '%s' not resolved"
msgstr "树冲突只能解决为 “working” 状态;“%s” 还没有解决"
@@ -5016,10 +5201,6 @@
msgstr "已经删除的节点“%s”不能被复制。"
#, c-format
-msgid "The node '%s' was not found."
-msgstr "找不到节点 '%s'。"
-
-#, c-format
msgid "Cannot copy to '%s', as it is not from repository '%s'; it is from '%s'"
msgstr "无法复制到“%s”,因为它不在版本库“%s”中;它来自“%s”"
@@ -5193,11 +5374,17 @@
msgid "Can't obtain lock on non-directory '%s'."
msgstr "不能获取非目录对象 '%s' 的锁"
-msgid "Conflict callback violated API: returned no results"
-msgstr "冲突的回调函数违反 API:没有返回结果。"
+#, fuzzy, c-format
+msgid "Can't merge into conflicted node '%s'"
+msgstr "无法写入文件“%s”"
-msgid "Conflict callback violated API: returned no merged file"
-msgstr "冲突的回调函数违反 API:没有返回合并文件。"
+#, c-format
+msgid "The property '%s' may not be merged into '%s'."
+msgstr "属性 “%s” 不能合并到 “%s”。"
+
+#, fuzzy, c-format
+msgid "Incomplete copy information on path '%s'."
+msgstr "复制源路径“%s”无效"
#, c-format
msgid "'%s' is not the root of the working copy '%s'"
@@ -5280,10 +5467,6 @@
msgstr "节点 '%s' 没有属性。"
#, c-format
-msgid "The property '%s' may not be merged into '%s'."
-msgstr "属性 “%s” 不能合并到 “%s”。"
-
-#, c-format
msgid ""
"Trying to add new property '%s'\n"
"but the property already exists.\n"
@@ -5364,12 +5547,6 @@
msgid "Incoming property value:\n"
msgstr "外部属性取值:\n"
-msgid "Conflict callback violated API: returned no results."
-msgstr "冲突的回调函数违反 API:没有返回结果。"
-
-msgid "Conflict callback violated API: returned no merged file."
-msgstr "冲突的回调函数违反 API:没有返回合并文件。"
-
#, c-format
msgid "Property '%s' is an entry property"
msgstr "属性 “%s” 是条目属性"
@@ -5386,20 +5563,16 @@
msgstr "无法设定“%s”于文件 (“%s”)"
#, c-format
-msgid "'%s' is not a file or directory"
-msgstr "“%s”不是文件或目录"
-
-#, c-format
msgid "File '%s' has binary mime type property"
msgstr "文件“%s”有二进制的 mime 类型属性"
+msgid "Failed to load current properties"
+msgstr "加载当前属性失败"
+
#, c-format
msgid "Can't set properties on '%s': invalid status for updating properties."
msgstr "不能对 '%s' 设置属性: 对于更新属性,状态非法。"
-msgid "Failed to load current properties"
-msgstr "加载当前属性失败"
-
#, c-format
msgid "Unrecognized line ending style '%s' for '%s'"
msgstr "无法识别的行结束样式“%s”,对于“%s”"
@@ -5462,10 +5635,6 @@
" 实际: %s\n"
#, c-format
-msgid "Path '%s' is not in the working copy"
-msgstr "路径 “%s” 不在工作副本中"
-
-#, c-format
msgid "Failed to add directory '%s': object of the same name as the administrative directory"
msgstr "无法增加目录“%s”: 对象与管理目录同名"
@@ -5590,9 +5759,13 @@
msgid "Cannot delete '%s' as '%s' is excluded by server"
msgstr "不能删除 '%s',因为 '%s' 被服务器排除"
-#, c-format
-msgid "Cannot move '%s' to '%s' because they are not in the same working copy"
-msgstr "不能将 '%s' 移到 '%s',因为它们不在同一工作副本中"
+#, fuzzy, c-format
+msgid "Cannot delete '%s' as it is excluded by server"
+msgstr "不能删除 '%s',因为 '%s' 被服务器排除"
+
+#, fuzzy, c-format
+msgid "Cannot delete '%s' as it is excluded"
+msgstr "不能删除 '%s',因为 '%s' 被服务器排除"
#, c-format
msgid "Corrupt data for '%s'"
@@ -5614,6 +5787,10 @@
msgid "Expected node '%s' to be added."
msgstr "期望节点“%s”被增加。"
+#, fuzzy, c-format
+msgid "The base node '%s' was not found."
+msgstr "找不到节点 '%s'。"
+
#, c-format
msgid "Expected node '%s' to be deleted."
msgstr "期望节点“%s”被删除。"
@@ -5650,14 +5827,6 @@
msgid "Modification of '%s' already exists"
msgstr "“%s” 的修改已存在"
-#, c-format
-msgid "Invalid conflict file '%s' for '%s'"
-msgstr "非法的冲突文件“%s”于“%s”"
-
-#, c-format
-msgid "Invalid property reject file '%s' for '%s'"
-msgstr "非法的属性拒绝文件“%s”于“%s”"
-
#. If EXPRESSION is false, cause the caller to return an SVN_ERR_WC_CORRUPT
#. * error, showing EXPRESSION and the caller's LOCAL_RELPATH in the message.
#, c-format
@@ -5696,6 +5865,10 @@
msgid "Missing a row in WCROOT."
msgstr "WCROOT 中丢失了一行。"
+#, fuzzy, c-format
+msgid "Working copy database '%s' not found"
+msgstr "工作副本“%s”未被锁定"
+
#, c-format
msgid "Working copy format of '%s' is too old (%d); please check out your working copy again"
msgstr "“%s”的工作副本格式太旧(%d);请重新取出工作副本"
@@ -5803,10 +5976,12 @@
"非法选项;没有已合并的版本来编辑。\n"
"\n"
-msgid "No merge tool found.\n"
+#, fuzzy
+msgid "No merge tool found, try '(m) merge' instead.\n"
msgstr "没有发现合并工具。\n"
-msgid "Error running merge tool."
+#, fuzzy
+msgid "Error running merge tool, try '(m) merge' instead."
msgstr "执行合并工具错误。"
msgid "No editor found; leaving all conflicts."
@@ -5821,8 +5996,8 @@
msgid "Error running merge tool; leaving all conflicts."
msgstr "执行合并工具错误;不处理冲突。"
-#, c-format
-msgid "Conflict discovered in '%s'.\n"
+#, fuzzy, c-format
+msgid "Conflict discovered in file '%s'.\n"
msgstr "在 “%s” 中发现冲突。\n"
#, c-format
@@ -5840,7 +6015,8 @@
msgid "Select: (p) postpone"
msgstr "选择: (p) 推迟"
-msgid ", (df) diff-full, (e) edit"
+#, fuzzy
+msgid ", (df) diff-full, (e) edit, (m) merge"
msgstr ",(df) 显示全部差异,(e) 编辑"
msgid ", (r) resolved"
@@ -5863,7 +6039,7 @@
msgid "(s) show all options: "
msgstr "(s) 显示全部选项: "
-#, c-format
+#, fuzzy, c-format
msgid ""
"\n"
" (e) edit - change merged file in an editor\n"
@@ -5878,6 +6054,7 @@
" (tf) theirs-full - accept their version of entire file (same)\n"
"\n"
" (p) postpone - mark the conflict to be resolved later\n"
+" (m) merge - use internal merge tool to resolve conflict\n"
" (l) launch - launch external tool to resolve conflict\n"
" (s) show all - show this list\n"
"\n"
@@ -5947,9 +6124,9 @@
"非法选项;没有已合并的版本来比较差异。\n"
"\n"
-#, c-format
+#, fuzzy, c-format
msgid ""
-"Invalid option; cannot resolve property conflicts with an external merge tool.\n"
+"Invalid option; can only resolve text conflicts with the internal merge tool.\n"
"\n"
msgstr ""
"非法选项;不能使用外部合并工具来解决属性冲突。\n"
@@ -5965,6 +6142,14 @@
#, c-format
msgid ""
+"Invalid option; cannot resolve property conflicts with an external merge tool.\n"
+"\n"
+msgstr ""
+"非法选项;不能使用外部合并工具来解决属性冲突。\n"
+"\n"
+
+#, c-format
+msgid ""
"Conflict discovered when trying to add '%s'.\n"
"An object of the same name already exists.\n"
msgstr ""
@@ -5988,6 +6173,22 @@
" (h) 帮助 - 显示这个列表\n"
"\n"
+#, fuzzy, c-format
+msgid ""
+"Tree conflict on '%s'\n"
+" > %s\n"
+msgstr " 树冲突:%u\n"
+
+#, fuzzy
+msgid "Select: (p) postpone, (r) mark-resolved, (h) help: "
+msgstr "选择:(p) 推迟,(mf) 全用我的,(tf) 全用他人的,(h) 帮助:"
+
+#, c-format
+msgid ""
+" (p) postpone - resolve the conflict later\n"
+" (r) resolved - accept current working tree\n"
+msgstr ""
+
#, c-format
msgid "'%s': a peg revision is not allowed here"
msgstr "'%s': 此处不允许铆钉版本范围"
@@ -6011,6 +6212,58 @@
msgid "Failure occurred processing one or more externals definitions"
msgstr "处理一个或更多的外部定义失败"
+#, fuzzy
+msgid "Could not write data to merged file"
+msgstr "无法将 svndiff 写入临时文件"
+
+#, fuzzy
+msgid "Could not write data to temporary file"
+msgstr "无法将 svndiff 写入临时文件"
+
+msgid "Conflicting section found during merge."
+msgstr ""
+
+#, c-format
+msgid "(1) their version (at line %lu)"
+msgstr ""
+
+#, c-format
+msgid "(2) your version (at line %lu)"
+msgstr ""
+
+msgid ""
+"Select: (1) use their version, (2) use your version,\n"
+" (e1) edit their version and use the result,\n"
+" (e2) edit your version and use the result,\n"
+" (eb) edit both versions and use the result,\n"
+" (p) postpone this conflicting section leaving conflict markers,\n"
+" (a) abort file merge and return to main menu: "
+msgstr ""
+
+#, fuzzy, c-format
+msgid "Merging '%s'.\n"
+msgstr "正在读取 “%s”"
+
+#, c-format
+msgid "Merge of '%s' aborted.\n"
+msgstr ""
+
+#, c-format
+msgid ""
+"Could not write merged result to '%s', saved instead at '%s'.\n"
+"'%s' remains in conflict.\n"
+msgstr ""
+
+#, fuzzy, c-format
+msgid "Merge of '%s' completed (remains in conflict).\n"
+msgstr "已跳过 '%s' -- 节点处于冲突状态\n"
+
+#, fuzzy, c-format
+msgid "Merge of '%s' completed.\n"
+msgstr ""
+"\n"
+"完成升级。\n"
+
#, c-format
msgid ""
"usage: svn <subcommand> [options] [args]\n"
@@ -6247,6 +6500,9 @@
msgid "Lock comment contains a zero byte"
msgstr "加锁注释中有一个零字节"
+msgid "\n"
+msgstr "\n"
+
msgid "(no author)"
msgstr "(没有作者信息)"
@@ -6274,10 +6530,6 @@
msgid "Merged via:"
msgstr "合并通过: "
-#, c-format
-msgid "\n"
-msgstr "\n"
-
msgid "'with-all-revprops' option only valid in XML mode"
msgstr "只有在 XML 模式 “with-all-revprops” 选项才有效"
@@ -6465,24 +6717,12 @@
msgid "try operation but make no changes"
msgstr "尝试操作但没有修改"
-msgid "do not print differences for deleted files"
-msgstr "不要输出删除文件造成的差异"
-
-msgid "ignore properties during the operation"
-msgstr "操作时忽略属性"
-
-msgid "notice ancestry when calculating differences"
-msgstr "比较差异时提示原始信息"
-
msgid "ignore ancestry when calculating merges"
msgstr "合并时忽略原始信息"
msgid "ignore externals definitions"
msgstr "忽略外部项目"
-msgid "use ARG as diff command"
-msgstr "使用 ARG 作为比较命令"
-
msgid "use ARG as merge command"
msgstr "使用 ARG 作为合并命令"
@@ -6540,9 +6780,6 @@
msgid "don't unlock the targets"
msgstr "不要解锁目标"
-msgid "show a summary of the results"
-msgstr "显示结果的概要"
-
msgid "remove changelist association"
msgstr "删除修改列表耦合"
@@ -6596,8 +6833,8 @@
"指定显示哪个版本集合\n"
" ('merged', 'eligible')"
-msgid "merge a branch back into its parent branch"
-msgstr "合并分支回父分支"
+msgid "deprecated"
+msgstr ""
msgid ""
"number of leading path components to strip from\n"
@@ -6616,9 +6853,6 @@
" 'crunchy.html' 。在所有平台中期望的组件分隔符\n"
" 都是 '/'。前导 '/' 被计算为一个组件。"
-msgid "don't diff copied or moved files with their source"
-msgstr "请不要将复制或移动的文件与其源文件比较"
-
msgid "don't expand keywords"
msgstr "不要展开关键字"
@@ -6631,16 +6865,41 @@
msgid "produce diff output"
msgstr "产生差异输出"
+#. maps to show_diff
+#. diff options
+msgid "use ARG as diff command"
+msgstr "使用 ARG 作为比较命令"
+
msgid "override diff-cmd specified in config file"
msgstr "覆盖配置文件中指定的 diff-cmd"
+msgid "do not print differences for deleted files"
+msgstr "不要输出删除文件造成的差异"
+
+msgid "don't diff copied or moved files with their source"
+msgstr "请不要将复制或移动的文件与其源文件比较"
+
+msgid "notice ancestry when calculating differences"
+msgstr "比较差异时提示原始信息"
+
+msgid "show a summary of the results"
+msgstr "显示结果的概要"
+
msgid "use git's extended diff format"
msgstr "使用 git 的扩展差异格式"
+msgid "ignore properties during the operation"
+msgstr "操作时忽略属性"
+
+#, fuzzy
+msgid "show only properties during the operation"
+msgstr "操作时忽略属性"
+
+#, fuzzy
msgid ""
"generate diff suitable for generic third-party\n"
" patch tools; currently the same as\n"
-" --show-copies-as-adds --ignore-properties\n"
+" --show-copies-as-adds --ignore-properties"
msgstr ""
"生成适用于第三方工具的差异格式;当前等价于\n"
" --show-copies-as-adds --ignore-properties\n"
@@ -6660,6 +6919,12 @@
" fixed revision. (See the svn:externals property)"
msgstr ""
+msgid "use ARG as search pattern (glob syntax)"
+msgstr ""
+
+msgid "like --search, but case-insensitive"
+msgstr ""
+
msgid ""
"Put files and directories under version control, scheduling\n"
"them for addition to repository. They will be added in next commit.\n"
@@ -7053,16 +7318,41 @@
" The --depth option is only valid in combination with the --diff option\n"
" and limits the scope of the displayed diff to the specified depth.\n"
"\n"
-" Examples:\n"
-" svn log\n"
-" svn log foo.c\n"
-" svn log bar.c@42\n"
-" svn log http://www.example.com/repo/project/foo.c\n"
-" svn log http://www.example.com/repo/project foo.c bar.c\n"
-" svn log http://www.example.com/repo/project@50 foo.c bar.c\n"
+" If the --search option is used, log messages are displayed only if the\n"
+" provided search pattern matches the author, date, log message text,\n"
+" or, if the --verbose option is also provided, a changed path.\n"
+" The search pattern may include glob syntax wildcards:\n"
+" ? matches any single character\n"
+" * matches a sequence of arbitrary characters\n"
+" [...] matches any of the characters listed inside the brackets\n"
+" If --limit is used in combination with --search, --limit restricts the\n"
+" number of log messages searched, rather than restricting the output\n"
+" to a particular number of matching log messages.\n"
"\n"
-" This command shows the log entry for the revision the branch\n"
-" ^/branches/foo was created in:\n"
+" Examples:\n"
+"\n"
+" Show the latest 5 log messages for the current working copy\n"
+" directory and display paths changed in each commit:\n"
+" svn log -l 5 -v\n"
+"\n"
+" Show the log for bar.c as of revision 42:\n"
+" svn log bar.c@42\n"
+"\n"
+" Show log messages and diffs for each commit to foo.c:\n"
+" svn log --diff http://www.example.com/repo/project/foo.c\n"
+" (Because the above command uses a full URL it does not require\n"
+" a working copy.)\n"
+"\n"
+" Show log messages for the children foo.c and bar.c of the directory\n"
+" '/trunk' as it appeared in revision 50, using the ^/ URL shortcut:\n"
+" svn log ^/trunk@50 foo.c bar.c\n"
+"\n"
+" Show the log messages for any incoming changes to foo.c during the\n"
+" next 'svn update':\n"
+" svn log -r BASE:HEAD foo.c\n"
+"\n"
+" Show the log message for the revision in which /branches/foo\n"
+" was created:\n"
" svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo\n"
msgstr ""
"显示一组版本与/或路径的日志信息。\n"
@@ -7107,22 +7397,16 @@
" (the 'sync' merge)\n"
" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
" (the 'cherry-pick' merge)\n"
-" 3. merge --reintegrate SOURCE[@REV] [TARGET_WCPATH]\n"
-" (the 'reintegrate' merge)\n"
-" 4. merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n"
+" 3. merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n"
" (the '2-URL' merge)\n"
"\n"
-" 1. This form is called a 'sync' (or 'catch-up') merge:\n"
+" 1. This form, with one source path and no revision range:\n"
"\n"
" svn merge SOURCE[@REV] [TARGET_WCPATH]\n"
"\n"
-" A sync merge is used to fetch all the latest changes made on a parent\n"
-" branch. In other words, the target branch has originally been created\n"
-" by copying the source branch, and any changes committed on the source\n"
-" branch since branching are applied to the target branch. This uses\n"
-" merge tracking to skip all those revisions that have already been\n"
-" merged, so a sync merge can be repeated periodically to stay up-to-\n"
-" date with the source branch.\n"
+" finds all the changes on the source branch that have not already been\n"
+" merged to the target branch, and merges them. Merge tracking is used\n"
+" to know which changes have already been merged.\n"
"\n"
" SOURCE specifies the branch from where the changes will be pulled, and\n"
" TARGET_WCPATH specifies a working copy of the target branch to which\n"
@@ -7138,22 +7422,44 @@
" used, and the default value of 'REV' is the base revision (usually the\n"
" revision last updated to).\n"
"\n"
-" TARGET_WCPATH is a working copy path; if omitted, '.' is assumed.\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is assumed. In\n"
+" normal usage the working copy should be up to date, at a single\n"
+" revision, with no local modifications and no switched subtrees.\n"
+"\n"
+" - The 'Feature Branch' Merging Pattern -\n"
+"\n"
+" In this commonly used pattern of merging, a developer is working on\n"
+" a feature development branch, committing a series of changes that\n"
+" implement the feature. The developer periodically merges all the\n"
+" latest changes from the 'parent' branch (from which the feature branch\n"
+" is branched off). When the feature development is complete, the\n"
+" developer integrates the feature back into the parent branch by\n"
+" merging the other way, into a trunk working copy.\n"
+"\n"
+" trunk --+----------o------o-o-------------o--\n"
+" \\ \\ \\ /\n"
+" \\ merge merge merge\n"
+" \\ \\ \\ /\n"
+" feature +--o-o-------o----o-o----o-------\n"
+"\n"
+" In this pattern, a merge from the parent branch to the feature branch\n"
+" is known as a 'sync' merge (or 'catch-up' merge), and a merge from the\n"
+" feature branch to the parent branch may be called a 'reintegrate'\n"
+" merge. The 'sync' merges are normally low-risk because the parent\n"
+" branch is considered to be more 'stable' than the feature branch, in\n"
+" the sense of being less likely to contain incomplete or broken work.\n"
+" By syncing often, these merges can be kept small, avoiding the need\n"
+" for a difficult 'big bang' merge at reintegration time.\n"
"\n"
" - Sync Merge Example -\n"
-"\n"
-" A feature is being developed on a branch called 'feature', which has\n"
-" originally been a copy of trunk. The feature branch has been regularly\n"
-" synced with trunk to keep up with the changes made there. The previous\n"
-" sync merges are not shown on this diagram, and the last of them was\n"
-" done when HEAD was r100. Currently, HEAD is r200.\n"
-"\n"
-" feature +------------------------o-----\n"
-" / ^\n"
-" / ............ |\n"
-" / . . /\n"
-" trunk ------+------------L--------------R------\n"
-" r100 r200\n"
+" ............\n"
+" . .\n"
+" trunk --+------------L--------------R------\n"
+" \\ \\\n"
+" \\ |\n"
+" \\ v\n"
+" feature +------------------------o-----\n"
+" r100 r200\n"
"\n"
" Subversion will locate all the changes on 'trunk' that have not yet\n"
" been merged into the 'feature' branch. In this case that is a single\n"
@@ -7174,6 +7480,40 @@
" conflicts before you commit the merge.\n"
"\n"
"\n"
+" - Reintegrate Merge Example -\n"
+"\n"
+" The feature branch was last synced with trunk up to revision X. So the\n"
+" difference between trunk@X and feature@HEAD contains the complete set\n"
+" of changes that implement the feature, and no other changes. These\n"
+" changes are applied to trunk.\n"
+"\n"
+" rW rX\n"
+" trunk ------+--------------------L------------------o\n"
+" \\ . ^\n"
+" \\ ............. /\n"
+" \\ . /\n"
+" feature +--------------------------------R\n"
+"\n"
+" In the diagram above, L marks the left side (trunk@X) and R marks the\n"
+" right side (feature@HEAD) of the merge. The difference between the\n"
+" left and right side is merged into trunk, the target.\n"
+"\n"
+" To perform the merge, have a clean working copy of trunk and run the\n"
+" following command in its top-level directory:\n"
+"\n"
+" svn merge ^/feature\n"
+"\n"
+" To prevent unnecessary merge conflicts, a reintegrate merge requires\n"
+" that TARGET_WCPATH is not a mixed-revision working copy, has no local\n"
+" modifications, and has no switched subtrees.\n"
+"\n"
+" A reintegrate merge also requires that the source branch is coherently\n"
+" synced with the target -- in the above example, this means that all\n"
+" revisions between the branch point W and the last merged revision X\n"
+" are merged to the feature branch, so that there are no unmerged\n"
+" revisions in-between.\n"
+"\n"
+"\n"
" 2. This form is called a 'cherry-pick' merge:\n"
"\n"
" svn merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
@@ -7243,66 +7583,7 @@
" svn merge -c50,54,60 -r65:68 ^/trunk\n"
"\n"
"\n"
-" 3. This form is called a 'reintegrate merge':\n"
-"\n"
-" svn merge --reintegrate SOURCE[@REV] [TARGET_WCPATH]\n"
-"\n"
-" In a reintegrate merge, an (e.g. feature) branch is merged back to its\n"
-" originating branch. In other words, the source branch has originally\n"
-" been created by copying the target branch, development has concluded\n"
-" on the source branch and it should now be merged back into the target\n"
-" branch.\n"
-" \n"
-" SOURCE is the URL of a branch to be merged back. If REV is specified,\n"
-" it is used as the peg revision for SOURCE; if REV is not specified,\n"
-" the HEAD revision is assumed.\n"
-"\n"
-" TARGET_WCPATH is a working copy of the branch the changes will be\n"
-" applied to.\n"
-"\n"
-" - Reintegrate Merge Example -\n"
-"\n"
-" A feature has been developed on a branch called 'feature'. The feature\n"
-" branch started as a copy of trunk@W. Work on the feature has completed\n"
-" and it should be merged back into the trunk.\n"
-"\n"
-" The feature branch was last synced with trunk up to revision X. So the\n"
-" difference between trunk@X and feature@HEAD contains the complete set\n"
-" of changes that implement the feature, and no other changes. These\n"
-" changes are applied to trunk.\n"
-"\n"
-" feature +--------------------------------R\n"
-" / . \\\n"
-" / ............. \\\n"
-" / . v\n"
-" trunk ------+--------------------L------------------o\n"
-" rW rX\n"
-"\n"
-" In the diagram above, L marks the left side (trunk@X) and R marks the\n"
-" right side (feature@HEAD) of the merge. The difference between the\n"
-" left and right side is merged into trunk, the target.\n"
-"\n"
-" To perform the merge, have a clean working copy of trunk and run the\n"
-" following command in its top-level directory:\n"
-"\n"
-" svn merge --reintegrate ^/feature\n"
-"\n"
-" To prevent unnecessary merge conflicts, a reintegrate merge requires\n"
-" that TARGET_WCPATH is not a mixed-revision working copy, has no local\n"
-" modifications, and has no switched subtrees.\n"
-"\n"
-" A reintegrate merge also requires that the source branch is coherently\n"
-" synced with the target -- in the above example, this means that all\n"
-" revisions between the branch point W and the last merged revision X\n"
-" are merged to the feature branch, so that there are no unmerged\n"
-" revisions in-between.\n"
-"\n"
-" After the reintegrate merge, the feature branch cannot be synced to\n"
-" the trunk again without merge conflicts. If further work must be done\n"
-" on the feature branch, it should be deleted and then re-created.\n"
-"\n"
-"\n"
-" 4. This form is called a '2-URL merge':\n"
+" 3. This form is called a '2-URL merge':\n"
"\n"
" svn merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n"
"\n"
@@ -7425,6 +7706,7 @@
" repositories.\n"
msgstr ""
+#, fuzzy
msgid ""
"Display merge-related information.\n"
"usage: mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
@@ -7434,6 +7716,11 @@
" specified by the --show-revs option. If --show-revs isn't passed,\n"
" it defaults to --show-revs='merged'.\n"
"\n"
+" If --revision (-r) is provided, filter the displayed information to\n"
+" show only that which is associated with the revisions within the\n"
+" specified range. Revision numbers, dates, and the 'HEAD' keyword are\n"
+" valid range values.\n"
+"\n"
" The depth can be 'empty' or 'infinity'; the default is 'empty'.\n"
msgstr ""
"显示合并的相关信息。\n"
@@ -7585,6 +7872,7 @@
"\n"
"参见 “svn help propset” 以获得更多设置属性的信息。\n"
+#, fuzzy
msgid ""
"Print the value of a property on files, dirs, or revisions.\n"
"usage: 1. propget PROPNAME [TARGET[@REV]...]\n"
@@ -7595,13 +7883,15 @@
" 2. Prints unversioned remote prop on repos revision.\n"
" TARGET only determines which repository to access.\n"
"\n"
-" By default, this subcommand will add an extra newline to the end\n"
-" of the property values so that the output looks pretty. Also,\n"
-" whenever there are multiple paths involved, each property value\n"
-" is prefixed with the path with which it is associated. Use the\n"
-" --strict option to disable these beautifications (useful when\n"
-" redirecting a binary property value to a file, but available only\n"
-" if you supply a single TARGET to a non-recursive propget operation).\n"
+" With --verbose, the target path and the property name are printed on\n"
+" separate lines before each value, like 'svn proplist --verbose'.\n"
+" Otherwise, if there is more than one TARGET or a depth other than\n"
+" 'empty', the target path is printed on the same line before each value.\n"
+"\n"
+" By default, an extra newline is printed after the property value so that\n"
+" the output looks pretty. With a single TARGET and depth 'empty', you can\n"
+" use the --strict option to disable this (useful when redirecting a binary\n"
+" property value to a file, for example).\n"
msgstr ""
"显示目录、文件或版本的属性取值。\n"
"用法: 1、propget PROPNAME [TARGET[@REV]...]\n"
@@ -7616,6 +7906,14 @@
" 使用 --strict 选项可以关闭这些美化行为 (它很有用,例如将二进制属\n"
" 性内容重定向到一个文件,但是仅用于单一目标的非递归操作)。\n"
+msgid "print path, name and value on separate lines"
+msgstr ""
+
+#, fuzzy
+msgid "don't print an extra newline"
+msgstr "不打印未版本控制的条目"
+
+#, fuzzy
msgid ""
"List all properties on files, dirs, or revisions.\n"
"usage: 1. proplist [TARGET[@REV]...]\n"
@@ -7625,6 +7923,9 @@
" revision the target is first looked up.\n"
" 2. Lists unversioned remote props on repos revision.\n"
" TARGET only determines which repository to access.\n"
+"\n"
+" With --verbose, the property values are printed as well, like 'svn propget\n"
+" --verbose'. With --quiet, the paths are not printed.\n"
msgstr ""
"列出目录、文件或版本的所有属性。\n"
"用法: 1、proplist [TARGET[@REV]...]\n"
@@ -7633,6 +7934,10 @@
" 1、显示目标的属性。REV 指定从哪个版本开始查找目标。\n"
" 2、列出版本库中版本的属性。TARGET 只用来判断访问哪个版本库。\n"
+#, fuzzy
+msgid "don't print the path"
+msgstr "不要解锁目标"
+
msgid ""
"Set the value of a property on files, dirs, or revisions.\n"
"usage: 1. propset PROPNAME PROPVAL PATH...\n"
@@ -7801,14 +8106,13 @@
msgid ""
"Resolve conflicts on working copy files or directories.\n"
-"usage: resolve --accept=ARG [PATH...]\n"
+"usage: resolve [PATH...]\n"
"\n"
-" Note: the --accept option is currently required.\n"
+" If no arguments are given, perform interactive conflict resolution for\n"
+" all conflicted paths in the working copy, with default depth 'infinity'.\n"
+" The --accept=ARG option prevents prompting and forces conflicts on PATH\n"
+" to resolved in the manner specified by ARG, with default depth 'empty'.\n"
msgstr ""
-"解决工作副本中目录或文件的冲突。\n"
-"用法: resolve --accept=ARG [PATH...]\n"
-"\n"
-" 注意: 当前需要选项 --accept 。\n"
msgid ""
"specify automatic conflict resolution source\n"
@@ -7917,20 +8221,20 @@
"\n"
" Example output:\n"
" svn status wc\n"
-" M wc/bar.c\n"
-" A + wc/qax.c\n"
+" M wc/bar.c\n"
+" A + wc/qax.c\n"
"\n"
" svn status -u wc\n"
-" M 965 wc/bar.c\n"
-" * 965 wc/foo.c\n"
-" A + - wc/qax.c\n"
+" M 965 wc/bar.c\n"
+" * 965 wc/foo.c\n"
+" A + - wc/qax.c\n"
" Status against revision: 981\n"
"\n"
" svn status --show-updates --verbose wc\n"
-" M 965 938 kfogel wc/bar.c\n"
-" * 965 922 sussman wc/foo.c\n"
-" A + - 687 joe wc/qax.c\n"
-" 965 687 joe wc/zig.c\n"
+" M 965 938 kfogel wc/bar.c\n"
+" * 965 922 sussman wc/foo.c\n"
+" A + - 687 joe wc/qax.c\n"
+" 965 687 joe wc/zig.c\n"
" Status against revision: 981\n"
"\n"
" svn status\n"
@@ -8004,20 +8308,20 @@
"\n"
" 范例输出: \n"
" svn status wc\n"
-" M wc/bar.c\n"
-" A + wc/qax.c\n"
+" M wc/bar.c\n"
+" A + wc/qax.c\n"
"\n"
" svn status -u wc\n"
-" M 965 wc/bar.c\n"
-" * 965 wc/foo.c\n"
-" A + - wc/qax.c\n"
+" M 965 wc/bar.c\n"
+" * 965 wc/foo.c\n"
+" A + - wc/qax.c\n"
" 版本 981 的状态\n"
"\n"
" svn status --show-updates --verbose wc\n"
-" M 965 938 kfogel wc/bar.c\n"
-" * 965 922 sussman wc/foo.c\n"
-" A + - 687 joe wc/qax.c\n"
-" 965 687 joe wc/zig.c\n"
+" M 965 938 kfogel wc/bar.c\n"
+" * 965 922 sussman wc/foo.c\n"
+" A + - 687 joe wc/qax.c\n"
+" 965 687 joe wc/zig.c\n"
" 版本 981 的状态\n"
"\n"
" svn status\n"
@@ -8320,20 +8624,12 @@
msgid "--auto-props and --no-auto-props are mutually exclusive"
msgstr "--auto-props 与 --no-auto-props 是互斥的"
-msgid "--reintegrate cannot be used with --ignore-ancestry or --record-only"
-msgstr "--reintegrate 不能与 --ignore-ancestry 或 --record-only 共存"
-
-msgid "--reintegrate cannot be used with --ignore-ancestry"
-msgstr "--reintegrate 不能与 --ignore-ancestry 共存"
-
-msgid "--reintegrate cannot be used with --record-only"
-msgstr "--reintegrate 不能与 --record-only 共存"
-
#, c-format
msgid "--accept=%s incompatible with --non-interactive"
msgstr "--accept=%s 与 --non-interactive 不兼容"
-msgid "Try 'svn help' for more info"
+#, fuzzy, c-format
+msgid "Try 'svn help %s' for more information"
msgstr "请使用 “svn help” 以得到更多信息"
msgid "Please see the 'svn upgrade' command"
@@ -8342,6 +8638,18 @@
msgid "Run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)"
msgstr "运行“svn cleanup”删除锁 (运行“svn help cleanup”以得到详细信息)"
+msgid "The required merge is reintegrate-like, and the --record-only option cannot be used with this kind of merge"
+msgstr ""
+
+msgid "The required merge is reintegrate-like, and the --depth option cannot be used with this kind of merge"
+msgstr ""
+
+msgid "The required merge is reintegrate-like, and the --force option cannot be used with this kind of merge"
+msgstr ""
+
+msgid "The required merge is reintegrate-like, and the --allow-mixed-revisions option cannot be used with this kind of merge"
+msgstr ""
+
msgid "-r and -c can't be used with --reintegrate"
msgstr "-r 和 -c 不能与 --reintegrate 共存"
@@ -8357,6 +8665,12 @@
msgid "Cannot specify a revision range with two URLs"
msgstr "不能对两个 URL 指定版本范围"
+msgid "--reintegrate cannot be used with --ignore-ancestry"
+msgstr "--reintegrate 不能与 --ignore-ancestry 共存"
+
+msgid "--reintegrate cannot be used with --record-only"
+msgstr "--reintegrate 不能与 --record-only 共存"
+
msgid "--depth cannot be used with --reintegrate"
msgstr "--depth 不能与 --reintegrate 共存"
@@ -9064,6 +9378,12 @@
" earlier than 1.6"
msgstr "使用与 1.6 之前版本兼容的格式"
+#, fuzzy
+msgid ""
+"use format compatible with Subversion versions\n"
+" earlier than 1.8"
+msgstr "使用与1.4之前版本兼容的格式"
+
msgid ""
"size of the extra in-memory cache in MB used to\n"
" minimize redundant operations. Default: 16.\n"
@@ -9387,7 +9707,6 @@
"已取得版本库锁定。\n"
"请稍候;修复版本库可能需要一段时间...\n"
-#, c-format
msgid ""
"Repository lock acquired.\n"
"Please wait; upgrading the repository may take some time...\n"
@@ -9579,8 +9898,13 @@
msgid "No valid revision range 'end' in filtered stream"
msgstr "在过滤流中没有有效的版本范围 “end”"
-msgid "Delta property block detected - not supported by svndumpfilter"
-msgstr "检测到 Delta 属性块 - svndumpfilter 不支持"
+#, c-format
+msgid "Delta property block detected, but deltas are not enabled for node '%s' in original revision %ld"
+msgstr ""
+
+#, c-format
+msgid "Delta property block detected, but deltas are not enabled for node '%s' in originalrevision %ld"
+msgstr ""
msgid "Do not display filtering statistics."
msgstr "不显示过滤的统计数据。"
@@ -10194,29 +10518,22 @@
msgid "read configuration from file ARG"
msgstr "从文件 ARG 读取配置"
-msgid ""
-"listen port\n"
-" [mode: daemon, service, listen-once]"
+msgid "listen port. The default port is "
msgstr ""
-"监听端口\n"
-" [方式: daemon, service, listen-once]"
-msgid ""
-"listen port\n"
-" [mode: daemon, listen-once]"
-msgstr ""
-"监听端口\n"
-" [方式: daemon, listen-once]"
-
+#, fuzzy
msgid ""
"listen hostname or IP address\n"
+" By default svnserve listens on all addresses.\n"
" [mode: daemon, service, listen-once]"
msgstr ""
"监听主机名称或IP地址\n"
" [方式: daemon, service, listen-once]"
+#, fuzzy
msgid ""
"listen hostname or IP address\n"
+" By default svnserve listens on all addresses.\n"
" [mode: daemon, listen-once]"
msgstr ""
"监听主机名称或IP地址\n"
@@ -10271,6 +10588,17 @@
" 默认值: 是。\n"
" [仅用于 FSFS 仓库]"
+#, fuzzy
+msgid ""
+"enable or disable caching of revision properties.\n"
+" Consult the documentation before activating this.\n"
+" Default is no.\n"
+" [used for FSFS repositories only]"
+msgstr ""
+"是否对旧版本之间的差异启用缓存\n"
+" 默认值: 否。\n"
+" [仅用于 FSFS 仓库]"
+
#. ### Making the assumption here that WIN32 never has fork and so
#. * ### this option never exists when --service exists.
msgid "use threads instead of fork [mode: daemon]"
@@ -10283,6 +10611,11 @@
"在前台运行(调试用)\n"
" [方式: daemon]"
+msgid ""
+"handle one connection at a time in the parent process\n"
+" (useful for debugging)"
+msgstr ""
+
msgid "svnserve log file"
msgstr "svnserve 日志文件"
@@ -10350,6 +10683,9 @@
msgid "You must specify exactly one of -d, -i, -t or -X.\n"
msgstr "您必须指定 -d, -i, -t 或 -X 中的一个。\n"
+msgid "You may only specify one of -T or --single-thread\n"
+msgstr ""
+
#, c-format
msgid "Option --tunnel-user is only valid in tunnel mode.\n"
msgstr "选项 --tunnel-user 只在隧道模式有效。\n"
diff --git a/subversion/po/zh_TW.po b/subversion/po/zh_TW.po
index 8e3c640..f8415fe 100644
--- a/subversion/po/zh_TW.po
+++ b/subversion/po/zh_TW.po
@@ -12810,7 +12810,7 @@
#~ " '{' DATE '}' 該日開始的修訂版\n"
#~ " 'HEAD' 檔案庫最新版本\n"
#~ " 'BASE' 該項目工作複本的基礎修訂版\n"
-#~ " 'COMMITED' 位於或早於 BASE 的最後送交\n"
+#~ " 'COMMITTED' 位於或早於 BASE 的最後送交\n"
#~ " 'PREV' COMMITTED 之前的修訂版"
#~ msgid "Error reading administrative log file in '%s'"
diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h
index 87e1e7b..21441e3 100644
--- a/subversion/svn/cl.h
+++ b/subversion/svn/cl.h
@@ -236,11 +236,8 @@
svn_boolean_t show_diff; /* produce diff output (maps to --diff) */
svn_boolean_t allow_mixed_rev; /* Allow operation on mixed-revision WC */
svn_boolean_t include_externals; /* Recurses (in)to file & dir externals */
- const char *search_pattern; /* pattern argument for --search */
- svn_boolean_t case_insensitive_search; /* perform case-insensitive search */
-
- svn_wc_conflict_resolver_func2_t conflict_func;
- void *conflict_baton;
+ svn_boolean_t show_inherited_props; /* get inherited properties */
+ apr_array_header_t* search_patterns; /* pattern arguments for --search */
} svn_cl__opt_state_t;
@@ -333,37 +330,58 @@
/* Various conflict-resolution callbacks. */
-typedef struct svn_cl__conflict_baton_t {
- svn_cl__accept_t accept_which;
- apr_hash_t *config;
- const char *editor_cmd;
- svn_boolean_t external_failed;
- svn_cmdline_prompt_baton_t *pb;
- const char *path_prefix;
-} svn_cl__conflict_baton_t;
+/* Opaque baton type for svn_cl__conflict_func_interactive(). */
+typedef struct svn_cl__interactive_conflict_baton_t
+ svn_cl__interactive_conflict_baton_t;
-/* Create and return a conflict baton in *B, allocated from POOL, with the
- * values ACCEPT_WHICH, CONFIG, EDITOR_CMD and PB placed in the same-named
- * fields of the baton, and its 'external_failed' field initialised to FALSE. */
+/* Create and return an baton for use with svn_cl__conflict_func_interactive
+ * in *B, allocated from RESULT_POOL, and initialised with the values
+ * ACCEPT_WHICH, CONFIG, EDITOR_CMD, CANCEL_FUNC and CANCEL_BATON. */
svn_error_t *
-svn_cl__conflict_baton_make(svn_cl__conflict_baton_t **b,
- svn_cl__accept_t accept_which,
- apr_hash_t *config,
- const char *editor_cmd,
- svn_cmdline_prompt_baton_t *pb,
- apr_pool_t *pool);
+svn_cl__get_conflict_func_interactive_baton(
+ svn_cl__interactive_conflict_baton_t **b,
+ svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool);
/* A conflict-resolution callback which prompts the user to choose
one of the 3 fulltexts, edit the merged file on the spot, or just
skip the conflict (to be resolved later).
Implements @c svn_wc_conflict_resolver_func_t. */
svn_error_t *
-svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description2_t *desc,
- void *baton,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
+svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+/* Create an return a baton for use with svn_cl__conflict_func_postpone(),
+ * allocated in RESULT_POOL. */
+void *
+svn_cl__get_conflict_func_postpone_baton(apr_pool_t *result_pool);
+
+/* A conflict-resolution callback which postpones all conflicts and
+ * remembers conflicted paths in BATON. */
+svn_error_t *
+svn_cl__conflict_func_postpone(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Run the interactive conflict resolver, obtained internally from
+ * svn_cl__get_conflict_func_interactive(), on any conflicted paths
+ * stored in the BATON obtained from svn_cl__get_conflict_func_postpone(). */
+svn_error_t *
+svn_cl__resolve_postponed_conflicts(void *baton,
+ svn_depth_t depth,
+ svn_cl__accept_t accept_which,
+ const char *editor_cmd,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool);
/*** Command-line output functions -- printing to the user. ***/
@@ -460,12 +478,15 @@
svn_boolean_t names_only,
apr_pool_t *pool);
-/* Same as svn_cl__print_prop_hash(), only output xml to *OUTSTR. If *OUTSTR is
- NULL, allocate it first from POOL, otherwise append to it. */
+/* Similar to svn_cl__print_prop_hash(), only output xml to *OUTSTR.
+ If INHERITED_PROPS is true, then PROP_HASH contains inherited properties,
+ otherwise PROP_HASH contains explicit properties. If *OUTSTR is NULL,
+ allocate it first from POOL, otherwise append to it. */
svn_error_t *
svn_cl__print_xml_prop_hash(svn_stringbuf_t **outstr,
apr_hash_t *prop_hash,
svn_boolean_t names_only,
+ svn_boolean_t inherited_props,
apr_pool_t *pool);
/* Output a commit xml element to *OUTSTR. If *OUTSTR is NULL, allocate it
@@ -618,14 +639,6 @@
svn_error_t *
svn_cl__notifier_mark_wc_to_repos_copy(void *baton);
-/* Return TRUE if any conflicts were detected during notification. */
-svn_boolean_t
-svn_cl__notifier_check_conflicts(void *baton);
-
-/* Return a sorted array of conflicted paths detected during notification. */
-apr_array_header_t *
-svn_cl__notifier_get_conflicted_paths(void *baton, apr_pool_t *result_pool);
-
/* Baton for use with svn_cl__check_externals_failed_notify_wrapper(). */
struct svn_cl__check_externals_failed_notify_baton
{
@@ -873,15 +886,6 @@
svn_client_ctx_t *ctx,
apr_pool_t *pool);
-/* Run the conflict resolver for all targets in the TARGETS list with
- * the specified DEPTH. */
-svn_error_t *
-svn_cl__resolve_conflicts(apr_array_header_t *targets,
- svn_depth_t depth,
- const svn_cl__opt_state_t *opt_state,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool);
-
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c
index 6271d5b..84ea8c5 100644
--- a/subversion/svn/conflict-callbacks.c
+++ b/subversion/svn/conflict-callbacks.c
@@ -33,6 +33,7 @@
#include "svn_dirent_uri.h"
#include "svn_types.h"
#include "svn_pools.h"
+#include "svn_sorts.h"
#include "cl.h"
#include "tree-conflicts.h"
@@ -41,22 +42,37 @@
+struct svn_cl__interactive_conflict_baton_t {
+ svn_cl__accept_t accept_which;
+ apr_hash_t *config;
+ const char *editor_cmd;
+ svn_boolean_t external_failed;
+ svn_cmdline_prompt_baton_t *pb;
+ const char *path_prefix;
+};
svn_error_t *
-svn_cl__conflict_baton_make(svn_cl__conflict_baton_t **b,
- svn_cl__accept_t accept_which,
- apr_hash_t *config,
- const char *editor_cmd,
- svn_cmdline_prompt_baton_t *pb,
- apr_pool_t *pool)
+svn_cl__get_conflict_func_interactive_baton(
+ svn_cl__interactive_conflict_baton_t **b,
+ svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool)
{
- *b = apr_palloc(pool, sizeof(**b));
+ svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb));
+ pb->cancel_func = cancel_func;
+ pb->cancel_baton = cancel_baton;
+
+ *b = apr_palloc(result_pool, sizeof(**b));
(*b)->accept_which = accept_which;
(*b)->config = config;
(*b)->editor_cmd = editor_cmd;
(*b)->external_failed = FALSE;
(*b)->pb = pb;
- SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", pool));
+ SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool));
+
return SVN_NO_ERROR;
}
@@ -180,7 +196,7 @@
static svn_error_t *
open_editor(svn_boolean_t *performed_edit,
const svn_wc_conflict_description2_t *desc,
- svn_cl__conflict_baton_t *b,
+ svn_cl__interactive_conflict_baton_t *b,
apr_pool_t *pool)
{
svn_error_t *err;
@@ -232,7 +248,7 @@
static svn_error_t *
launch_resolver(svn_boolean_t *performed_edit,
const svn_wc_conflict_description2_t *desc,
- svn_cl__conflict_baton_t *b,
+ svn_cl__interactive_conflict_baton_t *b,
apr_pool_t *pool)
{
svn_error_t *err;
@@ -265,17 +281,359 @@
return SVN_NO_ERROR;
}
+/* Ask the user what to do about the text conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_text_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ const char *answer;
+ char *prompt;
+ svn_boolean_t diff_allowed = FALSE;
+ /* Have they done something that might have affected the merged
+ file (so that we need to save a .edited copy)? */
+ svn_boolean_t performed_edit = FALSE;
+ /* Have they done *something* (edit, look at diff, etc) to
+ give them a rational basis for choosing (r)esolved? */
+ svn_boolean_t knows_something = FALSE;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict discovered in file '%s'.\n"),
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ /* Diffing can happen between base and merged, to show conflict
+ markers to the user (this is the typical 3-way merge
+ scenario), or if no base is available, we can show a diff
+ between mine and theirs. */
+ if ((desc->merged_file && desc->base_abspath)
+ || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
+ diff_allowed = TRUE;
+
+ while (TRUE)
+ {
+ svn_pool_clear(scratch_pool);
+
+ prompt = apr_pstrdup(scratch_pool, _("Select: (p) postpone"));
+
+ if (diff_allowed)
+ {
+ prompt = apr_pstrcat(scratch_pool, prompt,
+ _(", (df) diff-full, (e) edit, (m) merge"),
+ (char *)NULL);
+
+ if (knows_something)
+ prompt = apr_pstrcat(scratch_pool, prompt, _(", (r) resolved"),
+ (char *)NULL);
+
+ if (! desc->is_binary)
+ prompt = apr_pstrcat(scratch_pool, prompt,
+ _(",\n (mc) mine-conflict, "
+ "(tc) theirs-conflict"),
+ (char *)NULL);
+ }
+ else
+ {
+ if (knows_something)
+ prompt = apr_pstrcat(scratch_pool, prompt, _(", (r) resolved"),
+ (char *)NULL);
+ prompt = apr_pstrcat(scratch_pool, prompt,
+ _(",\n "
+ "(mf) mine-full, (tf) theirs-full"),
+ (char *)NULL);
+ }
+
+ prompt = apr_pstrcat(scratch_pool, prompt, ",\n ", (char *)NULL);
+ prompt = apr_pstrcat(scratch_pool, prompt,
+ _("(s) show all options: "),
+ (char *)NULL);
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, scratch_pool));
+
+ if (strcmp(answer, "s") == 0)
+ {
+ /* These are used in svn_cl__accept_from_word(). */
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("\n"
+ " (e) edit - change merged file in an editor\n"
+ " (df) diff-full - show all changes made to merged "
+ "file\n"
+ " (r) resolved - accept merged version of file\n"
+ "\n"
+ " (dc) display-conflict - show all conflicts "
+ "(ignoring merged version)\n"
+ " (mc) mine-conflict - accept my version for all "
+ "conflicts (same)\n"
+ " (tc) theirs-conflict - accept their version for all "
+ "conflicts (same)\n"
+ "\n"
+ " (mf) mine-full - accept my version of entire file "
+ "(even non-conflicts)\n"
+ " (tf) theirs-full - accept their version of entire "
+ "file (same)\n"
+ "\n"
+ " (p) postpone - mark the conflict to be "
+ "resolved later\n"
+ " (m) merge - use internal merge tool to "
+ "resolve conflict\n"
+ " (l) launch - launch external tool to "
+ "resolve conflict\n"
+ " (s) show all - show this list\n\n")));
+ }
+ else if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
+ {
+ /* Do nothing, let file be marked conflicted. */
+ result->choice = svn_wc_conflict_choose_postpone;
+ break;
+ }
+ else if (strcmp(answer, "mc") == 0 || strcmp(answer, "X-)") == 0)
+ {
+ if (desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option; cannot choose "
+ "based on conflicts in a "
+ "binary file.\n\n")));
+ continue;
+ }
+ result->choice = svn_wc_conflict_choose_mine_conflict;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ else if (strcmp(answer, "tc") == 0 || strcmp(answer, "X-(") == 0)
+ {
+ if (desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option; cannot choose "
+ "based on conflicts in a "
+ "binary file.\n\n")));
+ continue;
+ }
+ result->choice = svn_wc_conflict_choose_theirs_conflict;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ else if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
+ {
+ result->choice = svn_wc_conflict_choose_mine_full;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ else if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
+ {
+ result->choice = svn_wc_conflict_choose_theirs_full;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ else if (strcmp(answer, "dc") == 0)
+ {
+ if (desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option; cannot "
+ "display conflicts for a "
+ "binary file.\n\n")));
+ continue;
+ }
+ else if (! (desc->my_abspath && desc->base_abspath &&
+ desc->their_abspath))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option; original "
+ "files not available.\n\n")));
+ continue;
+ }
+ SVN_ERR(show_conflicts(desc, scratch_pool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(answer, "df") == 0)
+ {
+ if (! diff_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option; there's no "
+ "merged version to diff.\n\n")));
+ continue;
+ }
+
+ SVN_ERR(show_diff(desc, scratch_pool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(answer, "e") == 0 || strcmp(answer, ":-E") == 0)
+ {
+ SVN_ERR(open_editor(&performed_edit, desc, b, scratch_pool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else if (strcmp(answer, "m") == 0 || strcmp(answer, ":-g") == 0 ||
+ strcmp(answer, "=>-") == 0 || strcmp(answer, ":>.") == 0)
+ {
+ if (desc->kind != svn_wc_conflict_kind_text)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option; can only "
+ "resolve text conflicts with "
+ "the internal merge tool."
+ "\n\n")));
+ continue;
+ }
+
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ svn_boolean_t remains_in_conflict;
+
+ SVN_ERR(svn_cl__merge_file(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
+ desc->merged_file,
+ desc->local_abspath,
+ b->path_prefix,
+ b->editor_cmd,
+ b->config,
+ &remains_in_conflict,
+ scratch_pool));
+ knows_something = !remains_in_conflict;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option.\n\n")));
+ }
+ else if (strcmp(answer, "l") == 0 || strcmp(answer, ":-l") == 0)
+ {
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ SVN_ERR(launch_resolver(&performed_edit, desc, b, scratch_pool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option.\n\n")));
+ }
+ else if (strcmp(answer, "r") == 0)
+ {
+ /* We only allow the user accept the merged version of
+ the file if they've edited it, or at least looked at
+ the diff. */
+ if (knows_something)
+ {
+ result->choice = svn_wc_conflict_choose_merged;
+ break;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Invalid option.\n\n")));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the property conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_prop_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ const char *answer;
+ const char *prompt;
+ svn_stringbuf_t *prop_reject;
+ apr_pool_t *iterpool;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict for property '%s' discovered"
+ " on '%s'.\n"),
+ desc->property_name,
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ /* ### Currently, the only useful information in a prop conflict
+ * ### description is the .prej file path, which, possibly due to
+ * ### deceitful interference from outer space, is stored in the
+ * ### 'their_abspath' field of the description.
+ * ### This needs to be fixed so we can present better options here. */
+ if (desc->their_abspath)
+ {
+ /* ### The library dumps an svn_string_t into a temp file, and
+ * ### we read it back from the file into an svn_stringbuf_t here.
+ * ### That's rather silly. We should be passed svn_string_t's
+ * ### containing the old/mine/theirs values instead. */
+ SVN_ERR(svn_stringbuf_from_file2(&prop_reject,
+ desc->their_abspath,
+ scratch_pool));
+ /* Print reject file contents. */
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ "%s\n", prop_reject->data));
+ }
+ else
+ {
+ /* Nothing much we can do without a prej file... */
+ result->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (TRUE)
+ {
+ svn_pool_clear(iterpool);
+
+ prompt = _("Select: (p) postpone, (mf) mine-full, (tf) theirs-full: ");
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, iterpool));
+
+ if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
+ {
+ /* Do nothing, let property be marked conflicted. */
+ result->choice = svn_wc_conflict_choose_postpone;
+ break;
+ }
+ else if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
+ {
+ result->choice = svn_wc_conflict_choose_mine_full;
+ break;
+ }
+ else if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
+ {
+ result->choice = svn_wc_conflict_choose_theirs_full;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
/* Implement svn_wc_conflict_resolver_func2_t; resolves based on
--accept option if given, else by prompting. */
svn_error_t *
-svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description2_t *desc,
- void *baton,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- svn_cl__conflict_baton_t *b = baton;
+ svn_cl__interactive_conflict_baton_t *b = baton;
svn_error_t *err;
apr_pool_t *subpool;
@@ -409,333 +767,11 @@
*/
if (((desc->node_kind == svn_node_file)
&& (desc->action == svn_wc_conflict_action_edit)
- && (desc->reason == svn_wc_conflict_reason_edited))
- || (desc->kind == svn_wc_conflict_kind_property))
- {
- const char *answer;
- char *prompt;
- svn_boolean_t diff_allowed = FALSE;
- /* Have they done something that might have affected the merged
- file (so that we need to save a .edited copy)? */
- svn_boolean_t performed_edit = FALSE;
- /* Have they done *something* (edit, look at diff, etc) to
- give them a rational basis for choosing (r)esolved? */
- svn_boolean_t knows_something = FALSE;
+ && (desc->reason == svn_wc_conflict_reason_edited)))
+ SVN_ERR(handle_text_conflict(*result, desc, b, subpool));
+ else if (desc->kind == svn_wc_conflict_kind_property)
+ SVN_ERR(handle_prop_conflict(*result, desc, b, subpool));
- if (desc->kind == svn_wc_conflict_kind_text)
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Conflict discovered in file '%s'.\n"),
- svn_cl__local_style_skip_ancestor(
- b->path_prefix, desc->local_abspath,
- subpool)));
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Conflict for property '%s' discovered"
- " on '%s'.\n"),
- desc->property_name,
- svn_cl__local_style_skip_ancestor(
- b->path_prefix, desc->local_abspath,
- subpool)));
-
- if ((!desc->my_abspath && desc->their_abspath)
- || (desc->my_abspath && !desc->their_abspath))
- {
- /* One agent wants to change the property, one wants to
- delete it. This is not something we can diff, so we
- just tell the user. */
- svn_stringbuf_t *myval = NULL, *theirval = NULL;
-
- if (desc->my_abspath)
- {
- SVN_ERR(svn_stringbuf_from_file2(&myval, desc->my_abspath,
- subpool));
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("They want to delete the property, "
- "you want to change the value to '%s'.\n"),
- myval->data));
- }
- else
- {
- SVN_ERR(svn_stringbuf_from_file2(&theirval,
- desc->their_abspath,
- subpool));
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("They want to change the property value to '%s', "
- "you want to delete the property.\n"),
- theirval->data));
- }
- }
- }
- else
- /* We don't recognize any other sort of conflict yet */
- return SVN_NO_ERROR;
-
- /* Diffing can happen between base and merged, to show conflict
- markers to the user (this is the typical 3-way merge
- scenario), or if no base is available, we can show a diff
- between mine and theirs. */
- if ((desc->merged_file && desc->base_abspath)
- || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
- diff_allowed = TRUE;
-
- while (TRUE)
- {
- svn_pool_clear(subpool);
-
- prompt = apr_pstrdup(subpool, _("Select: (p) postpone"));
-
- if (diff_allowed)
- {
- prompt = apr_pstrcat(subpool, prompt,
- _(", (df) diff-full, (e) edit, (m) merge"),
- (char *)NULL);
-
- if (knows_something)
- prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"),
- (char *)NULL);
-
- if (! desc->is_binary &&
- desc->kind != svn_wc_conflict_kind_property)
- prompt = apr_pstrcat(subpool, prompt,
- _(",\n (mc) mine-conflict, "
- "(tc) theirs-conflict"),
- (char *)NULL);
- }
- else
- {
- if (knows_something)
- prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"),
- (char *)NULL);
- prompt = apr_pstrcat(subpool, prompt,
- _(",\n "
- "(mf) mine-full, (tf) theirs-full"),
- (char *)NULL);
- }
-
- prompt = apr_pstrcat(subpool, prompt, ",\n ", (char *)NULL);
- prompt = apr_pstrcat(subpool, prompt,
- _("(s) show all options: "),
- (char *)NULL);
-
- SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
-
- if (strcmp(answer, "s") == 0)
- {
- /* These are used in svn_cl__accept_from_word(). */
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("\n"
- " (e) edit - change merged file in an editor\n"
- " (df) diff-full - show all changes made to merged "
- "file\n"
- " (r) resolved - accept merged version of file\n"
- "\n"
- " (dc) display-conflict - show all conflicts "
- "(ignoring merged version)\n"
- " (mc) mine-conflict - accept my version for all "
- "conflicts (same)\n"
- " (tc) theirs-conflict - accept their version for all "
- "conflicts (same)\n"
- "\n"
- " (mf) mine-full - accept my version of entire file "
- "(even non-conflicts)\n"
- " (tf) theirs-full - accept their version of entire "
- "file (same)\n"
- "\n"
- " (p) postpone - mark the conflict to be "
- "resolved later\n"
- " (m) merge - use internal merge tool to "
- "resolve conflict\n"
- " (l) launch - launch external tool to "
- "resolve conflict\n"
- " (s) show all - show this list\n\n")));
- }
- else if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
- {
- /* Do nothing, let file be marked conflicted. */
- (*result)->choice = svn_wc_conflict_choose_postpone;
- break;
- }
- else if (strcmp(answer, "mc") == 0 || strcmp(answer, "X-)") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts in a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts for "
- "properties.\n\n")));
- continue;
- }
-
- (*result)->choice = svn_wc_conflict_choose_mine_conflict;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "tc") == 0 || strcmp(answer, "X-(") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts in a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts for "
- "properties.\n\n")));
- continue;
- }
- (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_mine_full;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_theirs_full;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "dc") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "display conflicts for a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "display conflicts for "
- "properties.\n\n")));
- continue;
- }
- else if (! (desc->my_abspath && desc->base_abspath &&
- desc->their_abspath))
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; original "
- "files not available.\n\n")));
- continue;
- }
- SVN_ERR(show_conflicts(desc, subpool));
- knows_something = TRUE;
- }
- else if (strcmp(answer, "df") == 0)
- {
- if (! diff_allowed)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; there's no "
- "merged version to diff.\n\n")));
- continue;
- }
-
- SVN_ERR(show_diff(desc, subpool));
- knows_something = TRUE;
- }
- else if (strcmp(answer, "e") == 0 || strcmp(answer, ":-E") == 0)
- {
- SVN_ERR(open_editor(&performed_edit, desc, b, subpool));
- if (performed_edit)
- knows_something = TRUE;
- }
- else if (strcmp(answer, "m") == 0 || strcmp(answer, ":-M") == 0)
- {
- if (desc->kind != svn_wc_conflict_kind_text)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; can only "
- "resolve text conflicts with "
- "the internal merge tool."
- "\n\n")));
- continue;
- }
-
- if (desc->base_abspath && desc->their_abspath &&
- desc->my_abspath && desc->merged_file)
- {
- svn_boolean_t remains_in_conflict;
-
- SVN_ERR(svn_cl__merge_file(desc->base_abspath,
- desc->their_abspath,
- desc->my_abspath,
- desc->merged_file,
- desc->local_abspath,
- b->path_prefix,
- b->editor_cmd,
- b->config,
- &remains_in_conflict,
- subpool));
- knows_something = !remains_in_conflict;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- else if (strcmp(answer, "l") == 0 || strcmp(answer, ":-l") == 0)
- {
- if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "resolve property conflicts "
- "with an external merge tool."
- "\n\n")));
- continue;
- }
- if (desc->base_abspath && desc->their_abspath &&
- desc->my_abspath && desc->merged_file)
- {
- SVN_ERR(launch_resolver(&performed_edit, desc, b, subpool));
- if (performed_edit)
- knows_something = TRUE;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- else if (strcmp(answer, "r") == 0)
- {
- /* We only allow the user accept the merged version of
- the file if they've edited it, or at least looked at
- the diff. */
- if (knows_something)
- {
- (*result)->choice = svn_wc_conflict_choose_merged;
- break;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- }
- }
/*
Dealing with obstruction of additions can be tricky. The
obstructing item could be unversioned, versioned, or even
@@ -768,7 +804,7 @@
desc->local_abspath,
subpool)));
prompt = _("Select: (p) postpone, (mf) mine-full, "
- "(tf) theirs-full, (h) help:");
+ "(tf) theirs-full, (h) help: ");
while (1)
{
@@ -820,7 +856,9 @@
scratch_pool),
readable_desc));
- prompt = _("Select: (p) postpone, (r) mark-resolved, (h) help: ");
+ prompt = _("Select: (p) postpone, (r) mark-resolved, "
+ "(mc) mine-conflict,\n"
+ " (tc) theirs-conflict, (h) help: ");
while (1)
{
@@ -831,8 +869,10 @@
if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
{
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _(" (p) postpone - resolve the conflict later\n"
- " (r) resolved - accept current working tree\n")));
+ _(" (p) postpone - resolve the conflict later\n"
+ " (r) resolved - accept current working copy state\n"
+ " (mc) mine-conflict - prefer local change\n"
+ " (tc) theirs-conflict - prefer incoming change\n")));
}
if (strcmp(answer, "p") == 0 || strcmp(answer, ":-p") == 0)
{
@@ -844,6 +884,16 @@
(*result)->choice = svn_wc_conflict_choose_merged;
break;
}
+ else if (strcmp(answer, "mc") == 0)
+ {
+ (*result)->choice = svn_wc_conflict_choose_mine_conflict;
+ break;
+ }
+ else if (strcmp(answer, "tc") == 0)
+ {
+ (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
+ break;
+ }
}
}
@@ -856,16 +906,77 @@
return SVN_NO_ERROR;
}
+/* Implement svn_wc_conflict_resolver_func2_t; postpones all conflicts
+ * and remembers conflicted paths in BATON. */
svn_error_t *
-svn_cl__resolve_conflicts(apr_array_header_t *targets,
- svn_depth_t depth,
- const svn_cl__opt_state_t *opt_state,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
+svn_cl__conflict_func_postpone(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
+ apr_hash_t *conflicted_paths = baton;
+
+ apr_hash_set(conflicted_paths,
+ apr_pstrdup(apr_hash_pool_get(conflicted_paths),
+ desc->local_abspath),
+ APR_HASH_KEY_STRING, "");
+
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+ return SVN_NO_ERROR;
+}
+
+void *
+svn_cl__get_conflict_func_postpone_baton(apr_pool_t *result_pool)
+{
+ return apr_hash_make(result_pool);
+}
+
+static apr_array_header_t *
+get_postponed_conflicted_paths(void *baton, apr_pool_t *result_pool)
+{
+ apr_hash_t *conflicted_paths = baton;
+ apr_array_header_t *sorted_array;
+ apr_array_header_t *result_array;
+ int i;
+
+ if (apr_hash_count(conflicted_paths) == 0)
+ return NULL;
+
+ sorted_array = svn_sort__hash(conflicted_paths,
+ svn_sort_compare_items_as_paths,
+ apr_hash_pool_get(conflicted_paths));
+ result_array = apr_array_make(result_pool, sorted_array->nelts,
+ sizeof(const char *));
+ for (i = 0; i < sorted_array->nelts; i++)
+ {
+ svn_sort__item_t item;
+
+ item = APR_ARRAY_IDX(sorted_array, i, svn_sort__item_t);
+ APR_ARRAY_PUSH(result_array, const char *) = apr_pstrdup(result_pool,
+ item.key);
+ }
+
+ return result_array;
+}
+
+svn_error_t *
+svn_cl__resolve_postponed_conflicts(void *baton,
+ svn_depth_t depth,
+ svn_cl__accept_t accept_which,
+ const char *editor_cmd,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *targets;
int i;
apr_pool_t *iterpool;
+ targets = get_postponed_conflicted_paths(baton, scratch_pool);
+ if (targets == NULL)
+ return SVN_NO_ERROR;
+
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
{
@@ -874,19 +985,25 @@
const char *local_abspath;
svn_wc_conflict_resolver_func2_t conflict_func2;
void *conflict_baton2;
+ svn_cl__interactive_conflict_baton_t *b;
svn_pool_clear(iterpool);
SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
-
/* Store old state */
conflict_func2 = ctx->conflict_func2;
conflict_baton2 = ctx->conflict_baton2;
- /* Store interactive resolver */
- ctx->conflict_func2 = opt_state->conflict_func;
- ctx->conflict_baton2 = opt_state->conflict_baton;
+ /* Set up the interactive resolver. */
+ ctx->conflict_func2 = svn_cl__conflict_func_interactive;
+ SVN_ERR(svn_cl__get_conflict_func_interactive_baton(&b, accept_which,
+ ctx->config,
+ editor_cmd,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ scratch_pool));
+ ctx->conflict_baton2 = b;
err = svn_client_resolve(local_abspath, depth,
svn_wc_conflict_choose_unspecified,
diff --git a/subversion/svn/file-merge.c b/subversion/svn/file-merge.c
index bd2c101..9e559e1 100644
--- a/subversion/svn/file-merge.c
+++ b/subversion/svn/file-merge.c
@@ -365,7 +365,7 @@
prepare_line_for_display(const char *line, apr_pool_t *pool)
{
svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
- int width;
+ size_t width;
int line_width = LINE_DISPLAY_WIDTH;
apr_pool_t *iterpool;
@@ -596,7 +596,7 @@
prompt = svn_stringbuf_create(
apr_psprintf(scratch_pool, "%s\n%s|%s\n%s",
- _("Conflicting section found during merge."),
+ _("Conflicting section found during merge:"),
prepare_line_for_display(
apr_psprintf(scratch_pool,
_("(1) their version (at line %lu)"),
@@ -851,6 +851,7 @@
apr_file_t *merged_file;
const char *merged_file_name;
struct file_merge_baton fmb;
+ svn_boolean_t executable;
SVN_ERR(svn_cmdline_printf(
@@ -912,7 +913,9 @@
return SVN_NO_ERROR;
}
- SVN_ERR_W(svn_io_file_move(merged_file_name, merged_path, scratch_pool),
+ SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
+ SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
+ scratch_pool),
apr_psprintf(scratch_pool,
_("Could not write merged result to '%s', saved "
"instead at '%s'.\n'%s' remains in conflict.\n"),
@@ -924,6 +927,9 @@
svn_dirent_local_style(
svn_dirent_skip_ancestor(path_prefix, wc_path),
scratch_pool)));
+ SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
+ scratch_pool));
+ SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
/* The merge was not aborted and we could install the merged result. The
* file remains in conflict unless all conflicting sections were resolved. */
diff --git a/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c
index 2ca24f1..216cb5d 100644
--- a/subversion/svn/log-cmd.c
+++ b/subversion/svn/log-cmd.c
@@ -71,10 +71,9 @@
/* Stack which keeps track of merge revision nesting, using svn_revnum_t's */
apr_array_header_t *merge_stack;
- /* Log message search pattern. Log entries will only be shown if the author,
- * the log message, or a changed path matches this pattern. */
- const char *search_pattern;
- svn_boolean_t case_insensitive_search;
+ /* Log message search patterns. Log entries will only be shown if the author,
+ * the log message, or a changed path matches one of these patterns. */
+ apr_array_header_t *search_patterns;
/* Pool for persistent allocations. */
apr_pool_t *pool;
@@ -156,12 +155,11 @@
const char *date,
const char *log_message,
apr_hash_t *changed_paths,
- svn_boolean_t case_insensitive_search,
apr_pool_t *pool)
{
/* Match any substring containing the pattern, like UNIX 'grep' does. */
const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
- int flags = (case_insensitive_search ? APR_FNM_CASE_BLIND : 0);
+ int flags = APR_FNM_CASE_BLIND;
/* Does the author match the search pattern? */
if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
@@ -203,6 +201,50 @@
return FALSE;
}
+/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
+ * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
+ * SCRACH_POOL is used for temporary allocations. */
+static svn_boolean_t
+match_search_patterns(apr_array_header_t *search_patterns,
+ const char *author,
+ const char *date,
+ const char *message,
+ apr_hash_t *changed_paths,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_boolean_t match = FALSE;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < search_patterns->nelts; i++)
+ {
+ apr_array_header_t *pattern_group;
+ int j;
+
+ pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
+
+ /* All patterns within the group must match. */
+ for (j = 0; j < pattern_group->nelts; j++)
+ {
+ const char *pattern;
+
+ svn_pool_clear(iterpool);
+
+ pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
+ match = match_search_pattern(pattern, author, date, message,
+ changed_paths, iterpool);
+ if (!match)
+ break;
+ }
+
+ match = (match && j == pattern_group->nelts);
+ if (match)
+ break;
+ }
+ svn_pool_destroy(iterpool);
+
+ return match;
+}
/* Implement `svn_log_entry_receiver_t', printing the logs in
* a human-readable and machine-parseable format.
@@ -320,10 +362,9 @@
if (! lb->omit_log_message && message == NULL)
message = "";
- if (lb->search_pattern &&
- ! match_search_pattern(lb->search_pattern, author, date, message,
- log_entry->changed_paths2,
- lb->case_insensitive_search, pool))
+ if (lb->search_patterns &&
+ ! match_search_patterns(lb->search_patterns, author, date, message,
+ log_entry->changed_paths2, pool))
{
if (log_entry->has_children)
APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
@@ -505,10 +546,9 @@
}
/* Match search pattern before XML-escaping. */
- if (lb->search_pattern &&
- ! match_search_pattern(lb->search_pattern, author, date, message,
- log_entry->changed_paths2,
- lb->case_insensitive_search, pool))
+ if (lb->search_patterns &&
+ ! match_search_patterns(lb->search_patterns, author, date, message,
+ log_entry->changed_paths2, pool))
{
if (log_entry->has_children)
APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
@@ -586,7 +626,13 @@
/* <path action="X"> */
svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
"action", action,
- "kind", svn_cl__node_kind_str_xml(log_item->node_kind), NULL);
+ "kind", svn_cl__node_kind_str_xml(
+ log_item->node_kind),
+ "text-mods", svn_tristate__to_word(
+ log_item->text_modified),
+ "prop-mods", svn_tristate__to_word(
+ log_item->props_modified),
+ NULL);
}
/* xxx</path> */
svn_xml_escape_cdata_cstring(&sb, path, pool);
@@ -609,7 +655,7 @@
svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
SVN_ERR(svn_cl__print_xml_prop_hash(&sb, log_entry->revprops,
FALSE, /* name_only */
- pool));
+ FALSE, pool));
svn_xml_make_close_tag(&sb, pool, "revprops");
}
@@ -740,8 +786,7 @@
: opt_state->depth;
lb.diff_extensions = opt_state->extensions;
lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t));
- lb.search_pattern = opt_state->search_pattern;
- lb.case_insensitive_search = opt_state->case_insensitive_search;
+ lb.search_patterns = opt_state->search_patterns;
lb.pool = pool;
if (opt_state->xml)
diff --git a/subversion/svn/main.c b/subversion/svn/main.c
index 26db55f..967c447 100644
--- a/subversion/svn/main.c
+++ b/subversion/svn/main.c
@@ -129,8 +129,9 @@
opt_diff,
opt_allow_mixed_revisions,
opt_include_externals,
+ opt_show_inherited_props,
opt_search,
- opt_isearch,
+ opt_search_and,
} svn_cl__longopt_t;
@@ -184,37 +185,23 @@
{"username", opt_auth_username, 1, N_("specify a username ARG")},
{"password", opt_auth_password, 1, N_("specify a password ARG")},
{"extensions", 'x', 1,
- N_("Default: '-u'. When Subversion is invoking an\n"
+ N_("Specify differencing options for external diff or\n"
" "
- "external diff program, ARG is simply passed along\n"
+ "internal diff or blame. Default: '-u'. Options are\n"
" "
- "to the program. But when Subversion is using its\n"
+ "separated by spaces. Internal diff and blame take:\n"
" "
- "default internal diff implementation, or when\n"
+ " -u, --unified: Show 3 lines of unified context\n"
" "
- "Subversion is displaying blame annotations, ARG\n"
+ " -b, --ignore-space-change: Ignore changes in\n"
" "
- "could be any of the following:\n"
+ " amount of white space\n"
" "
- " -u (--unified):\n"
+ " -w, --ignore-all-space: Ignore all white space\n"
" "
- " Output 3 lines of unified context.\n"
+ " --ignore-eol-style: Ignore changes in EOL style\n"
" "
- " -b (--ignore-space-change):\n"
- " "
- " Ignore changes in the amount of white space.\n"
- " "
- " -w (--ignore-all-space):\n"
- " "
- " Ignore all white space.\n"
- " "
- " --ignore-eol-style:\n"
- " "
- " Ignore changes in EOL style.\n"
- " "
- " -p (--show-c-function):\n"
- " "
- " Show C function name in diff output.")},
+ " -p, --show-c-function: Show C function name")},
{"targets", opt_targets, 1,
N_("pass contents of file ARG as additional args")},
{"depth", opt_depth, 1,
@@ -363,7 +350,7 @@
)},
/* end of diff options */
{"allow-mixed-revisions", opt_allow_mixed_revisions, 0,
- N_("Allow merge into mixed-revision working copy.\n"
+ N_("Allow operation on mixed-revision working copy.\n"
" "
"Use of this option is not recommended!\n"
" "
@@ -374,11 +361,12 @@
"recursion. This does not include externals with a\n"
" "
"fixed revision. (See the svn:externals property)")},
+ {"show-inherited-props", opt_show_inherited_props, 0,
+ N_("retrieve target's inherited properties")},
{"search", opt_search, 1,
N_("use ARG as search pattern (glob syntax)")},
-
- {"isearch", opt_isearch, 1,
- N_("like --search, but case-insensitive")},
+ {"search-and", opt_search_and, 1,
+ N_("combine ARG with the previous search pattern")},
/* Long-opt Aliases
*
@@ -500,25 +488,22 @@
opt_changelist, opt_keep_changelists, opt_include_externals} },
{ "copy", svn_cl__copy, {"cp"}, N_
- ("Duplicate something in working copy or repository, remembering\n"
- "history.\n"
+ ("Copy files and directories in a working copy or repository.\n"
"usage: copy SRC[@REV]... DST\n"
"\n"
- "When copying multiple sources, they will be added as children of DST,\n"
- "which must be a directory.\n"
- "\n"
" SRC and DST can each be either a working copy (WC) path or URL:\n"
" WC -> WC: copy and schedule for addition (with history)\n"
" WC -> URL: immediately commit a copy of WC to URL\n"
" URL -> WC: check out URL into WC, schedule for addition\n"
" URL -> URL: complete server-side copy; used to branch and tag\n"
- " All the SRCs must be of the same type.\n"
+ " All the SRCs must be of the same type. When copying multiple sources,\n"
+ " they will be added as children of DST, which must be a directory.\n"
"\n"
- "WARNING: For compatibility with previous versions of Subversion,\n"
- "copies performed using two working copy paths (WC -> WC) will not\n"
- "contact the repository. As such, they may not, by default, be able\n"
- "to propagate merge tracking information from the source of the copy\n"
- "to the destination.\n"),
+ " WARNING: For compatibility with previous versions of Subversion,\n"
+ " copies performed using two working copy paths (WC -> WC) will not\n"
+ " contact the repository. As such, they may not, by default, be able\n"
+ " to propagate merge tracking information from the source of the copy\n"
+ " to the destination.\n"),
{'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
{ "delete", svn_cl__delete, {"del", "remove", "rm"}, N_
@@ -684,12 +669,18 @@
" and limits the scope of the displayed diff to the specified depth.\n"
"\n"
" If the --search option is used, log messages are displayed only if the\n"
- " provided search pattern matches the author, date, log message text,\n"
- " or, if the --verbose option is also provided, a changed path.\n"
- " The search pattern may include glob syntax wildcards:\n"
+ " provided search pattern matches any of the author, date, log message\n"
+ " text (unless --quiet is used), or, if the --verbose option is also\n"
+ " provided, a changed path.\n"
+ " The search pattern may include \"glob syntax\" wildcards:\n"
" ? matches any single character\n"
" * matches a sequence of arbitrary characters\n"
- " [...] matches any of the characters listed inside the brackets\n"
+ " [abc] matches any of the characters listed inside the brackets\n"
+ " If multiple --search options are provided, a log message is shown if\n"
+ " it matches any of the provided search patterns. If the --search-and\n"
+ " option is used, that option's argument is combined with the pattern\n"
+ " from the previous --search or --search-and option, and a log message\n"
+ " is shown only if it matches the combined search pattern.\n"
" If --limit is used in combination with --search, --limit restricts the\n"
" number of log messages searched, rather than restricting the output\n"
" to a particular number of matching log messages.\n"
@@ -722,7 +713,7 @@
{'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy, opt_incremental,
opt_xml, 'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,
opt_depth, opt_diff, opt_diff_cmd, opt_internal_diff, 'x', opt_search,
- opt_isearch},
+ opt_search_and, },
{{opt_with_revprop, N_("retrieve revision property ARG")},
{'c', N_("the change made in revision ARG")}} },
@@ -732,19 +723,22 @@
* (with quotes and newlines removed). */
"Merge changes into a working copy.\n"
"usage: 1. merge SOURCE[@REV] [TARGET_WCPATH]\n"
-" (the 'sync' merge)\n"
+" (the 'automatic' merge)\n"
" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
" (the 'cherry-pick' merge)\n"
" 3. merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n"
" (the '2-URL' merge)\n"
"\n"
-" 1. This form, with one source path and no revision range:\n"
+" 1. This form, with one source path and no revision range, is called\n"
+" an 'automatic' merge:\n"
"\n"
" svn merge SOURCE[@REV] [TARGET_WCPATH]\n"
"\n"
-" finds all the changes on the source branch that have not already been\n"
-" merged to the target branch, and merges them. Merge tracking is used\n"
-" to know which changes have already been merged.\n"
+" The automatic merge is used for the 'sync' and 'reintegrate' merges\n"
+" in the 'feature branch' pattern described below. It finds all the\n"
+" changes on the source branch that have not already been merged to the\n"
+" target branch, and merges them into the working copy. Merge tracking\n"
+" is used to know which changes have already been merged.\n"
"\n"
" SOURCE specifies the branch from where the changes will be pulled, and\n"
" TARGET_WCPATH specifies a working copy of the target branch to which\n"
@@ -766,43 +760,38 @@
"\n"
" - The 'Feature Branch' Merging Pattern -\n"
"\n"
-" In this commonly used pattern of merging, a developer is working on\n"
-" a feature development branch, committing a series of changes that\n"
-" implement the feature. The developer periodically merges all the\n"
-" latest changes from the 'parent' branch (from which the feature branch\n"
-" is branched off). When the feature development is complete, the\n"
-" developer integrates the feature back into the parent branch by\n"
-" merging the other way, into a trunk working copy.\n"
+" In this commonly used work flow, known also as the 'development\n"
+" branch' pattern, a developer creates a branch and commits a series of\n"
+" changes that implement a new feature. The developer periodically\n"
+" merges all the latest changes from the parent branch so as to keep the\n"
+" development branch up to date with those changes. When the feature is\n"
+" complete, the developer performs a merge from the feature branch to\n"
+" the parent branch to re-integrate the changes.\n"
"\n"
-" trunk --+----------o------o-o-------------o--\n"
-" \\ \\ \\ /\n"
-" \\ merge merge merge\n"
-" \\ \\ \\ /\n"
+" parent --+----------o------o-o-------------o--\n"
+" \\ \\ \\ /\n"
+" \\ merge merge merge\n"
+" \\ \\ \\ /\n"
" feature +--o-o-------o----o-o----o-------\n"
"\n"
-" In this pattern, a merge from the parent branch to the feature branch\n"
-" is known as a 'sync' merge (or 'catch-up' merge), and a merge from the\n"
-" feature branch to the parent branch may be called a 'reintegrate'\n"
-" merge. The 'sync' merges are normally low-risk because the parent\n"
-" branch is considered to be more 'stable' than the feature branch, in\n"
-" the sense of being less likely to contain incomplete or broken work.\n"
-" By syncing often, these merges can be kept small, avoiding the need\n"
-" for a difficult 'big bang' merge at reintegration time.\n"
+" A merge from the parent branch to the feature branch is called a\n"
+" 'sync' or 'catch-up' merge, and a merge from the feature branch to the\n"
+" parent branch is called a 'reintegrate' merge.\n"
"\n"
" - Sync Merge Example -\n"
" ............\n"
" . .\n"
" trunk --+------------L--------------R------\n"
-" \\ \\\n"
-" \\ |\n"
-" \\ v\n"
+" \\ \\\n"
+" \\ |\n"
+" \\ v\n"
" feature +------------------------o-----\n"
" r100 r200\n"
"\n"
" Subversion will locate all the changes on 'trunk' that have not yet\n"
" been merged into the 'feature' branch. In this case that is a single\n"
-" range, r100:200. In the diagram above, L marks the left side\n"
-" (trunk@100) and R marks the right side (trunk@200) of the merge. The\n"
+" range, r100:200. In the diagram above, L marks the left side (trunk@100)\n"
+" and R marks the right side (trunk@200) of the merge source. The\n"
" difference between L and R will be applied to the target working copy\n"
" path. In this case, the working copy is a clean checkout of the entire\n"
" 'feature' branch.\n"
@@ -817,7 +806,6 @@
" others. You can review the changes and you may have to resolve\n"
" conflicts before you commit the merge.\n"
"\n"
-"\n"
" - Reintegrate Merge Example -\n"
"\n"
" The feature branch was last synced with trunk up to revision X. So the\n"
@@ -827,9 +815,9 @@
"\n"
" rW rX\n"
" trunk ------+--------------------L------------------o\n"
-" \\ . ^\n"
-" \\ ............. /\n"
-" \\ . /\n"
+" \\ . ^\n"
+" \\ ............. /\n"
+" \\ . /\n"
" feature +--------------------------------R\n"
"\n"
" In the diagram above, L marks the left side (trunk@X) and R marks the\n"
@@ -1048,17 +1036,31 @@
{ "mergeinfo", svn_cl__mergeinfo, {0}, N_
("Display merge-related information.\n"
- "usage: mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
+ "usage: 1. mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
+ " 2. mergeinfo --show-revs=WHICH SOURCE[@REV] [TARGET[@REV]]\n"
"\n"
- " Display information related to merges (or potential merges) between\n"
- " SOURCE and TARGET (default: '.'). Display the type of information\n"
- " specified by the --show-revs option. If --show-revs isn't passed,\n"
- " it defaults to --show-revs='merged'.\n"
+ " 1. Summarize the history of merging between SOURCE and TARGET. The graph\n"
+ " shows, from left to right:\n"
+ " the youngest common ancestor of the branches;\n"
+ " the latest full merge in either direction, and thus the common base\n"
+ " that will be used for the next automatic merge;\n"
+ " the repository path and revision number of the tip of each branch.\n"
"\n"
- " If --revision (-r) is provided, filter the displayed information to\n"
- " show only that which is associated with the revisions within the\n"
- " specified range. Revision numbers, dates, and the 'HEAD' keyword are\n"
- " valid range values.\n"
+ " 2. Print the revision numbers on SOURCE that have been merged to TARGET\n"
+ " (with --show-revs=merged), or that have not been merged to TARGET\n"
+ " (with --show-revs=eligible). Print only revisions in which there was\n"
+ " at least one change in SOURCE.\n"
+ "\n"
+ " If --revision (-r) is provided, filter the displayed information to\n"
+ " show only that which is associated with the revisions within the\n"
+ " specified range. Revision numbers, dates, and the 'HEAD' keyword are\n"
+ " valid range values.\n"
+ "\n"
+ " SOURCE and TARGET are the source and target branch URLs, respectively.\n"
+ " (If a WC path is given, the corresponding base URL is used.) The default\n"
+ " TARGET is the current working directory ('.'). REV specifies the revision\n"
+ " to be considered the tip of the branch; the default for SOURCE is HEAD,\n"
+ " and the default for TARGET is HEAD for a URL or BASE for a WC path.\n"
"\n"
" The depth can be 'empty' or 'infinity'; the default is 'empty'.\n"),
{'r', 'R', opt_depth, opt_show_revs} },
@@ -1081,20 +1083,26 @@
{'q', opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
{ "move", svn_cl__move, {"mv", "rename", "ren"}, N_
- ("Move and/or rename something in working copy or repository.\n"
+ ("Move (rename) an item in a working copy or repository.\n"
"usage: move SRC... DST\n"
"\n"
- "When moving multiple sources, they will be added as children of DST,\n"
- "which must be a directory.\n"
- "\n"
- " Note: this subcommand is equivalent to a 'copy' and 'delete'.\n"
- " Note: the --revision option has no use and is deprecated.\n"
- "\n"
" SRC and DST can both be working copy (WC) paths or URLs:\n"
- " WC -> WC: move and schedule for addition (with history)\n"
- " URL -> URL: complete server-side rename.\n"
- " All the SRCs must be of the same type.\n"),
- {'r', 'q', opt_force, opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+ " WC -> WC: move an item in a working copy, as a local change to\n"
+ " be committed later (with or without further changes)\n"
+ " URL -> URL: move an item in the repository directly, immediately\n"
+ " creating a new revision in the repository\n"
+ " All the SRCs must be of the same type. When moving multiple sources,\n"
+ " they will be added as children of DST, which must be a directory.\n"
+ "\n"
+ " SRC and DST of WC -> WC moves must be committed in the same revision.\n"
+ " Furthermore, WC -> WC moves will refuse to move a mixed-revision subtree.\n"
+ " To avoid unnecessary conflicts, it is recommended to run 'svn update'\n"
+ " to update the subtree to a single revision before moving it.\n"
+ " The --allow-mixed-revisions option is provided for backward compatibility.\n"
+ "\n"
+ " The --revision option has no use and is deprecated.\n"),
+ {'r', 'q', opt_force, opt_parents, opt_allow_mixed_revisions,
+ SVN_CL__LOG_MSG_OPTIONS} },
{ "patch", svn_cl__patch, {0}, N_
("Apply a patch to a working copy.\n"
@@ -1162,7 +1170,7 @@
" 2. Edits unversioned remote prop on repos revision.\n"
" TARGET only determines which repository to access.\n"
"\n"
- "See 'svn help propset' for more on setting properties.\n"),
+ " See 'svn help propset' for more on setting properties.\n"),
{'r', opt_revprop, SVN_CL__LOG_MSG_OPTIONS, opt_force} },
{ "propget", svn_cl__propget, {"pget", "pg"}, N_
@@ -1185,7 +1193,7 @@
" use the --strict option to disable this (useful when redirecting a binary\n"
" property value to a file, for example).\n"),
{'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml,
- opt_changelist },
+ opt_changelist, opt_show_inherited_props },
{{'v', N_("print path, name and value on separate lines")},
{opt_strict, N_("don't print an extra newline")}} },
@@ -1201,7 +1209,8 @@
"\n"
" With --verbose, the property values are printed as well, like 'svn propget\n"
" --verbose'. With --quiet, the paths are not printed.\n"),
- {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist },
+ {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist,
+ opt_show_inherited_props },
{{'v', N_("print path, name and value on separate lines")},
{'q', N_("don't print the path")}} },
@@ -1579,11 +1588,50 @@
return SVN_NO_ERROR;
}
+/* Add a --search argument to OPT_STATE.
+ * These options start a new search pattern group. */
+static void
+add_search_pattern_group(svn_cl__opt_state_t *opt_state,
+ const char *pattern,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *group = NULL;
+
+ if (opt_state->search_patterns == NULL)
+ opt_state->search_patterns = apr_array_make(result_pool, 1,
+ sizeof(apr_array_header_t *));
+
+ group = apr_array_make(result_pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(group, const char *) = pattern;
+ APR_ARRAY_PUSH(opt_state->search_patterns, apr_array_header_t *) = group;
+}
+
+/* Add a --search-and argument to OPT_STATE.
+ * These patterns are added to an existing pattern group, if any. */
+static void
+add_search_pattern_to_latest_group(svn_cl__opt_state_t *opt_state,
+ const char *pattern,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *group;
+
+ if (opt_state->search_patterns == NULL)
+ {
+ add_search_pattern_group(opt_state, pattern, result_pool);
+ return;
+ }
+
+ group = APR_ARRAY_IDX(opt_state->search_patterns,
+ opt_state->search_patterns->nelts - 1,
+ apr_array_header_t *);
+ APR_ARRAY_PUSH(group, const char *) = pattern;
+}
/*** Main. ***/
-/* Report and clear the error ERR, and return EXIT_FAILURE. */
+/* Report and clear the error ERR, and return EXIT_FAILURE. Suppress the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
#define EXIT_ERROR(err) \
svn_cmdline_handle_exit_error(err, NULL, "svn: ")
@@ -1616,6 +1664,8 @@
svn_boolean_t interactive_conflicts = FALSE;
svn_boolean_t use_notifier = TRUE;
apr_hash_t *changelists;
+ const char *sqlite_exclusive;
+ apr_hash_t *cfg_hash;
received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
@@ -1644,7 +1694,7 @@
opt_state.depth = svn_depth_unknown;
opt_state.set_depth = svn_depth_unknown;
opt_state.accept_which = svn_cl__accept_unspecified;
- opt_state.show_revs = svn_cl__show_revs_merged;
+ opt_state.show_revs = svn_cl__show_revs_invalid;
/* No args? Show usage. */
if (argc <= 1)
@@ -2126,16 +2176,17 @@
case opt_include_externals:
opt_state.include_externals = TRUE;
break;
+ case opt_show_inherited_props:
+ opt_state.show_inherited_props = TRUE;
+ break;
case opt_properties_only:
opt_state.diff.properties_only = TRUE;
break;
case opt_search:
- opt_state.search_pattern = opt_arg;
+ add_search_pattern_group(&opt_state, opt_arg, pool);
break;
- case opt_isearch:
- opt_state.search_pattern = opt_arg;
- opt_state.case_insensitive_search = TRUE;
- break;
+ case opt_search_and:
+ add_search_pattern_to_latest_group(&opt_state, opt_arg, pool);
default:
/* Hmmm. Perhaps this would be a good place to squirrel away
opts that commands like svn diff might need. Hmmm indeed. */
@@ -2336,14 +2387,111 @@
opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
svn_opt_revision_range_t *)->end;
+ err = svn_config_get_config(&cfg_hash, opt_state.config_dir, pool);
+ if (err)
+ {
+ /* Fallback to default config if the config directory isn't readable
+ or is not a directory. */
+ if (APR_STATUS_IS_EACCES(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ cfg_hash = NULL;
+ }
+ else
+ return EXIT_ERROR(err);
+ }
+
/* Create a client context object. */
command_baton.opt_state = &opt_state;
- SVN_INT_ERR(svn_client_create_context(&ctx, pool));
+ SVN_INT_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
command_baton.ctx = ctx;
+ /* Relocation is infinite-depth only. */
+ if (opt_state.relocate)
+ {
+ if (opt_state.depth != svn_depth_unknown)
+ {
+ err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--relocate and --depth are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+ if (! descend)
+ {
+ err = svn_error_create(
+ SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--relocate and --non-recursive (-N) are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* Only a few commands can accept a revision range; the rest can take at
+ most one revision number. */
+ if (subcommand->cmd_func != svn_cl__blame
+ && subcommand->cmd_func != svn_cl__diff
+ && subcommand->cmd_func != svn_cl__log
+ && subcommand->cmd_func != svn_cl__mergeinfo
+ && subcommand->cmd_func != svn_cl__merge)
+ {
+ if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* -N has a different meaning depending on the command */
+ if (descend == FALSE)
+ {
+ if (subcommand->cmd_func == svn_cl__status)
+ {
+ opt_state.depth = svn_depth_immediates;
+ }
+ else if (subcommand->cmd_func == svn_cl__revert
+ || subcommand->cmd_func == svn_cl__add
+ || subcommand->cmd_func == svn_cl__commit)
+ {
+ /* In pre-1.5 Subversion, some commands treated -N like
+ --depth=empty, so force that mapping here. Anyway, with
+ revert it makes sense to be especially conservative,
+ since revert can lose data. */
+ opt_state.depth = svn_depth_empty;
+ }
+ else
+ {
+ opt_state.depth = svn_depth_files;
+ }
+ }
+
+ cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+
+ /* Update the options in the config */
+ if (opt_state.config_options)
+ {
+ svn_error_clear(
+ svn_cmdline__apply_config_options(ctx->config,
+ opt_state.config_options,
+ "svn: ", "--config-option"));
+ }
+
+ svn_config_get(cfg_config, &sqlite_exclusive,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ NULL);
+ if (!sqlite_exclusive)
+ svn_config_set(cfg_config,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ "true");
+
/* If we're running a command that could result in a commit, verify
that any log message we were given on the command line makes
- sense (unless we've also been instructed not to care). */
+ sense (unless we've also been instructed not to care). This may
+ access the working copy so do it after setting the locking mode. */
if ((! opt_state.force_log)
&& (subcommand->cmd_func == svn_cl__commit
|| subcommand->cmd_func == svn_cl__copy
@@ -2418,92 +2566,6 @@
}
}
- /* Relocation is infinite-depth only. */
- if (opt_state.relocate)
- {
- if (opt_state.depth != svn_depth_unknown)
- {
- err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--relocate and --depth are mutually "
- "exclusive"));
- return EXIT_ERROR(err);
- }
- if (! descend)
- {
- err = svn_error_create(
- SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--relocate and --non-recursive (-N) are mutually "
- "exclusive"));
- return EXIT_ERROR(err);
- }
- }
-
- /* Only a few commands can accept a revision range; the rest can take at
- most one revision number. */
- if (subcommand->cmd_func != svn_cl__blame
- && subcommand->cmd_func != svn_cl__diff
- && subcommand->cmd_func != svn_cl__log
- && subcommand->cmd_func != svn_cl__mergeinfo
- && subcommand->cmd_func != svn_cl__merge)
- {
- if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
- {
- err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
- return EXIT_ERROR(err);
- }
- }
-
- /* -N has a different meaning depending on the command */
- if (descend == FALSE)
- {
- if (subcommand->cmd_func == svn_cl__status)
- {
- opt_state.depth = svn_depth_immediates;
- }
- else if (subcommand->cmd_func == svn_cl__revert
- || subcommand->cmd_func == svn_cl__add
- || subcommand->cmd_func == svn_cl__commit)
- {
- /* In pre-1.5 Subversion, some commands treated -N like
- --depth=empty, so force that mapping here. Anyway, with
- revert it makes sense to be especially conservative,
- since revert can lose data. */
- opt_state.depth = svn_depth_empty;
- }
- else
- {
- opt_state.depth = svn_depth_files;
- }
- }
-
- err = svn_config_get_config(&(ctx->config),
- opt_state.config_dir, pool);
- if (err)
- {
- /* Fallback to default config if the config directory isn't readable
- or is not a directory. */
- if (APR_STATUS_IS_EACCES(err->apr_err)
- || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
- {
- svn_handle_warning2(stderr, err, "svn: ");
- svn_error_clear(err);
- }
- else
- return EXIT_ERROR(err);
- }
-
- cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING);
-
- /* Update the options in the config */
- if (opt_state.config_options)
- {
- svn_error_clear(
- svn_cmdline__apply_config_options(ctx->config,
- opt_state.config_options,
- "svn: ", "--config-option"));
- }
-
/* XXX: Only diff_cmd for now, overlay rest later and stop passing
opt_state altogether? */
if (opt_state.diff.diff_cmd)
@@ -2620,60 +2682,53 @@
ctx->auth_baton = ab;
- /* Set up conflict resolution callback. */
+ /* Install the default conflict handler which postpones all conflicts
+ * and remembers the list of conflicted paths to be resolved later.
+ * This is overridden only within the 'resolve' subcommand. */
+ ctx->conflict_func = NULL;
+ ctx->conflict_baton = NULL;
+ ctx->conflict_func2 = svn_cl__conflict_func_postpone;
+ ctx->conflict_baton2 = svn_cl__get_conflict_func_postpone_baton(pool);
+
+ if (opt_state.non_interactive)
+ {
+ if (opt_state.accept_which == svn_cl__accept_edit)
+ return EXIT_ERROR(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=%s incompatible with"
+ " --non-interactive"),
+ SVN_CL__ACCEPT_EDIT));
+
+ if (opt_state.accept_which == svn_cl__accept_launch)
+ return EXIT_ERROR(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=%s incompatible with"
+ " --non-interactive"),
+ SVN_CL__ACCEPT_LAUNCH));
+
+ /* The default action when we're non-interactive is to postpone
+ * conflict resolution. */
+ if (opt_state.accept_which == svn_cl__accept_unspecified)
+ opt_state.accept_which = svn_cl__accept_postpone;
+ }
+
+ /* Check whether interactive conflict resolution is disabled by
+ * the configuration file. If no --accept option was specified
+ * we postpone all conflicts in this case. */
SVN_INT_ERR(svn_config_get_bool(cfg_config, &interactive_conflicts,
SVN_CONFIG_SECTION_MISCELLANY,
SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS,
- TRUE)); /* ### interactivity on by default.
- we can change this. */
-
- /* The new svn behavior is to postpone everything until after the operation
- completed */
- ctx->conflict_func = NULL;
- ctx->conflict_baton = NULL;
- ctx->conflict_func2 = NULL;
- ctx->conflict_baton2 = NULL;
-
- if ((opt_state.accept_which == svn_cl__accept_unspecified
- && (!interactive_conflicts || opt_state.non_interactive))
- || opt_state.accept_which == svn_cl__accept_postpone)
+ TRUE));
+ if (!interactive_conflicts)
{
- /* If no --accept option at all and we're non-interactive, we're
- leaving the conflicts behind, so don't need the callback. Same if
- the user said to postpone. */
- opt_state.conflict_func = NULL;
- opt_state.conflict_baton = NULL;
- }
- else
- {
- svn_cl__conflict_baton_t * conflict_baton2;
- svn_cmdline_prompt_baton_t *pb = apr_palloc(pool, sizeof(*pb));
- pb->cancel_func = ctx->cancel_func;
- pb->cancel_baton = ctx->cancel_baton;
+ /* Make 'svn resolve' non-interactive. */
+ if (subcommand->cmd_func == svn_cl__resolve)
+ opt_state.non_interactive = TRUE;
- if (opt_state.non_interactive)
- {
- if (opt_state.accept_which == svn_cl__accept_edit)
- return EXIT_ERROR
- (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("--accept=%s incompatible with"
- " --non-interactive"), SVN_CL__ACCEPT_EDIT));
- if (opt_state.accept_which == svn_cl__accept_launch)
- return EXIT_ERROR
- (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("--accept=%s incompatible with"
- " --non-interactive"),
- SVN_CL__ACCEPT_LAUNCH));
- }
-
- opt_state.conflict_func = svn_cl__conflict_handler;
- SVN_INT_ERR(svn_cl__conflict_baton_make(&conflict_baton2,
- opt_state.accept_which,
- ctx->config,
- opt_state.editor_cmd,
- pb,
- pool));
- opt_state.conflict_baton = conflict_baton2;
+ /* We're not resolving conflicts interactively. If no --accept option
+ * was provided the default behaviour is to postpone all conflicts. */
+ if (opt_state.accept_which == svn_cl__accept_unspecified)
+ opt_state.accept_which = svn_cl__accept_postpone;
}
/* And now we finally run the subcommand. */
diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c
index c1af5f2..222471d 100644
--- a/subversion/svn/merge-cmd.c
+++ b/subversion/svn/merge-cmd.c
@@ -104,9 +104,9 @@
return SVN_NO_ERROR;
}
-/* Symmetric, merge-tracking merge, used for sync or reintegrate purposes. */
+/* Automatic, merge-tracking merge, used for sync or reintegrate purposes. */
static svn_error_t *
-symmetric_merge(const char *source_path_or_url,
+automatic_merge(const char *source_path_or_url,
const svn_opt_revision_t *source_revision,
const char *target_wcpath,
svn_depth_t depth,
@@ -120,19 +120,16 @@
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
- svn_client__symmetric_merge_t *merge;
- svn_boolean_t reintegrate_like;
+ svn_client_automatic_merge_t *merge;
/* Find the 3-way merges needed (and check suitability of the WC). */
- SVN_ERR(svn_client__find_symmetric_merge(&merge,
- source_path_or_url, source_revision,
- target_wcpath, allow_mixed_rev,
- allow_local_mods, allow_switched_subtrees,
- ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client_find_automatic_merge(&merge,
+ source_path_or_url, source_revision,
+ target_wcpath, allow_mixed_rev,
+ allow_local_mods, allow_switched_subtrees,
+ ctx, scratch_pool, scratch_pool));
- reintegrate_like = (merge->mid != NULL);
-
- if (reintegrate_like)
+ if (svn_client_automatic_merge_is_reintegrate_like(merge))
{
if (record_only)
return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
@@ -160,10 +157,10 @@
}
/* Perform the 3-way merges */
- SVN_ERR(svn_client__do_symmetric_merge(merge, target_wcpath, depth,
- force, record_only,
- dry_run, merge_options,
- ctx, scratch_pool));
+ SVN_ERR(svn_client_do_automatic_merge(merge, target_wcpath, depth,
+ force, record_only,
+ dry_run, merge_options,
+ ctx, scratch_pool));
return SVN_NO_ERROR;
}
@@ -431,7 +428,7 @@
/* Postpone conflict resolution during the merge operation.
* If any conflicts occur we'll run the conflict resolver later. */
- /* Do a symmetric merge if just one source and no revisions. */
+ /* Do an automatic merge if just one source and no revisions. */
if ((! two_sources_specified)
&& (! opt_state->reintegrate)
&& (! opt_state->ignore_ancestry)
@@ -443,7 +440,7 @@
ctx, pool),
_("Source and target must be different but related branches"));
- merge_err = symmetric_merge(sourcepath1, &peg_revision1, targetpath,
+ merge_err = automatic_merge(sourcepath1, &peg_revision1, targetpath,
opt_state->depth,
opt_state->force,
opt_state->record_only,
@@ -524,15 +521,12 @@
if (! opt_state->quiet)
err = svn_cl__print_conflict_stats(ctx->notify_baton2, pool);
- if (!err
- && opt_state->conflict_func
- && svn_cl__notifier_check_conflicts(ctx->notify_baton2))
- {
- err = svn_cl__resolve_conflicts(
- svn_cl__notifier_get_conflicted_paths(ctx->notify_baton2, pool),
- opt_state->depth, opt_state, ctx, pool);
- }
-
+ if (!err)
+ err = svn_cl__resolve_postponed_conflicts(ctx->conflict_baton2,
+ opt_state->depth,
+ opt_state->accept_which,
+ opt_state->editor_cmd,
+ ctx, pool);
if (merge_err)
{
if (merge_err->apr_err ==
diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c
index eee6e7e..aea307f 100644
--- a/subversion/svn/mergeinfo-cmd.c
+++ b/subversion/svn/mergeinfo-cmd.c
@@ -37,6 +37,7 @@
#include "cl.h"
#include "svn_private_config.h"
+#include "private/svn_client_private.h"
/*** Code. ***/
@@ -55,6 +56,187 @@
return SVN_NO_ERROR;
}
+/* Draw a diagram (by printing text to the console) summarizing the state
+ * of merging between two branches, given the merge description
+ * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */
+static svn_error_t *
+mergeinfo_diagram(svn_client__pathrev_t *yca,
+ svn_client__pathrev_t *base,
+ svn_client__pathrev_t *right,
+ svn_client__pathrev_t *target,
+ svn_boolean_t target_is_wc,
+ svn_boolean_t reintegrate_like,
+ apr_pool_t *pool)
+{
+ /* The graph occupies 4 rows of text, and the annotations occupy
+ * another 2 rows above and 2 rows below. The graph is constructed
+ * from left to right in discrete sections ("columns"), each of which
+ * can have a different width (measured in characters). Each element in
+ * the array is either a text string of the appropriate width, or can
+ * be NULL to draw a blank cell. */
+#define ROWS 8
+#define COLS 4
+ const char *g[ROWS][COLS] = {{0}};
+ int col_width[COLS];
+ int row, col;
+
+ /* The YCA (that is, the branching point). And an ellipsis, because we
+ * don't show information about earlier merges */
+ g[0][0] = apr_psprintf(pool, " %-8ld ", yca->rev);
+ g[1][0] = " | ";
+ if (strcmp(yca->url, right->url) == 0)
+ {
+ g[2][0] = "-------| |--";
+ g[3][0] = " \\ ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+ else if (strcmp(yca->url, target->url) == 0)
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = " / ";
+ g[4][0] = " / ";
+ g[5][0] = "-------| |--";
+ }
+ else
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = "... / ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+
+ /* The last full merge */
+ if ((base->rev > yca->rev) && reintegrate_like)
+ {
+ g[2][2] = "---------";
+ g[3][2] = " / ";
+ g[4][2] = " / ";
+ g[5][2] = "---------";
+ g[6][2] = "| ";
+ g[7][2] = apr_psprintf(pool, "%-8ld ", base->rev);
+ }
+ else if (base->rev > yca->rev)
+ {
+ g[0][2] = apr_psprintf(pool, "%-8ld ", base->rev);
+ g[1][2] = "| ";
+ g[2][2] = "---------";
+ g[3][2] = " \\ ";
+ g[4][2] = " \\ ";
+ g[5][2] = "---------";
+ }
+ else
+ {
+ g[2][2] = "---------";
+ g[3][2] = " ";
+ g[4][2] = " ";
+ g[5][2] = "---------";
+ }
+
+ /* The tips of the branches */
+ {
+ g[0][3] = apr_psprintf(pool, "%-8ld", right->rev);
+ g[1][3] = "| ";
+ g[2][3] = "- ";
+ g[3][3] = " ";
+ g[4][3] = " ";
+ g[5][3] = "- ";
+ g[6][3] = "| ";
+ g[7][3] = target_is_wc ? "WC "
+ : apr_psprintf(pool, "%-8ld", target->rev);
+ }
+
+ /* Find the width of each column, so we know how to print blank cells */
+ for (col = 0; col < COLS; col++)
+ {
+ col_width[col] = 0;
+ for (row = 0; row < ROWS; row++)
+ {
+ if (g[row][col] && (strlen(g[row][col]) > col_width[col]))
+ col_width[col] = strlen(g[row][col]);
+ }
+ }
+
+ /* Column headings */
+ SVN_ERR(svn_cmdline_fputs(
+ _(" youngest last repos.\n"
+ " common full tip of path of\n"
+ " ancestor merge branch branch\n"
+ "\n"),
+ stdout, pool));
+
+ /* Print the diagram, row by row */
+ for (row = 0; row < ROWS; row++)
+ {
+ SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
+ for (col = 0; col < COLS; col++)
+ {
+ if (g[row][col])
+ {
+ SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool));
+ }
+ else
+ {
+ /* Print <column-width> spaces */
+ SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], ""));
+ }
+ }
+ if (row == 2)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_client__pathrev_relpath(right, pool)));
+ if (row == 5)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_client__pathrev_relpath(target, pool)));
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Display a summary of the state of merging between the two branches
+ * SOURCE_PATH_OR_URL@SOURCE_REVISION and
+ * TARGET_PATH_OR_URL@TARGET_REVISION. */
+static svn_error_t *
+mergeinfo_summary(
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_client_automatic_merge_t *the_merge;
+ svn_client__pathrev_t *yca, *base, *right, *target;
+ svn_boolean_t target_is_wc, reintegrate_like;
+
+ target_is_wc = (! svn_path_is_url(target_path_or_url))
+ && (target_revision->kind == svn_opt_revision_unspecified
+ || target_revision->kind == svn_opt_revision_working);
+ if (target_is_wc)
+ SVN_ERR(svn_client_find_automatic_merge(
+ &the_merge,
+ source_path_or_url, source_revision,
+ target_path_or_url,
+ TRUE, TRUE, TRUE, /* allow_* */
+ ctx, pool, pool));
+ else
+ SVN_ERR(svn_client_find_automatic_merge_no_wc(
+ &the_merge,
+ source_path_or_url, source_revision,
+ target_path_or_url, target_revision,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_client__automatic_merge_get_locations(
+ &yca, &base, &right, &target, the_merge, pool));
+ reintegrate_like = svn_client_automatic_merge_is_reintegrate_like(the_merge);
+
+ SVN_ERR(mergeinfo_diagram(yca, base, right, target,
+ target_is_wc, reintegrate_like,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
/* This implements the `svn_opt_subcommand_t' interface. */
svn_error_t *
svn_cl__mergeinfo(apr_getopt_t *os,
@@ -74,20 +256,15 @@
opt_state->targets,
ctx, FALSE, pool));
- /* We expect a single source URL followed by a single target --
- nothing more, nothing less. */
+ /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */
if (targets->nelts < 1)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Not enough arguments given"));
if (targets->nelts > 2)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Too many arguments given"));
-
- /* Parse the SOURCE-URL[@REV] argument. */
SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
APR_ARRAY_IDX(targets, 0, const char *), pool));
-
- /* Parse the TARGET[@REV] argument (if provided). */
if (targets->nelts == 2)
{
SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
@@ -101,11 +278,15 @@
}
/* If no peg-rev was attached to the source URL, assume HEAD. */
+ /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use
+ * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */
if (src_peg_revision.kind == svn_opt_revision_unspecified)
src_peg_revision.kind = svn_opt_revision_head;
/* If no peg-rev was attached to a URL target, then assume HEAD; if
no peg-rev was attached to a non-URL target, then assume BASE. */
+ /* ### But we would like to be able to examine a working copy with an
+ uncommitted merge in it, so change this to use WORKING not BASE? */
if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
{
if (svn_path_is_url(target))
@@ -140,5 +321,11 @@
TRUE, depth, NULL, ctx,
pool));
}
+ else
+ {
+ SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
+ target, &tgt_peg_revision,
+ ctx, pool));
+ }
return SVN_NO_ERROR;
}
diff --git a/subversion/svn/move-cmd.c b/subversion/svn/move-cmd.c
index 795870a..4f5b4bb 100644
--- a/subversion/svn/move-cmd.c
+++ b/subversion/svn/move-cmd.c
@@ -84,8 +84,10 @@
SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
- err = svn_client_move6(targets, dst_path,
- TRUE, opt_state->parents, opt_state->revprop_table,
+ err = svn_client_move7(targets, dst_path,
+ TRUE, opt_state->parents,
+ opt_state->allow_mixed_rev,
+ opt_state->revprop_table,
(opt_state->quiet ? NULL : svn_cl__print_commit_info),
NULL, ctx, pool);
diff --git a/subversion/svn/notify.c b/subversion/svn/notify.c
index cd0d1ae..6933b04 100644
--- a/subversion/svn/notify.c
+++ b/subversion/svn/notify.c
@@ -58,7 +58,6 @@
unsigned int prop_conflicts;
unsigned int tree_conflicts;
unsigned int skipped_paths;
- apr_hash_t *conflicted_paths;
/* The cwd, for use in decomposing absolute paths. */
const char *path_prefix;
@@ -102,16 +101,6 @@
return SVN_NO_ERROR;
}
-/* Add a conflicted path to the list of conflicted paths stored
- * in the notify baton. */
-static void
-add_conflicted_path(struct notify_baton *nb, const char *path)
-{
- apr_hash_set(nb->conflicted_paths,
- apr_pstrdup(apr_hash_pool_get(nb->conflicted_paths), path),
- APR_HASH_KEY_STRING, "");
-}
-
/* This implements `svn_wc_notify_func2_t'.
* NOTE: This function can't fail, so we just ignore any print errors. */
static void
@@ -231,7 +220,6 @@
if (n->content_state == svn_wc_notify_state_conflicted)
{
nb->text_conflicts++;
- add_conflicted_path(nb, n->path);
if ((err = svn_cmdline_printf(pool, "C %s\n", path_local)))
goto print_error;
}
@@ -247,7 +235,6 @@
if (n->content_state == svn_wc_notify_state_conflicted)
{
nb->text_conflicts++;
- add_conflicted_path(nb, n->path);
statchar_buf[0] = 'C';
}
else
@@ -256,7 +243,6 @@
if (n->prop_state == svn_wc_notify_state_conflicted)
{
nb->prop_conflicts++;
- add_conflicted_path(nb, n->path);
statchar_buf[1] = 'C';
}
else if (n->prop_state == svn_wc_notify_state_merged)
@@ -323,7 +309,6 @@
if (n->content_state == svn_wc_notify_state_conflicted)
{
nb->text_conflicts++;
- add_conflicted_path(nb, n->path);
statchar_buf[0] = 'C';
}
else if (n->kind == svn_node_file)
@@ -337,7 +322,6 @@
if (n->prop_state == svn_wc_notify_state_conflicted)
{
nb->prop_conflicts++;
- add_conflicted_path(nb, n->path);
statchar_buf[1] = 'C';
}
else if (n->prop_state == svn_wc_notify_state_changed)
@@ -531,7 +515,6 @@
if (n->content_state == svn_wc_notify_state_conflicted)
{
nb->text_conflicts++;
- add_conflicted_path(nb, n->path);
statchar_buf[0] = 'C';
}
else if (n->kind == svn_node_file)
@@ -545,7 +528,6 @@
if (n->prop_state == svn_wc_notify_state_conflicted)
{
nb->prop_conflicts++;
- add_conflicted_path(nb, n->path);
statchar_buf[1] = 'C';
}
else if (n->prop_state == svn_wc_notify_state_merged)
@@ -923,7 +905,6 @@
case svn_wc_notify_tree_conflict:
nb->tree_conflicts++;
- add_conflicted_path(nb, n->path);
if ((err = svn_cmdline_printf(pool, " C %s\n", path_local)))
goto print_error;
break;
@@ -1064,7 +1045,6 @@
nb->prop_conflicts = 0;
nb->tree_conflicts = 0;
nb->skipped_paths = 0;
- nb->conflicted_paths = apr_hash_make(pool);
SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
*notify_func_p = notify;
@@ -1112,36 +1092,3 @@
if (nwb->wrapped_func)
nwb->wrapped_func(nwb->wrapped_baton, n, pool);
}
-
-svn_boolean_t
-svn_cl__notifier_check_conflicts(void *baton)
-{
- struct notify_baton *nb = baton;
-
- return (nb->text_conflicts || nb->prop_conflicts || nb->tree_conflicts);
-}
-
-apr_array_header_t *
-svn_cl__notifier_get_conflicted_paths(void *baton, apr_pool_t *result_pool)
-{
- struct notify_baton *nb = baton;
- apr_array_header_t *sorted_array;
- apr_array_header_t *result_array;
- int i;
-
- sorted_array = svn_sort__hash(nb->conflicted_paths,
- svn_sort_compare_items_as_paths,
- apr_hash_pool_get(nb->conflicted_paths));
- result_array = apr_array_make(result_pool, sorted_array->nelts,
- sizeof(const char *));
- for (i = 0; i < sorted_array->nelts; i++)
- {
- svn_sort__item_t item;
-
- item = APR_ARRAY_IDX(sorted_array, i, svn_sort__item_t);
- APR_ARRAY_PUSH(result_array, const char *) = apr_pstrdup(result_pool,
- item.key);
- }
-
- return result_array;
-}
diff --git a/subversion/svn/propedit-cmd.c b/subversion/svn/propedit-cmd.c
index 7c87fef..245cce5 100644
--- a/subversion/svn/propedit-cmd.c
+++ b/subversion/svn/propedit-cmd.c
@@ -228,7 +228,7 @@
peg_revision.kind = svn_opt_revision_unspecified;
/* Fetch the current property. */
- SVN_ERR(svn_client_propget4(&props, pname_utf8, abspath_or_url,
+ SVN_ERR(svn_client_propget5(&props, NULL, pname_utf8, abspath_or_url,
&peg_revision,
&(opt_state->start_revision),
&base_rev, svn_depth_empty,
diff --git a/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c
index fcb6503..786947e 100644
--- a/subversion/svn/propget-cmd.c
+++ b/subversion/svn/propget-cmd.c
@@ -70,11 +70,46 @@
static svn_error_t *
print_properties_xml(const char *pname,
apr_hash_t *props,
+ apr_array_header_t *inherited_props,
apr_pool_t *pool)
{
apr_array_header_t *sorted_props;
int i;
- apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_pool_t *iterpool = NULL;
+ svn_stringbuf_t *sb;
+
+ if (inherited_props && inherited_props->nelts)
+ {
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ const char *name_local;
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+ svn_string_t *propval = svn__apr_hash_index_val(
+ apr_hash_first(pool, iprop->prop_hash));
+
+ sb = NULL;
+ svn_pool_clear(iterpool);
+
+ if (svn_path_is_url(iprop->path_or_url))
+ name_local = iprop->path_or_url;
+ else
+ name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+
+ svn_cmdline__print_xml_prop(&sb, pname, propval, TRUE, iterpool);
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ }
+
+ if (iterpool == NULL)
+ iterpool = svn_pool_create(iterpool);
sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
for (i = 0; i < sorted_props->nelts; i++)
@@ -82,38 +117,126 @@
svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
const char *filename = item.key;
svn_string_t *propval = item.value;
- svn_stringbuf_t *sb = NULL;
+ sb = NULL;
svn_pool_clear(iterpool);
svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
"path", filename, NULL);
- svn_cmdline__print_xml_prop(&sb, pname, propval, iterpool);
+ svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, iterpool);
svn_xml_make_close_tag(&sb, iterpool, "target");
SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
}
- svn_pool_destroy(iterpool);
+ if (iterpool)
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
+/* Print the property PNAME_UTF with the value PROPVAL set on ABSPATH_OR_URL
+ to the stream OUT.
-/* Print the properties in PROPS to the stream OUT. PROPS is a hash mapping
- * (const char *) path to (svn_string_t) property value.
- * If IS_URL is true, all paths are URLs, else all paths are local paths.
- * PNAME_UTF8 is the property name of all the properties.
- * If PRINT_FILENAMES is true, print the item's path before each property.
- * If OMIT_NEWLINE is true, don't add a newline at the end of each property.
- * If LIKE_PROPLIST is true, print everything in a more verbose format
- * like "svn proplist -v" does.
- * */
+ If INHERITED_PROPERTY is true then the property described is inherited,
+ otherwise it is explicit.
+
+ WC_PATH_PREFIX is the absolute path of the current working directory (and
+ is ignored if ABSPATH_OR_URL is a URL).
+
+ All other arguments are as per print_properties. */
+static svn_error_t *
+print_single_prop(svn_string_t *propval,
+ const char *abspath_or_URL,
+ const char *wc_path_prefix,
+ svn_stream_t *out,
+ const char *pname_utf8,
+ svn_boolean_t print_filenames,
+ svn_boolean_t omit_newline,
+ svn_boolean_t like_proplist,
+ svn_boolean_t inherited_property,
+ apr_pool_t *scratch_pool)
+{
+ if (print_filenames)
+ {
+ const char *header;
+
+ /* Print the file name. */
+
+ if (! svn_path_is_url(abspath_or_URL))
+ abspath_or_URL = svn_cl__local_style_skip_ancestor(wc_path_prefix,
+ abspath_or_URL,
+ scratch_pool);
+
+ /* In verbose mode, print exactly same as "proplist" does;
+ * otherwise, print a brief header. */
+ if (inherited_property)
+ header = apr_psprintf(scratch_pool, like_proplist
+ ? _("Properties inherited from '%s':\n")
+ : "%s - ", abspath_or_URL);
+ else
+ header = apr_psprintf(scratch_pool, like_proplist
+ ? _("Properties on '%s':\n")
+ : "%s - ", abspath_or_URL);
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, scratch_pool));
+ SVN_ERR(svn_subst_translate_cstring2(header, &header,
+ APR_EOL_STR, /* 'native' eol */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ scratch_pool));
+ SVN_ERR(stream_write(out, header, strlen(header)));
+ }
+
+ if (like_proplist)
+ {
+ /* Print the property name and value just as "proplist -v" does */
+ apr_hash_t *hash = apr_hash_make(scratch_pool);
+
+ apr_hash_set(hash, pname_utf8, APR_HASH_KEY_STRING, propval);
+ SVN_ERR(svn_cl__print_prop_hash(out, hash, FALSE, scratch_pool));
+ }
+ else
+ {
+ /* If this is a special Subversion property, it is stored as
+ UTF8, so convert to the native format. */
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval,
+ TRUE, scratch_pool));
+
+ SVN_ERR(stream_write(out, propval->data, propval->len));
+
+ if (! omit_newline)
+ SVN_ERR(stream_write(out, APR_EOL_STR,
+ strlen(APR_EOL_STR)));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Print the properties in PROPS and/or *INHERITED_PROPS to the stream OUT.
+ PROPS is a hash mapping (const char *) path to (svn_string_t) property
+ value. INHERITED_PROPS is a depth-first ordered array of
+ svn_prop_inherited_item_t * structures.
+
+ PROPS may be an empty hash, but is never null. INHERITED_PROPS may be
+ null.
+
+ If IS_URL is true, all paths in PROPS are URLs, else all paths are local
+ paths.
+
+ PNAME_UTF8 is the property name of all the properties.
+
+ If PRINT_FILENAMES is true, print the item's path before each property.
+
+ If OMIT_NEWLINE is true, don't add a newline at the end of each property.
+
+ If LIKE_PROPLIST is true, print everything in a more verbose format
+ like "svn proplist -v" does. */
static svn_error_t *
print_properties(svn_stream_t *out,
- svn_boolean_t is_url,
const char *pname_utf8,
apr_hash_t *props,
+ apr_array_header_t *inherited_props,
svn_boolean_t print_filenames,
svn_boolean_t omit_newline,
svn_boolean_t like_proplist,
@@ -126,6 +249,24 @@
SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+ if (inherited_props)
+ {
+ svn_pool_clear(iterpool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+ svn_string_t *propval = svn__apr_hash_index_val(apr_hash_first(pool,
+ iprop->prop_hash));
+ SVN_ERR(print_single_prop(propval,
+ iprop->path_or_url,
+ path_prefix, out, pname_utf8,
+ print_filenames, omit_newline,
+ like_proplist, TRUE, iterpool));
+ }
+ }
+
sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
for (i = 0; i < sorted_props->nelts; i++)
{
@@ -135,53 +276,10 @@
svn_pool_clear(iterpool);
- if (print_filenames)
- {
- const char *header;
-
- /* Print the file name. */
-
- if (! is_url)
- filename = svn_cl__local_style_skip_ancestor(path_prefix, filename,
- iterpool);
-
- /* In verbose mode, print exactly same as "proplist" does;
- * otherwise, print a brief header. */
- header = apr_psprintf(iterpool, like_proplist
- ? _("Properties on '%s':\n")
- : "%s - ", filename);
- SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, iterpool));
- SVN_ERR(svn_subst_translate_cstring2(header, &header,
- APR_EOL_STR, /* 'native' eol */
- FALSE, /* no repair */
- NULL, /* no keywords */
- FALSE, /* no expansion */
- iterpool));
- SVN_ERR(stream_write(out, header, strlen(header)));
- }
-
- if (like_proplist)
- {
- /* Print the property name and value just as "proplist -v" does */
- apr_hash_t *hash = apr_hash_make(iterpool);
-
- apr_hash_set(hash, pname_utf8, APR_HASH_KEY_STRING, propval);
- SVN_ERR(svn_cl__print_prop_hash(out, hash, FALSE, iterpool));
- }
- else
- {
- /* If this is a special Subversion property, it is stored as
- UTF8, so convert to the native format. */
- if (svn_prop_needs_translation(pname_utf8))
- SVN_ERR(svn_subst_detranslate_string(&propval, propval,
- TRUE, iterpool));
-
- SVN_ERR(stream_write(out, propval->data, propval->len));
-
- if (! omit_newline)
- SVN_ERR(stream_write(out, APR_EOL_STR,
- strlen(APR_EOL_STR)));
- }
+ SVN_ERR(print_single_prop(propval, filename, path_prefix,
+ out, pname_utf8, print_filenames,
+ omit_newline, like_proplist, FALSE,
+ iterpool));
}
svn_pool_destroy(iterpool);
@@ -234,6 +332,11 @@
const char *URL;
svn_string_t *propval;
+ if (opt_state->show_inherited_props)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-inherited-props can't be used with --revprop"));
+
SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
&URL, ctx, pool));
@@ -255,7 +358,8 @@
"revprops",
"rev", revstr, NULL);
- svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, pool);
+ svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, FALSE,
+ pool);
svn_xml_make_close_tag(&sb, pool, "revprops");
@@ -310,6 +414,7 @@
svn_boolean_t like_proplist;
const char *truepath;
svn_opt_revision_t peg_revision;
+ apr_array_header_t *inherited_props;
svn_pool_clear(subpool);
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
@@ -321,12 +426,15 @@
if (!svn_path_is_url(truepath))
SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
- SVN_ERR(svn_client_propget4(&props, pname_utf8, truepath,
- &peg_revision,
- &(opt_state->start_revision),
- NULL, opt_state->depth,
- opt_state->changelists, ctx, subpool,
- subpool));
+ SVN_ERR(svn_client_propget5(
+ &props,
+ opt_state->show_inherited_props ? &inherited_props : NULL,
+ pname_utf8, truepath,
+ &peg_revision,
+ &(opt_state->start_revision),
+ NULL, opt_state->depth,
+ opt_state->changelists, ctx, subpool,
+ subpool));
/* Any time there is more than one thing to print, or where
the path associated with a printed thing is not obvious,
@@ -341,11 +449,17 @@
like_proplist = opt_state->verbose && !opt_state->strict;
if (opt_state->xml)
- SVN_ERR(print_properties_xml(pname_utf8, props, subpool));
+ SVN_ERR(print_properties_xml(
+ pname_utf8, props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ subpool));
else
- SVN_ERR(print_properties(out, svn_path_is_url(target), pname_utf8,
- props, print_filenames, omit_newline,
- like_proplist, subpool));
+ SVN_ERR(print_properties(
+ out, pname_utf8,
+ props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ print_filenames,
+ omit_newline, like_proplist, subpool));
}
if (opt_state->xml)
diff --git a/subversion/svn/proplist-cmd.c b/subversion/svn/proplist-cmd.c
index 8ca81ab..17a6587 100644
--- a/subversion/svn/proplist-cmd.c
+++ b/subversion/svn/proplist-cmd.c
@@ -35,6 +35,7 @@
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_xml.h"
+#include "svn_props.h"
#include "cl.h"
#include "svn_private_config.h"
@@ -48,43 +49,81 @@
/*** Code. ***/
-/* This implements the svn_proplist_receiver_t interface, printing XML to
+/* This implements the svn_proplist_receiver2_t interface, printing XML to
stdout. */
static svn_error_t *
proplist_receiver_xml(void *baton,
const char *path,
apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
apr_pool_t *pool)
{
svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state;
svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url;
- svn_stringbuf_t *sb = NULL;
+ svn_stringbuf_t *sb;
const char *name_local;
+ if (inherited_props && inherited_props->nelts)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ sb = NULL;
+
+ if (svn_path_is_url(iprop->path_or_url))
+ name_local = iprop->path_or_url;
+ else
+ name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+ SVN_ERR(svn_cl__print_xml_prop_hash(&sb, iprop->prop_hash,
+ (! opt_state->verbose),
+ TRUE, iterpool));
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
if (! is_url)
name_local = svn_dirent_local_style(path, pool);
else
name_local = path;
- /* "<target ...>" */
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
- "path", name_local, NULL);
+ sb = NULL;
- SVN_ERR(svn_cl__print_xml_prop_hash(&sb, prop_hash, (! opt_state->verbose),
- pool));
- /* "</target>" */
- svn_xml_make_close_tag(&sb, pool, "target");
+ if (prop_hash)
+ {
+ /* "<target ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", name_local, NULL);
- return svn_cl__error_checked_fputs(sb->data, stdout);
+ SVN_ERR(svn_cl__print_xml_prop_hash(&sb, prop_hash,
+ (! opt_state->verbose),
+ FALSE, pool));
+
+ /* "</target>" */
+ svn_xml_make_close_tag(&sb, pool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ return SVN_NO_ERROR;
}
-/* This implements the svn_proplist_receiver_t interface. */
+/* This implements the svn_proplist_receiver2_t interface. */
static svn_error_t *
proplist_receiver(void *baton,
const char *path,
apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
apr_pool_t *pool)
{
svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state;
@@ -96,10 +135,46 @@
else
name_local = path;
- if (!opt_state->quiet)
- SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), name_local));
- return svn_cl__print_prop_hash(NULL, prop_hash, (! opt_state->verbose),
- pool);
+ if (inherited_props)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (!opt_state->quiet)
+ {
+ if (svn_path_is_url(iprop->path_or_url))
+ SVN_ERR(svn_cmdline_printf(
+ iterpool, _("Properties inherited from '%s':\n"),
+ iprop->path_or_url));
+ else
+ SVN_ERR(svn_cmdline_printf(
+ iterpool, _("Properties inherited from '%s':\n"),
+ svn_dirent_local_style(iprop->path_or_url, iterpool)));
+ }
+
+ SVN_ERR(svn_cl__print_prop_hash(NULL, iprop->prop_hash,
+ (! opt_state->verbose), iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (prop_hash && apr_hash_count(prop_hash))
+ {
+ if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"),
+ name_local));
+ SVN_ERR(svn_cl__print_prop_hash(NULL, prop_hash, (! opt_state->verbose),
+ pool));
+ }
+
+ return SVN_NO_ERROR;
}
@@ -129,6 +204,11 @@
const char *URL;
apr_hash_t *proplist;
+ if (opt_state->show_inherited_props)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-inherited-props can't be used with --revprop"));
+
SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
&URL, ctx, scratch_pool));
@@ -147,8 +227,9 @@
svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal,
"revprops",
"rev", revstr, NULL);
- SVN_ERR(svn_cl__print_xml_prop_hash
- (&sb, proplist, (! opt_state->verbose), scratch_pool));
+ SVN_ERR(svn_cl__print_xml_prop_hash(&sb, proplist,
+ (! opt_state->verbose), FALSE,
+ scratch_pool));
svn_xml_make_close_tag(&sb, scratch_pool, "revprops");
SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
@@ -169,7 +250,7 @@
{
int i;
apr_pool_t *iterpool;
- svn_proplist_receiver_t pl_receiver;
+ svn_proplist_receiver2_t pl_receiver;
if (opt_state->xml)
{
@@ -203,12 +284,13 @@
iterpool));
SVN_ERR(svn_cl__try(
- svn_client_proplist3(truepath, &peg_revision,
+ svn_client_proplist4(truepath, &peg_revision,
&(opt_state->start_revision),
opt_state->depth,
opt_state->changelists,
+ opt_state->show_inherited_props,
pl_receiver, &pl_baton,
- ctx, iterpool),
+ ctx, iterpool, iterpool),
errors, opt_state->quiet,
SVN_ERR_UNVERSIONED_RESOURCE,
SVN_ERR_ENTRY_NOT_FOUND,
diff --git a/subversion/svn/props.c b/subversion/svn/props.c
index 9bd1c2a..3e1bc86 100644
--- a/subversion/svn/props.c
+++ b/subversion/svn/props.c
@@ -153,6 +153,7 @@
svn_cl__print_xml_prop_hash(svn_stringbuf_t **outstr,
apr_hash_t *prop_hash,
svn_boolean_t names_only,
+ svn_boolean_t inherited_props,
apr_pool_t *pool)
{
apr_array_header_t *sorted_props;
@@ -171,8 +172,10 @@
if (names_only)
{
- svn_xml_make_open_tag(outstr, pool, svn_xml_self_closing, "property",
- "name", pname, NULL);
+ svn_xml_make_open_tag(
+ outstr, pool, svn_xml_self_closing,
+ inherited_props ? "inherited_property" : "property",
+ "name", pname, NULL);
}
else
{
@@ -184,7 +187,8 @@
SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
- svn_cmdline__print_xml_prop(outstr, pname_out, propval, pool);
+ svn_cmdline__print_xml_prop(outstr, pname_out, propval,
+ inherited_props, pool);
}
}
diff --git a/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c
index 353a043..0e1ad51 100644
--- a/subversion/svn/resolve-cmd.c
+++ b/subversion/svn/resolve-cmd.c
@@ -57,6 +57,7 @@
svn_boolean_t had_error = FALSE;
svn_wc_conflict_resolver_func2_t conflict_func2;
void *conflict_baton2;
+ svn_cl__interactive_conflict_baton_t *b;
switch (opt_state->accept_which)
{
@@ -79,7 +80,7 @@
conflict_choice = svn_wc_conflict_choose_mine_full;
break;
case svn_cl__accept_unspecified:
- if (opt_state->conflict_func == NULL)
+ if (opt_state->non_interactive)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("missing --accept option"));
conflict_choice = svn_wc_conflict_choose_unspecified;
@@ -112,9 +113,16 @@
conflict_func2 = ctx->conflict_func2;
conflict_baton2 = ctx->conflict_baton2;
- /* Store interactive resolver */
- ctx->conflict_func2 = opt_state->conflict_func;
- ctx->conflict_baton2 = opt_state->conflict_baton;
+ /* This subcommand always uses the interactive resolver function. */
+ ctx->conflict_func2 = svn_cl__conflict_func_interactive;
+ SVN_ERR(svn_cl__get_conflict_func_interactive_baton(&b,
+ opt_state->accept_which,
+ ctx->config,
+ opt_state->editor_cmd,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ scratch_pool));
+ ctx->conflict_baton2 = b;
iterpool = svn_pool_create(scratch_pool);
for (i = 0; i < targets->nelts; i++)
diff --git a/subversion/svn/status-cmd.c b/subversion/svn/status-cmd.c
index df62910..8d22795 100644
--- a/subversion/svn/status-cmd.c
+++ b/subversion/svn/status-cmd.c
@@ -377,7 +377,7 @@
### non-changelist entries. */
if (opt_state->xml)
{
- svn_stringbuf_set(buf, "");
+ svn_stringbuf_setempty(buf);
svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
"changelist", "name", changelist_name,
NULL);
@@ -398,7 +398,7 @@
if (opt_state->xml)
{
- svn_stringbuf_set(buf, "");
+ svn_stringbuf_setempty(buf);
svn_xml_make_close_tag(&buf, scratch_pool, "changelist");
SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
}
diff --git a/subversion/svn/switch-cmd.c b/subversion/svn/switch-cmd.c
index 0300e0a..d4f4cc1 100644
--- a/subversion/svn/switch-cmd.c
+++ b/subversion/svn/switch-cmd.c
@@ -192,16 +192,11 @@
return svn_error_compose_create(externals_err, err);
}
- if (opt_state->conflict_func
- && svn_cl__notifier_check_conflicts(nwb.wrapped_baton))
- {
- err = svn_cl__resolve_conflicts(
- svn_cl__notifier_get_conflicted_paths(nwb.wrapped_baton,
- scratch_pool),
- depth, opt_state, ctx, scratch_pool);
- if (err)
- return svn_error_compose_create(externals_err, err);
- }
+ err = svn_cl__resolve_postponed_conflicts(ctx->conflict_baton2,
+ opt_state->depth,
+ opt_state->accept_which,
+ opt_state->editor_cmd,
+ ctx, scratch_pool);
return svn_error_compose_create(externals_err, err);
}
diff --git a/subversion/svn/tree-conflicts.c b/subversion/svn/tree-conflicts.c
index 85ddfbf..e596646 100644
--- a/subversion/svn/tree-conflicts.c
+++ b/subversion/svn/tree-conflicts.c
@@ -63,7 +63,6 @@
{ N_("replace"), svn_wc_conflict_reason_replaced },
{ N_("unversioned"), svn_wc_conflict_reason_unversioned },
{ N_("moved away"), svn_wc_conflict_reason_moved_away },
- { N_("moved away and edited"), svn_wc_conflict_reason_moved_away_and_edited },
{ N_("moved here"), svn_wc_conflict_reason_moved_here },
{ NULL, 0 }
};
@@ -79,7 +78,6 @@
{ "replace", svn_wc_conflict_reason_replaced },
{ "unversioned", svn_wc_conflict_reason_unversioned },
{ "moved-away", svn_wc_conflict_reason_moved_away },
- { "moved-away-and-edited", svn_wc_conflict_reason_moved_away_and_edited },
{ "moved-here", svn_wc_conflict_reason_moved_here },
{ NULL, 0 }
};
diff --git a/subversion/svn/update-cmd.c b/subversion/svn/update-cmd.c
index 3835cdf..f71a43e 100644
--- a/subversion/svn/update-cmd.c
+++ b/subversion/svn/update-cmd.c
@@ -110,8 +110,6 @@
svn_boolean_t depth_is_sticky;
struct svn_cl__check_externals_failed_notify_baton nwb;
apr_array_header_t *result_revs;
- svn_wc_conflict_resolver_func2_t conflict_func2 = ctx->conflict_func2;
- void *conflict_baton2 = ctx->conflict_baton2;
svn_error_t *err = SVN_NO_ERROR;
svn_error_t *externals_err = SVN_NO_ERROR;
@@ -189,18 +187,11 @@
return svn_error_compose_create(externals_err, err);
}
- if (opt_state->conflict_func
- && svn_cl__notifier_check_conflicts(nwb.wrapped_baton))
- {
- ctx->conflict_func2 = conflict_func2;
- ctx->conflict_baton2 = conflict_baton2;
- err = svn_cl__resolve_conflicts(
- svn_cl__notifier_get_conflicted_paths(nwb.wrapped_baton,
- scratch_pool),
- depth, opt_state, ctx, scratch_pool);
- if (err)
- return svn_error_compose_create(externals_err, err);
- }
+ err = svn_cl__resolve_postponed_conflicts(ctx->conflict_baton2,
+ opt_state->depth,
+ opt_state->accept_which,
+ opt_state->editor_cmd,
+ ctx, scratch_pool);
return svn_error_compose_create(externals_err, err);
}
diff --git a/subversion/svn_private_config.hw b/subversion/svn_private_config.hw
index 5a3251e..16690f0 100644
--- a/subversion/svn_private_config.hw
+++ b/subversion/svn_private_config.hw
@@ -83,6 +83,7 @@
#include <libintl.h>
#define _(x) dgettext(PACKAGE_NAME, x)
#define Q_(x1, x2, n) dngettext(PACKAGE_NAME, x1, x2, n)
+#define HAVE_BIND_TEXTDOMAIN_CODESET
#else
#define _(x) (x)
#define Q_(x1, x2, n) (((n) == 1) ? x1 : x2)
diff --git a/subversion/svnadmin/main.c b/subversion/svnadmin/main.c
index 001d610..6605872 100644
--- a/subversion/svnadmin/main.c
+++ b/subversion/svnadmin/main.c
@@ -43,6 +43,7 @@
#include "svn_xml.h"
#include "private/svn_opt_private.h"
+#include "private/svn_named_atomic.h"
#include "svn_private_config.h"
@@ -115,7 +116,8 @@
apr_hash_set(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
APR_HASH_KEY_STRING, "1");
apr_hash_set(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS,
- APR_HASH_KEY_STRING, "1");
+ APR_HASH_KEY_STRING,
+ svn_named_atomic__is_efficient() ? "1" : "0");
/* now, open the requested repository */
SVN_ERR(svn_repos_open2(repos, path, fs_config, pool));
@@ -150,6 +152,7 @@
subcommand_create,
subcommand_deltify,
subcommand_dump,
+ subcommand_freeze,
subcommand_help,
subcommand_hotcopy,
subcommand_load,
@@ -336,6 +339,11 @@
"changed in those revisions.)\n"),
{'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M'} },
+ {"freeze", subcommand_freeze, {0}, N_
+ ("usage: svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n\n"
+ "Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"),
+ {0} },
+
{"help", subcommand_help, {"?", "h"}, N_
("usage: svnadmin help [SUBCOMMAND...]\n\n"
"Describe the usage of this program or its subcommands.\n"),
@@ -969,6 +977,66 @@
return SVN_NO_ERROR;
}
+struct freeze_baton_t {
+ const char *command;
+ const char **args;
+ int status;
+};
+
+static svn_error_t *
+freeze_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t *b = baton;
+ apr_status_t apr_err;
+ apr_file_t *infile, *outfile, *errfile;
+
+ apr_err = apr_file_open_stdin(&infile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdin");
+ apr_err = apr_file_open_stdout(&outfile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdout");
+ apr_err = apr_file_open_stderr(&errfile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stderr");
+
+ SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
+ NULL, TRUE,
+ infile, outfile, errfile, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ int i;
+ struct freeze_baton_t b;
+
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ if (!args->nelts)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("No program provided"));
+
+ b.command = APR_ARRAY_IDX(args, 0, const char *);
+ b.args = apr_palloc(pool, sizeof(char *) * args->nelts + 1);
+ for (i = 0; i < args->nelts; ++i)
+ b.args[i] = APR_ARRAY_IDX(args, i, const char *);
+ b.args[args->nelts] = NULL;
+
+ SVN_ERR(svn_repos_freeze(opt_state->repository_path, freeze_body, &b, pool));
+
+ /* Make any non-zero status visible to the user. */
+ if (b.status)
+ exit(b.status);
+
+ return SVN_NO_ERROR;
+}
+
/* This implements `svn_opt_subcommand_t'. */
static svn_error_t *
diff --git a/subversion/svndumpfilter/main.c b/subversion/svndumpfilter/main.c
index 3f62e75..da49d9d 100644
--- a/subversion/svndumpfilter/main.c
+++ b/subversion/svndumpfilter/main.c
@@ -1526,6 +1526,8 @@
{
svn_stringbuf_t *buffer, *buffer_utf8;
const char *utf8_targets_file;
+ apr_array_header_t *targets = apr_array_make(pool, 0,
+ sizeof(const char *));
/* We need to convert to UTF-8 now, even before we divide
the targets into an array, because otherwise we wouldn't
@@ -1538,10 +1540,18 @@
pool));
SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
- opt_state.prefixes = apr_array_append(pool,
- svn_cstring_split(buffer_utf8->data, "\n\r",
- TRUE, pool),
- opt_state.prefixes);
+ targets = apr_array_append(pool,
+ svn_cstring_split(buffer_utf8->data, "\n\r",
+ TRUE, pool),
+ targets);
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *prefix = APR_ARRAY_IDX(targets, i, const char *);
+ if (prefix[0] != '/')
+ prefix = apr_pstrcat(pool, "/", prefix, (char *)NULL);
+ APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
+ }
}
if (apr_is_empty_array(opt_state.prefixes))
diff --git a/subversion/svnlook/main.c b/subversion/svnlook/main.c
index 22f8d1c..1da99f2 100644
--- a/subversion/svnlook/main.c
+++ b/subversion/svnlook/main.c
@@ -1792,7 +1792,7 @@
if (verbose)
{
if (xml)
- svn_cmdline__print_xml_prop(&sb, pname, propval, pool);
+ svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
else
{
const char *pname_stdout;
diff --git a/subversion/svnrdump/load_editor.c b/subversion/svnrdump/load_editor.c
index a411869..8825124 100644
--- a/subversion/svnrdump/load_editor.c
+++ b/subversion/svnrdump/load_editor.c
@@ -704,6 +704,7 @@
apr_size_t residual_close_count;
apr_array_header_t *residual_open_path;
int i;
+ apr_size_t n;
/* Before attempting to handle the action, call open_directory
for all the path components and set the directory baton
@@ -720,7 +721,7 @@
/* First close all as many directories as there are after
skip_ancestor, and then open fresh directories */
- for (i = 0; i < residual_close_count; i ++)
+ for (n = 0; n < residual_close_count; n ++)
{
/* Don't worry about destroying the actual rb->db object,
since the pool we're using has the lifetime of one
diff --git a/subversion/svnrdump/svnrdump.c b/subversion/svnrdump/svnrdump.c
index 6158f4c..90d3a81 100644
--- a/subversion/svnrdump/svnrdump.c
+++ b/subversion/svnrdump/svnrdump.c
@@ -270,7 +270,7 @@
SVN_ERR(svn_ra_initialize(pool));
SVN_ERR(svn_config_ensure(config_dir, pool));
- SVN_ERR(svn_client_create_context(&ctx, pool));
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
diff --git a/subversion/svnserve/cyrus_auth.c b/subversion/svnserve/cyrus_auth.c
index 9ac42ae..28495d9 100644
--- a/subversion/svnserve/cyrus_auth.c
+++ b/subversion/svnserve/cyrus_auth.c
@@ -98,7 +98,7 @@
static sasl_callback_t callbacks[] =
{
- { SASL_CB_CANON_USER, canonicalize_username, NULL },
+ { SASL_CB_CANON_USER, (void*)canonicalize_username, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
diff --git a/subversion/svnserve/main.c b/subversion/svnserve/main.c
index b9d3929..37e32dc 100644
--- a/subversion/svnserve/main.c
+++ b/subversion/svnserve/main.c
@@ -149,6 +149,8 @@
#define SVNSERVE_OPT_CACHE_FULLTEXTS 266
#define SVNSERVE_OPT_CACHE_REVPROPS 267
#define SVNSERVE_OPT_SINGLE_CONN 268
+#define SVNSERVE_OPT_CLIENT_SPEED 269
+#define SVNSERVE_OPT_VIRTUAL_HOST 270
static const apr_getopt_option_t svnserve__options[] =
{
@@ -235,6 +237,16 @@
"Default is no.\n"
" "
"[used for FSFS repositories only]")},
+ {"client-speed", SVNSERVE_OPT_CLIENT_SPEED, 1,
+ N_("Optimize throughput based on the assumption that\n"
+ " "
+ "clients can receive data with a bitrate of at\n"
+ " "
+ "least ARG Gbit/s. For clients receiving data at\n"
+ " "
+ "less than 1 Gbit/s, zero should be used.\n"
+ " "
+ "Default is 0 (optimizations disabled).")},
#ifdef CONNECTION_HAVE_THREAD_OPTION
/* ### Making the assumption here that WIN32 never has fork and so
* ### this option never exists when --service exists. */
@@ -266,6 +278,10 @@
" "
"[mode: tunnel]")},
{"help", 'h', 0, N_("display this help")},
+ {"virtual-host", SVNSERVE_OPT_VIRTUAL_HOST, 0,
+ N_("virtual host mode (look for repo in directory\n"
+ " "
+ "of provided hostname)")},
{"version", SVNSERVE_OPT_VERSION, 0,
N_("show program version information")},
{"quiet", 'q', 0,
@@ -492,11 +508,14 @@
params.authzdb = NULL;
params.compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
params.log_file = NULL;
+ params.vhost = FALSE;
params.username_case = CASE_ASIS;
params.memory_cache_size = (apr_uint64_t)-1;
params.cache_fulltexts = TRUE;
params.cache_txdeltas = FALSE;
params.cache_revprops = FALSE;
+ params.zero_copy_limit = 0;
+ params.error_check_interval = 4096;
while (1)
{
@@ -645,6 +664,18 @@
= svn_tristate__from_word(arg) == svn_tristate_true;
break;
+ case SVNSERVE_OPT_CLIENT_SPEED:
+ {
+ apr_size_t bandwidth = (apr_size_t)apr_strtoi64(arg, NULL, 0);
+
+ /* block other clients for at most 1 ms (at full bandwidth) */
+ params.zero_copy_limit = bandwidth * 120000;
+
+ /* check for aborted connections at the same rate */
+ params.error_check_interval = bandwidth * 120000;
+ }
+ break;
+
#ifdef WIN32
case SVNSERVE_OPT_SERVICE:
if (run_mode != run_mode_service)
@@ -669,7 +700,11 @@
pool));
break;
- case SVNSERVE_OPT_LOG_FILE:
+ case SVNSERVE_OPT_VIRTUAL_HOST:
+ params.vhost = TRUE;
+ break;
+
+ case SVNSERVE_OPT_LOG_FILE:
SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool));
log_filename = svn_dirent_internal_style(log_filename, pool);
SVN_INT_ERR(svn_dirent_get_absolute(&log_filename, log_filename,
@@ -755,8 +790,10 @@
* the pool cleanup handlers that call sasl_dispose() (connection_pool)
* and sasl_done() (pool) are run in the right order. See issue #3664. */
connection_pool = svn_pool_create(pool);
- conn = svn_ra_svn_create_conn2(NULL, in_file, out_file,
+ conn = svn_ra_svn_create_conn3(NULL, in_file, out_file,
params.compression_level,
+ params.zero_copy_limit,
+ params.error_check_interval,
connection_pool);
svn_error_clear(serve(conn, ¶ms, connection_pool));
exit(0);
@@ -925,7 +962,7 @@
settings.single_threaded = TRUE;
if (handling_mode == connection_mode_thread)
{
-#ifdef APR_HAS_THREADS
+#if APR_HAS_THREADS
settings.single_threaded = FALSE;
#else
/* No requests will be processed at all
@@ -988,8 +1025,10 @@
/* It's not a fatal error if we cannot enable keep-alives. */
}
- conn = svn_ra_svn_create_conn2(usock, NULL, NULL,
+ conn = svn_ra_svn_create_conn3(usock, NULL, NULL,
params.compression_level,
+ params.zero_copy_limit,
+ params.error_check_interval,
connection_pool);
if (run_mode == run_mode_listen_once)
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
index bebb8b4..57c90af 100644
--- a/subversion/svnserve/serve.c
+++ b/subversion/svnserve/serve.c
@@ -886,13 +886,14 @@
/* Make an svn_repos report baton. Tell it to drive the network editor
* when the report is complete. */
svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL);
- SVN_CMD_ERR(svn_repos_begin_report2(&report_baton, rev, b->repos,
+ SVN_CMD_ERR(svn_repos_begin_report3(&report_baton, rev, b->repos,
b->fs_path->data, target, tgt_path,
text_deltas, depth, ignore_ancestry,
send_copyfrom_args,
editor, edit_baton,
authz_check_access_cb_func(b),
- b, pool));
+ b, svn_ra_svn_zero_copy_limit(conn),
+ pool));
rb.sb = b;
rb.repos_url = svn_path_uri_decode(b->repos_url, pool);
@@ -962,33 +963,52 @@
}
/* ### This really belongs in libsvn_repos. */
-/* Get the properties for a path, with hardcoded committed-info values. */
-static svn_error_t *get_props(apr_hash_t **props, svn_fs_root_t *root,
- const char *path, apr_pool_t *pool)
+/* Get the explicit properties and/or inherited properties for a PATH in
+ ROOT, with hardcoded committed-info values. */
+static svn_error_t *
+get_props(apr_hash_t **props,
+ apr_array_header_t **iprops,
+ server_baton_t *b,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
{
- svn_string_t *str;
- svn_revnum_t crev;
- const char *cdate, *cauthor, *uuid;
+ /* Get the explicit properties. */
+ if (props)
+ {
+ svn_string_t *str;
+ svn_revnum_t crev;
+ const char *cdate, *cauthor, *uuid;
- /* Get the properties. */
- SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
+ SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
- /* Hardcode the values for the committed revision, date, and author. */
- SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
- path, pool));
- str = svn_string_create(apr_psprintf(pool, "%ld", crev),
- pool);
- apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING, str);
- str = (cdate) ? svn_string_create(cdate, pool) : NULL;
- apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
- str);
- str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
- apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING, str);
+ /* Hardcode the values for the committed revision, date, and author. */
+ SVN_ERR(svn_repos_get_committed_info(&crev, &cdate, &cauthor, root,
+ path, pool));
+ str = svn_string_create(apr_psprintf(pool, "%ld", crev),
+ pool);
+ apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING,
+ str);
+ str = (cdate) ? svn_string_create(cdate, pool) : NULL;
+ apr_hash_set(*props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING,
+ str);
+ str = (cauthor) ? svn_string_create(cauthor, pool) : NULL;
+ apr_hash_set(*props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING,
+ str);
- /* Hardcode the values for the UUID. */
- SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
- str = (uuid) ? svn_string_create(uuid, pool) : NULL;
- apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
+ /* Hardcode the values for the UUID. */
+ SVN_ERR(svn_fs_get_uuid(svn_fs_root_fs(root), &uuid, pool));
+ str = (uuid) ? svn_string_create(uuid, pool) : NULL;
+ apr_hash_set(*props, SVN_PROP_ENTRY_UUID, APR_HASH_KEY_STRING, str);
+ }
+
+ /* Get any inherited properties the user is authorized to. */
+ if (iprops)
+ {
+ SVN_ERR(svn_repos_fs_get_inherited_props(iprops, root, path,
+ authz_check_access_cb_func(b),
+ b, pool, pool));
+ }
return SVN_NO_ERROR;
}
@@ -1390,16 +1410,20 @@
svn_fs_root_t *root;
svn_stream_t *contents;
apr_hash_t *props = NULL;
+ apr_array_header_t *inherited_props;
svn_string_t write_str;
char buf[4096];
apr_size_t len;
svn_boolean_t want_props, want_contents;
+ apr_uint64_t wants_inherited_props;
svn_checksum_t *checksum;
svn_error_t *err, *write_err;
+ int i;
/* Parse arguments. */
- SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb", &path, &rev,
- &want_props, &want_contents));
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?B", &path, &rev,
+ &want_props, &want_contents,
+ &wants_inherited_props));
full_path = svn_fspath__join(b->fs_path->data,
svn_relpath_canonicalize(path, pool), pool);
@@ -1420,8 +1444,9 @@
SVN_CMD_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root,
full_path, TRUE, pool));
hex_digest = svn_checksum_to_cstring_display(checksum, pool);
- if (want_props)
- SVN_CMD_ERR(get_props(&props, root, full_path, pool));
+ if (want_props || wants_inherited_props)
+ SVN_CMD_ERR(get_props(&props, &inherited_props, b, root, full_path,
+ pool));
if (want_contents)
SVN_CMD_ERR(svn_fs_file_contents(&contents, root, full_path, pool));
@@ -1429,6 +1454,27 @@
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w((?c)r(!", "success",
hex_digest, rev));
SVN_ERR(svn_ra_svn_write_proplist(conn, pool, props));
+
+ if (wants_inherited_props)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?!"));
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!(c(!",
+ iprop->path_or_url));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, iprop->prop_hash));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!))!",
+ iprop->path_or_url));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!))"));
/* Now send the file's contents. */
@@ -1473,17 +1519,21 @@
const char *path, *full_path;
svn_revnum_t rev;
apr_hash_t *entries, *props = NULL;
+ apr_array_header_t *inherited_props;
apr_hash_index_t *hi;
svn_fs_root_t *root;
apr_pool_t *subpool;
svn_boolean_t want_props, want_contents;
+ apr_uint64_t wants_inherited_props;
apr_uint64_t dirent_fields;
apr_array_header_t *dirent_fields_list = NULL;
svn_ra_svn_item_t *elt;
+ int i;
- SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l", &path, &rev,
+ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c(?r)bb?l?B", &path, &rev,
&want_props, &want_contents,
- &dirent_fields_list));
+ &dirent_fields_list,
+ &wants_inherited_props));
if (! dirent_fields_list)
{
@@ -1491,8 +1541,6 @@
}
else
{
- int i;
-
dirent_fields = 0;
for (i = 0; i < dirent_fields_list->nelts; ++i)
@@ -1536,9 +1584,11 @@
/* Fetch the root of the appropriate revision. */
SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool));
- /* Fetch the directory properties if requested. */
- if (want_props)
- SVN_CMD_ERR(get_props(&props, root, full_path, pool));
+ /* Fetch the directory's explicit and/or inherited properties
+ if requested. */
+ if (want_props || wants_inherited_props)
+ SVN_CMD_ERR(get_props(&props, &inherited_props, b, root, full_path,
+ pool));
/* Begin response ... */
SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "w(r(!", "success", rev));
@@ -1630,6 +1680,26 @@
svn_pool_destroy(subpool);
}
+ if (wants_inherited_props)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?!"));
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!(c(!",
+ iprop->path_or_url));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, iprop->prop_hash));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!))!",
+ iprop->path_or_url));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
/* Finish response. */
return svn_ra_svn_write_tuple(conn, pool, "!))");
}
@@ -2771,7 +2841,8 @@
svn_error_clear(editor->abort_edit(edit_baton, pool));
SVN_CMD_ERR(err);
- return svn_ra_svn_write_cmd(conn, pool, "finish-replay", "");
+ return svn_ra_svn_write_templated_cmd(conn, pool,
+ svn_ra_svn_cmd_finish_replay);
}
static svn_error_t *replay(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
@@ -2858,6 +2929,65 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+get_inherited_props(svn_ra_svn_conn_t *conn,
+ apr_pool_t *pool,
+ apr_array_header_t *params,
+ void *baton)
+{
+ server_baton_t *b = baton;
+ const char *path, *full_path;
+ svn_revnum_t rev;
+ svn_fs_root_t *root;
+ apr_array_header_t *inherited_props;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Parse arguments. */
+ SVN_ERR(svn_ra_svn_parse_tuple(params, iterpool, "c(?r)", &path, &rev));
+
+ full_path = svn_fspath__join(b->fs_path->data,
+ svn_relpath_canonicalize(path, iterpool),
+ pool);
+
+ /* Check authorizations */
+ SVN_ERR(must_have_access(conn, iterpool, b, svn_authz_read,
+ full_path, FALSE));
+
+ if (!SVN_IS_VALID_REVNUM(rev))
+ SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool));
+
+ SVN_ERR(log_command(b, conn, pool, "%s",
+ svn_log__get_inherited_props(full_path, rev,
+ iterpool)));
+
+ /* Fetch the properties and a stream for the contents. */
+ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, iterpool));
+ SVN_CMD_ERR(get_props(NULL, &inherited_props, b, root, full_path, pool));
+
+ /* Send successful command response with revision and props. */
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "w(!", "success"));
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!(?!"));
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!(c(!",
+ iprop->path_or_url));
+ SVN_ERR(svn_ra_svn_write_proplist(conn, iterpool, iprop->prop_hash));
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!))!",
+ iprop->path_or_url));
+ }
+
+ SVN_ERR(svn_ra_svn_write_tuple(conn, iterpool, "!))"));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
static const svn_ra_svn_cmd_entry_t main_commands[] = {
{ "reparent", reparent },
{ "get-latest-rev", get_latest_rev },
@@ -2889,6 +3019,7 @@
{ "replay", replay },
{ "replay-range", replay_range },
{ "get-deleted-rev", get_deleted_rev },
+ { "get-iprops", get_inherited_props },
{ NULL }
};
@@ -2944,24 +3075,6 @@
return TRUE;
}
-/* Callback which receives hook environment variables from the hook
- * environment configuration section,
- * An implementation of svn_config_enumerator2_t. */
-static svn_boolean_t
-hooks_env_conf_cb(const char *name,
- const char *value,
- void *baton,
- apr_pool_t *pool)
-{
- apr_hash_t *hooks_env = baton;
- apr_pool_t *hash_pool = apr_hash_pool_get(hooks_env);
-
- apr_hash_set(hooks_env, apr_pstrdup(hash_pool, name),
- APR_HASH_KEY_STRING, apr_pstrdup(hash_pool, value));
-
- return TRUE;
-}
-
/* Look for the repository given by URL, using ROOT as the virtual
* repository root. If we find one, fill in the repos, fs, cfg,
* repos_url, and fs_path fields of B. Set B->repos's client
@@ -2974,7 +3087,7 @@
const apr_array_header_t *capabilities,
apr_pool_t *pool)
{
- const char *path, *full_path, *repos_root, *fs_path;
+ const char *path, *full_path, *repos_root, *fs_path, *hooks_env;
svn_stringbuf_t *url_buf;
/* Skip past the scheme and authority part. */
@@ -2983,9 +3096,13 @@
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
"Non-svn URL passed to svn server: '%s'", url);
-
- path = strchr(path, '/');
- path = (path == NULL) ? "" : svn_relpath_canonicalize(path, pool);
+ if (! b->vhost)
+ {
+ path = strchr(path, '/');
+ if (path == NULL)
+ path = "";
+ }
+ path = svn_relpath_canonicalize(path, pool);
path = svn_path_uri_decode(path, pool);
/* Ensure that it isn't possible to escape the root by disallowing
@@ -3032,8 +3149,8 @@
#ifdef SVN_HAVE_SASL
/* Should we use Cyrus SASL? */
- svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
- SVN_CONFIG_OPTION_USE_SASL, FALSE);
+ SVN_ERR(svn_config_get_bool(b->cfg, &b->use_sasl, SVN_CONFIG_SECTION_SASL,
+ SVN_CONFIG_OPTION_USE_SASL, FALSE));
#endif
/* Use the repository UUID as the default realm. */
@@ -3052,16 +3169,12 @@
"No access allowed to this repository",
b, conn, pool);
- /* If a hook environment has been configured, set it up. */
- if (svn_config_has_section(b->cfg, SVN_CONFIG_SECTION_HOOKS_ENV))
- {
- apr_hash_t *hooks_env = apr_hash_make(pool);
-
- svn_config_enumerate2(b->cfg, SVN_CONFIG_SECTION_HOOKS_ENV,
- hooks_env_conf_cb, hooks_env, pool);
-
- svn_repos_hooks_setenv(b->repos, hooks_env);
- }
+ /* Configure hook script environment variables. */
+ svn_config_get(b->cfg, &hooks_env, SVN_CONFIG_SECTION_GENERAL,
+ SVN_CONFIG_OPTION_HOOKS_ENV, NULL);
+ if (hooks_env)
+ hooks_env = svn_dirent_internal_style(hooks_env, pool);
+ SVN_ERR(svn_repos_hooks_setenv(b->repos, hooks_env, pool, pool));
return SVN_NO_ERROR;
}
@@ -3089,6 +3202,53 @@
svn_pool_clear(b->pool);
}
+/* Return the normalized repository-relative path for the given PATH
+ * (may be a URL, full path or relative path) and fs contained in the
+ * server baton BATON. Allocate the result in POOL.
+ */
+static const char *
+get_normalized_repo_rel_path(void *baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ server_baton_t *sb = baton;
+
+ if (svn_path_is_url(path))
+ {
+ /* This is a copyfrom URL. */
+ path = svn_uri_skip_ancestor(sb->repos_url, path, pool);
+ path = svn_fspath__canonicalize(path, pool);
+ }
+ else
+ {
+ /* This is a base-relative path. */
+ if ((path)[0] != '/')
+ /* Get an absolute path for use in the FS. */
+ path = svn_fspath__join(sb->fs_path->data, path, pool);
+ }
+
+ return path;
+}
+
+/* Get the revision root for REVISION in fs given by server baton BATON
+ * and return it in *FS_ROOT. Use HEAD if REVISION is SVN_INVALID_REVNUM.
+ * Use POOL for allocations.
+ */
+static svn_error_t *
+get_revision_root(svn_fs_root_t **fs_root,
+ void *baton,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ server_baton_t *sb = baton;
+
+ if (!SVN_IS_VALID_REVNUM(revision))
+ SVN_ERR(svn_fs_youngest_rev(&revision, sb->fs, pool));
+
+ SVN_ERR(svn_fs_revision_root(fs_root, sb->fs, revision, pool));
+
+ return SVN_NO_ERROR;
+}
static svn_error_t *
fetch_props_func(apr_hash_t **props,
@@ -3098,28 +3258,11 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- server_baton_t *sb = baton;
svn_fs_root_t *fs_root;
svn_error_t *err;
- if (!SVN_IS_VALID_REVNUM(base_revision))
- SVN_ERR(svn_fs_youngest_rev(&base_revision, sb->fs, scratch_pool));
-
- if (svn_path_is_url(path))
- {
- /* This is a copyfrom URL. */
- path = svn_uri_skip_ancestor(sb->repos_url, path, scratch_pool);
- path = svn_fspath__canonicalize(path, scratch_pool);
- }
- else
- {
- /* This is a base-relative path. */
- if (path[0] != '/')
- /* Get an absolute path for use in the FS. */
- path = svn_fspath__join(sb->fs_path->data, path, scratch_pool);
- }
-
- SVN_ERR(svn_fs_revision_root(&fs_root, sb->fs, base_revision, scratch_pool));
+ path = get_normalized_repo_rel_path(baton, path, scratch_pool);
+ SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
err = svn_fs_node_proplist(props, fs_root, path, result_pool);
if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
@@ -3141,28 +3284,11 @@
svn_revnum_t base_revision,
apr_pool_t *scratch_pool)
{
- server_baton_t *sb = baton;
svn_node_kind_t node_kind;
svn_fs_root_t *fs_root;
- if (!SVN_IS_VALID_REVNUM(base_revision))
- SVN_ERR(svn_fs_youngest_rev(&base_revision, sb->fs, scratch_pool));
-
- if (svn_path_is_url(path))
- {
- /* This is a copyfrom URL. */
- path = svn_uri_skip_ancestor(sb->repos_url, path, scratch_pool);
- path = svn_fspath__canonicalize(path, scratch_pool);
- }
- else
- {
- /* This is a base-relative path. */
- if (path[0] != '/')
- /* Get an absolute path for use in the FS. */
- path = svn_fspath__join(sb->fs_path->data, path, scratch_pool);
- }
-
- SVN_ERR(svn_fs_revision_root(&fs_root, sb->fs, base_revision, scratch_pool));
+ path = get_normalized_repo_rel_path(baton, path, scratch_pool);
+ SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
SVN_ERR(svn_fs_check_path(&node_kind, fs_root, path, scratch_pool));
*kind = svn__kind_from_node_kind(node_kind, FALSE);
@@ -3178,31 +3304,14 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- server_baton_t *sb = baton;
svn_stream_t *contents;
svn_stream_t *file_stream;
const char *tmp_filename;
svn_fs_root_t *fs_root;
svn_error_t *err;
- if (!SVN_IS_VALID_REVNUM(base_revision))
- SVN_ERR(svn_fs_youngest_rev(&base_revision, sb->fs, scratch_pool));
-
- if (svn_path_is_url(path))
- {
- /* This is a copyfrom URL. */
- path = svn_uri_skip_ancestor(sb->repos_url, path, scratch_pool);
- path = svn_fspath__canonicalize(path, scratch_pool);
- }
- else
- {
- /* This is a base-relative path. */
- if (path[0] != '/')
- /* Get an absolute path for use in the FS. */
- path = svn_fspath__join(sb->fs_path->data, path, scratch_pool);
- }
-
- SVN_ERR(svn_fs_revision_root(&fs_root, sb->fs, base_revision, scratch_pool));
+ path = get_normalized_repo_rel_path(baton, path, scratch_pool);
+ SVN_ERR(get_revision_root(&fs_root, baton, base_revision, scratch_pool));
err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
@@ -3247,6 +3356,7 @@
b.log_file = params->log_file;
b.pool = pool;
b.use_sasl = FALSE;
+ b.vhost = params->vhost;
/* construct FS configuration parameters */
b.fs_config = apr_hash_make(pool);
@@ -3260,7 +3370,7 @@
/* Send greeting. We don't support version 1 any more, so we can
* send an empty mechlist. */
if (params->compression_level > 0)
- SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwwww)",
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwwwwww)",
(apr_uint64_t) 2, (apr_uint64_t) 2,
SVN_RA_SVN_CAP_EDIT_PIPELINE,
SVN_RA_SVN_CAP_SVNDIFF1,
@@ -3269,9 +3379,11 @@
SVN_RA_SVN_CAP_DEPTH,
SVN_RA_SVN_CAP_LOG_REVPROPS,
SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
- SVN_RA_SVN_CAP_PARTIAL_REPLAY));
+ SVN_RA_SVN_CAP_PARTIAL_REPLAY,
+ SVN_RA_SVN_CAP_INHERITED_PROPS,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS));
else
- SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwww)",
+ SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "nn()(wwwwwwwww)",
(apr_uint64_t) 2, (apr_uint64_t) 2,
SVN_RA_SVN_CAP_EDIT_PIPELINE,
SVN_RA_SVN_CAP_ABSENT_ENTRIES,
@@ -3279,7 +3391,9 @@
SVN_RA_SVN_CAP_DEPTH,
SVN_RA_SVN_CAP_LOG_REVPROPS,
SVN_RA_SVN_CAP_ATOMIC_REVPROPS,
- SVN_RA_SVN_CAP_PARTIAL_REPLAY));
+ SVN_RA_SVN_CAP_PARTIAL_REPLAY,
+ SVN_RA_SVN_CAP_INHERITED_PROPS,
+ SVN_RA_SVN_CAP_EPHEMERAL_TXNPROPS));
/* Read client response, which we assume to be in version 2 format:
* version, capability list, and client URL; then we do an auth
diff --git a/subversion/svnserve/server.h b/subversion/svnserve/server.h
index 018a607..b3dbf5e 100644
--- a/subversion/svnserve/server.h
+++ b/subversion/svnserve/server.h
@@ -59,6 +59,7 @@
svn_boolean_t use_sasl; /* Use Cyrus SASL for authentication;
always false if SVN_HAVE_SASL not defined */
apr_file_t *log_file; /* Log filehandle. */
+ svn_boolean_t vhost; /* Use virtual-host-based path to repo. */
apr_pool_t *pool;
} server_baton_t;
@@ -128,6 +129,16 @@
Defaults to SVN_DELTA_COMPRESSION_LEVEL_DEFAULT. */
int compression_level;
+ /* Item size up to which we use the zero-copy code path to transmit
+ them over the network. 0 disables that code path. */
+ apr_size_t zero_copy_limit;
+
+ /* Amount of data to send between checks for cancellation requests
+ coming in from the client. */
+ apr_size_t error_check_interval;
+
+ /* Use virtual-host-based path to repo. */
+ svn_boolean_t vhost;
} serve_params_t;
/* Serve the connection CONN according to the parameters PARAMS. */
diff --git a/subversion/svnversion/main.c b/subversion/svnversion/main.c
index 2b6da0e..d8a8235 100644
--- a/subversion/svnversion/main.c
+++ b/subversion/svnversion/main.c
@@ -57,19 +57,19 @@
(svn_cmdline_fprintf
(stdout, pool,
_("usage: svnversion [OPTIONS] [WC_PATH [TRAIL_URL]]\n\n"
- " Produce a compact 'version number' for the working copy path\n"
+ " Produce a compact version identifier for the working copy path\n"
" WC_PATH. TRAIL_URL is the trailing portion of the URL used to\n"
" determine if WC_PATH itself is switched (detection of switches\n"
- " within WC_PATH does not rely on TRAIL_URL). The version number\n"
+ " within WC_PATH does not rely on TRAIL_URL). The version identifier\n"
" is written to standard output. For example:\n"
"\n"
" $ svnversion . /repos/svn/trunk\n"
" 4168\n"
"\n"
- " The version number will be a single number if the working\n"
+ " The version identifier will be a single number if the working\n"
" copy is single revision, unmodified, not switched and with\n"
" a URL that matches the TRAIL_URL argument. If the working\n"
- " copy is unusual the version number will be more complex:\n"
+ " copy is unusual the version identifier will be more complex:\n"
"\n"
" 4123:4168 mixed revision working copy\n"
" 4168M modified working copy\n"
diff --git a/subversion/svnversion/svnversion.1 b/subversion/svnversion/svnversion.1
index 9cdb0b1..20edaa5 100644
--- a/subversion/svnversion/svnversion.1
+++ b/subversion/svnversion/svnversion.1
@@ -23,10 +23,10 @@
.\"
.TH svnversion 1
.SH NAME
-svnversion \- Produce a compact version number for a working copy.
+svnversion \- Produce a compact version identifier for a working copy.
.SH SYNOPSIS
.TP
-\fBsvnversion\fP [\fIwc_path\fP [\fItrail_url\fP]]
+\fBsvnversion\fP [\fIoptions\fP] [\fIwc_path\fP [\fItrail_url\fP]]
.SH OVERVIEW
Subversion is a version control system, which allows you to keep old
versions of files and directories (usually source code), keep a log of
diff --git a/subversion/tests/cmdline/autoprop_tests.py b/subversion/tests/cmdline/autoprop_tests.py
index 118269e..f13b471 100755
--- a/subversion/tests/cmdline/autoprop_tests.py
+++ b/subversion/tests/cmdline/autoprop_tests.py
@@ -42,6 +42,7 @@
Wimp = svntest.testcase.Wimp_deco
Item = svntest.wc.StateItem
+from svntest.main import SVN_PROP_INHERITABLE_AUTOPROPS
# Helper function
def check_proplist(path, exp_out):
@@ -324,6 +325,348 @@
{filename : Item(status='? ')})
run_and_verify_unquiet_status(filepath, expected_status)
+#----------------------------------------------------------------------
+
+def create_inherited_autoprops_config(config_dir, enable_flag):
+ "create config stuffs for inherited autoprops tests"
+
+ # contents of the file 'config'
+ config_contents = '''\
+[auth]
+password-stores =
+
+[miscellany]
+enable-auto-props = %s
+
+[auto-props]
+*.c = svn:keywords=Author Date Id Rev URL;svn:eol-style=native;
+''' % (enable_flag and 'yes' or 'no')
+
+ svntest.main.create_config_dir(config_dir, config_contents)
+
+#----------------------------------------------------------------------
+def check_inheritable_autoprops(sbox, auto_props_enabled):
+ """Check that the autoprops added or imported by inheritable_autoprops_test
+ are as expected based on whether traditional auto props are active or
+ not, as indicated by AUTO_PROPS_ENABLED."""
+
+ foo_path = sbox.ospath('foo.c')
+ bar_path = sbox.ospath('B/bar.c')
+ baf_path = sbox.ospath('C/baf.c')
+ qux_path = sbox.ospath('D/qux.c')
+ rip_path = sbox.ospath('D/rip.bat')
+ snk_path = sbox.ospath('D/H/snk.py')
+ sir_path = sbox.ospath('D/H/sir.c')
+
+ if auto_props_enabled:
+ check_proplist(foo_path, {'svn:eol-style':'CRLF',
+ 'svn:keywords':'Author Date Id Rev URL'})
+ check_proplist(bar_path, {'svn:eol-style':'CR',
+ 'svn:keywords':'Date'})
+ check_proplist(baf_path, {'svn:eol-style':'LF',
+ 'svn:keywords':'Rev'})
+ check_proplist(qux_path, {'svn:eol-style':'CRLF',
+ 'svn:keywords':'Author Date Id Rev URL'})
+ check_proplist(rip_path, {'svn:executable':'*'})
+ check_proplist(snk_path, {'svn:mime-type':'text/x-python'})
+ check_proplist(sir_path, {'svn:eol-style':'CRLF',
+ 'svn:keywords':'Author Date Id Rev URL'})
+ else:
+ check_proplist(foo_path, {'svn:eol-style':'CRLF'})
+ check_proplist(bar_path, {'svn:eol-style':'CR',
+ 'svn:keywords':'Date'})
+ check_proplist(baf_path, {'svn:eol-style':'LF',
+ 'svn:keywords':'Rev'})
+ check_proplist(qux_path, {'svn:eol-style':'CRLF'})
+ check_proplist(rip_path, {'svn:executable':'*'})
+ check_proplist(snk_path, {'svn:mime-type':'text/x-python'})
+ check_proplist(sir_path, {'svn:eol-style':'CRLF'})
+
+#----------------------------------------------------------------------
+def inheritable_autoprops_test(sbox, cmd, cfgenable, clienable, subdir,
+ do_import_or_add=True):
+ """configurable autoprops and svn:inheritable-auto-props test.
+
+ CMD is the subcommand to test: 'import' or 'add'
+ if CFGENABLE is true, enable autoprops in the config file, else disable
+ if CLIENABLE == 1: --auto-props is added to the command line
+ 0: nothing is added
+ -1: --no-auto-props is added to command line
+ if string SUBDIR is not empty files are created in that subdir and the
+ directory is added/imported
+ if DO_IMPORT_OR_ADD is false, setup the test, but don't perform
+ the actual import or add.
+
+ Return the directory where the config dir (if any) is located."""
+
+ # Bootstrap
+ sbox.build()
+
+ # some directories
+ wc_dir = sbox.wc_dir
+ tmp_dir = os.path.abspath(svntest.main.temp_dir)
+ config_dir = os.path.join(tmp_dir, 'autoprops_config_' + sbox.name)
+ repos_url = sbox.repo_url
+
+ # initialize parameters
+ if cmd == 'import':
+ parameters = ['import', '-m', 'importing']
+ files_dir = tmp_dir
+ else:
+ parameters = ['add']
+ files_dir = wc_dir
+
+ parameters = parameters + ['--config-dir', config_dir]
+
+ create_inherited_autoprops_config(config_dir, cfgenable)
+
+ # add comandline flags
+ if clienable == 1:
+ parameters = parameters + ['--auto-props']
+ enable_flag = 1
+ elif clienable == -1:
+ parameters = parameters + ['--no-auto-props']
+ enable_flag = 0
+ else:
+ enable_flag = cfgenable
+
+ # setup subdirectory if needed
+ if len(subdir) > 0:
+ files_dir = os.path.join(files_dir, subdir)
+ files_wc_dir = os.path.join(wc_dir, subdir)
+ os.makedirs(files_dir)
+ else:
+ files_wc_dir = wc_dir
+
+ # Set differing svn:inheritable-auto-props properties on various
+ # directories.
+ sbox.simple_propset(SVN_PROP_INHERITABLE_AUTOPROPS,
+ '*.c = svn:eol-style=CRLF\n'
+ '*.bat = svn:executable',
+ '.')
+ sbox.simple_propset(SVN_PROP_INHERITABLE_AUTOPROPS,
+ '*.c = svn:eol-style=CR;svn:keywords=Date',
+ 'A/B')
+ sbox.simple_propset(SVN_PROP_INHERITABLE_AUTOPROPS,
+ '*.c = svn:eol-style=LF;svn:keywords=Rev',
+ 'A/C')
+ sbox.simple_propset(SVN_PROP_INHERITABLE_AUTOPROPS,
+ '*.py = svn:mime-type=text/x-python',
+ 'A/D')
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'Add some ' + SVN_PROP_INHERITABLE_AUTOPROPS +
+ ' properties', wc_dir)
+
+ # Switch the root of the WC to ^/A.
+ svntest.main.run_svn(None, 'switch', '--ignore-ancestry',
+ sbox.repo_url + '/A', wc_dir)
+
+ # Array of file names to add or import, their WC locations (relative to the
+ # WC root) if being added, and their repository locations if being imported.
+ filenames = [['foo.c', 'foo.c', 'A/foo.c'],
+ ['bar.c', os.path.join('B', 'bar.c'), 'A/B/bar.c'],
+ ['baf.c', os.path.join('C', 'baf.c'), 'A/C/baf.c'],
+ ['qux.c', os.path.join('D', 'qux.c'), 'A/D/qux.c'],
+ ['rip.bat', os.path.join('D', 'rip.bat'), 'A/D/rip.bat'],
+ ['snk.py', os.path.join('D', 'H', 'snk.py'), 'A/D/H/snk.py'],
+ ['ric.c', os.path.join('D', 'H', 'sir.c'), 'A/D/H/sir.c']]
+
+ for filename in filenames:
+ if cmd == 'import':
+ svntest.main.file_write(os.path.join(files_dir, filename[0]),
+ 'foo\nbar\nbaz\n')
+ else:
+ svntest.main.file_write(os.path.join(files_dir, filename[1]),
+ 'foo\nbar\nbaz\n')
+
+ if do_import_or_add:
+ if len(subdir) == 0:
+ # add/import the files
+ for filename in filenames:
+ if cmd == 'import':
+ path = os.path.join(files_dir, filename[0])
+ tmp_params = parameters + [path, repos_url + '/' + filename[2]]
+ else:
+ path = os.path.join(files_dir, filename[1])
+ tmp_params = parameters + [path]
+ svntest.main.run_svn(None, *tmp_params)
+ else:
+ # add/import subdirectory
+ if cmd == 'import':
+ parameters = parameters + [files_dir, repos_url]
+ else:
+ parameters = parameters + [files_wc_dir]
+ svntest.main.run_svn(None, *parameters)
+
+ # do an svn co if needed
+ if cmd == 'import':
+ svntest.main.run_svn(None, 'checkout', repos_url + '/A', files_wc_dir,
+ '--config-dir', config_dir)
+
+ check_inheritable_autoprops(sbox, enable_flag)
+
+ return config_dir
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_add_no_none(sbox):
+ "inherit add: config=no, commandline=none"
+ inheritable_autoprops_test(sbox, 'add', False, 0, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_add_yes_none(sbox):
+ "inherit add: config=yes, commandline=none"
+ inheritable_autoprops_test(sbox, 'add', True, 0, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_add_no_yes(sbox):
+ "inherit add: config=no, commandline=yes"
+
+ inheritable_autoprops_test(sbox, 'add', 0, 1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_add_yes_yes(sbox):
+ "inherit add: config=yes, commandline=yes"
+
+ inheritable_autoprops_test(sbox, 'add', 1, 1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_add_no_no(sbox):
+ "inherit add: config=no, commandline=no"
+
+ inheritable_autoprops_test(sbox, 'add', 0, -1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_add_yes_no(sbox):
+ "inherit add: config=yes, commandline=no"
+
+ inheritable_autoprops_test(sbox, 'add', 1, -1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_import_no_none(sbox):
+ "inherit import: config=no, commandline=none"
+
+ inheritable_autoprops_test(sbox, 'import', False, 0, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_imp_yes_none(sbox):
+ "inherit import: config=yes, commandline=none"
+
+ inheritable_autoprops_test(sbox, 'import', 1, 0, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_imp_no_yes(sbox):
+ "inherit import: config=no, commandline=yes"
+
+ inheritable_autoprops_test(sbox, 'import', 0, 1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_imp_yes_yes(sbox):
+ "inherit import: config=yes, commandline=yes"
+
+ inheritable_autoprops_test(sbox, 'import', 1, 1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_imp_no_no(sbox):
+ "inherit import: config=no, commandline=no"
+
+ inheritable_autoprops_test(sbox, 'import', 0, -1, '')
+
+#----------------------------------------------------------------------
+
+def svn_prop_inheritable_autoprops_imp_yes_no(sbox):
+ "inherit import: config=yes, commandline=no"
+
+ inheritable_autoprops_test(sbox, 'import', 1, -1, '')
+
+#----------------------------------------------------------------------
+# Test svn:inheritable-auto-props when 'svn add' targets an already versioned
+# target.
+def svn_prop_inheritable_autoprops_add_versioned_target(sbox):
+ "svn:inheritable-auto-props and versioned target"
+
+ config_dir = inheritable_autoprops_test(sbox, 'add', 1, 0, '', False)
+
+ # Perform the add with the --force flag, and check the status.
+ ### Note: You have to be inside the working copy or else Subversion
+ ### will think you're trying to add the working copy to its parent
+ ### directory, and will (possibly, if the parent directory isn't
+ ### versioned) fail -- see also schedule_tests.py 11 "'svn add'
+ ### should traverse already-versioned dirs"
+ saved_wd = os.getcwd()
+ os.chdir(sbox.wc_dir)
+ svntest.main.run_svn(None, 'add', '.', '--force', '--config-dir',
+ config_dir)
+ os.chdir(saved_wd)
+
+ check_inheritable_autoprops(sbox, True)
+
+#----------------------------------------------------------------------
+# Can't set svn:inheritable-auto-props on files.
+def svn_prop_inheritable_autoprops_propset_file_target(sbox):
+ "svn:inheritable-auto-props can't be set on files"
+
+ sbox.build()
+ svntest.actions.run_and_verify_svn(
+ None, None,
+ ".*Cannot set '" + SVN_PROP_INHERITABLE_AUTOPROPS + "' on a file.*",
+ 'ps', SVN_PROP_INHERITABLE_AUTOPROPS, '*.c=svn:eol-style=native',
+ sbox.ospath('iota'))
+
+#----------------------------------------------------------------------
+# Multiple unversioned subtrees under a versioned target shouldn't segfault.
+def svn_prop_inheritable_autoprops_unversioned_subtrees_versioned_target(sbox):
+ "versioned target and unversioned subtrees"
+
+ sbox.build()
+ Z_path = sbox.ospath('A/D/Z')
+ Y_path = sbox.ospath('A/B/Y')
+ foo_path = sbox.ospath('A/D/Z/foo.c')
+ bar_path = sbox.ospath('A/B/Y/bar.c')
+
+ # Set svn:inheritable-auto-props properties on two directories.
+ svntest.main.run_svn(None, 'ps', SVN_PROP_INHERITABLE_AUTOPROPS,
+ '*.c=svn:eol-style=CR', sbox.ospath('A/B'))
+ svntest.main.run_svn(None, 'ps', SVN_PROP_INHERITABLE_AUTOPROPS,
+ '*.c=svn:eol-style=native', sbox.ospath('A/D'))
+ svntest.main.run_svn(None, 'ci', '-m', 'Add inheritable autoprops',
+ sbox.wc_dir)
+
+ # Create two subtrees, each with one new file.
+ os.mkdir(Z_path)
+ os.mkdir(Y_path)
+ svntest.main.file_write(foo_path,
+ '/* Someday there will be code here. */\n')
+ svntest.main.file_write(bar_path,
+ '/* Someday there will be code here. */\n')
+
+ # Perform the add with the --force flag, targeting the root of the WC.
+ ### Note: You have to be inside the working copy or else Subversion
+ ### will think you're trying to add the working copy to its parent
+ ### directory, and will (possibly, if the parent directory isn't
+ ### versioned) fail -- see also schedule_tests.py 11 "'svn add'
+ ### should traverse already-versioned dirs"
+ saved_wd = os.getcwd()
+ os.chdir(sbox.wc_dir)
+ # This was causing a segfault at one point.
+ svntest.main.run_svn(None, 'add', '.', '--force')
+ os.chdir(saved_wd)
+
+ # Check the resulting autoprops.
+ svntest.actions.run_and_verify_svn(None, 'native\n', [],
+ 'pg', 'svn:eol-style', foo_path)
+ svntest.actions.run_and_verify_svn(None, 'CR\n', [],
+ 'pg', 'svn:eol-style', bar_path)
########################################################################
# Run the tests
@@ -346,6 +689,21 @@
autoprops_add_dir,
autoprops_imp_dir,
fail_add_mixed_eol_style,
+ svn_prop_inheritable_autoprops_add_no_none,
+ svn_prop_inheritable_autoprops_add_yes_none,
+ svn_prop_inheritable_autoprops_add_no_yes,
+ svn_prop_inheritable_autoprops_add_yes_yes,
+ svn_prop_inheritable_autoprops_add_no_no,
+ svn_prop_inheritable_autoprops_add_yes_no,
+ svn_prop_inheritable_autoprops_import_no_none,
+ svn_prop_inheritable_autoprops_imp_yes_none,
+ svn_prop_inheritable_autoprops_imp_no_yes,
+ svn_prop_inheritable_autoprops_imp_yes_yes,
+ svn_prop_inheritable_autoprops_imp_no_no,
+ svn_prop_inheritable_autoprops_imp_yes_no,
+ svn_prop_inheritable_autoprops_add_versioned_target,
+ svn_prop_inheritable_autoprops_propset_file_target,
+ svn_prop_inheritable_autoprops_unversioned_subtrees_versioned_target,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/copy_tests.py b/subversion/tests/cmdline/copy_tests.py
index 21633fb..e0699ce 100755
--- a/subversion/tests/cmdline/copy_tests.py
+++ b/subversion/tests/cmdline/copy_tests.py
@@ -1878,12 +1878,14 @@
# Try to move an unversioned file.
svntest.actions.run_and_verify_svn(None, None,
- ".*unversioned1.* is not under version control.*",
+ ".*unversioned1' " +
+ "(does not exist|is not under version control)",
'mv', unver_path_1, dest_path_1)
# Try to forcibly move an unversioned file.
svntest.actions.run_and_verify_svn(None, None,
- ".*unversioned2.* is not under version control.*",
+ ".*unversioned2.* " +
+ "(does not exist|is not under version control)",
'mv',
unver_path_2, dest_path_2)
@@ -5457,11 +5459,13 @@
sbox.simple_rm('A')
svntest.actions.run_and_verify_svn(None, None,
- 'svn: E145000: Path.* does not exist',
+ '(svn: E145000: Path.* does not exist)|' +
+ "(svn: E155035: Deleted node .* copied)",
'cp', sbox.ospath('iota'),
sbox.ospath('new_iota'))
svntest.actions.run_and_verify_svn(None, None,
- 'svn: E145000: Path.* does not exist',
+ '(svn: E145000: Path.* does not exist)|' +
+ "(svn: E155035: Deleted node .* copied)",
'cp', sbox.ospath('A/D'),
sbox.ospath('new_D'))
@@ -5742,6 +5746,30 @@
})
svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status)
+@Issue(2843)
+def copy_over_excluded(sbox):
+ "copy on top of excluded should give error"
+
+ sbox.build(read_only = True)
+ wc_dir = sbox.wc_dir
+
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'update', '--set-depth', 'exclude',
+ sbox.ospath('A/D'))
+
+ expected_error = "svn: E155000: Path '.*D' exists.*excluded.*"
+
+ svntest.actions.run_and_verify_svn(None, None, expected_error,
+ 'cp',
+ sbox.repo_url + '/A/C',
+ sbox.ospath('A/D'))
+
+ expected_error = "svn: E155000: Path '.*D' exists.*excluded.*"
+ svntest.actions.run_and_verify_svn(None, None, expected_error,
+ 'cp',
+ sbox.ospath('A/C'),
+ sbox.ospath('A/D'))
+
########################################################################
# Run the tests
@@ -5858,6 +5886,7 @@
three_nested_moves,
copy_to_unversioned_parent,
copy_text_conflict,
+ copy_over_excluded,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/export_tests.py b/subversion/tests/cmdline/export_tests.py
index 1231a5d..d83e193 100755
--- a/subversion/tests/cmdline/export_tests.py
+++ b/subversion/tests/cmdline/export_tests.py
@@ -356,6 +356,7 @@
expected_disk)
@XFail()
+@Issue(3798)
def export_working_copy_at_base_revision(sbox):
"export working copy at base revision"
sbox.build(read_only = True)
diff --git a/subversion/tests/cmdline/externals_tests.py b/subversion/tests/cmdline/externals_tests.py
index 9d386ab..1d6b7ee 100755
--- a/subversion/tests/cmdline/externals_tests.py
+++ b/subversion/tests/cmdline/externals_tests.py
@@ -2554,10 +2554,12 @@
actions.run_and_verify_unquiet_status(wc_dir, expected_status)
+@Issue(4252)
@XFail()
def include_immediate_dir_externals(sbox):
"commit --include-externals --depth=immediates"
- # See also comment inside svn_client_commit6().
+ # See also comment in append_externals_as_explicit_targets() in
+ # libsvn_client/commit.c, from r1198765.
# svntest.factory.make(sbox,"""
# svn mkdir X
diff --git a/subversion/tests/cmdline/getopt_tests.py b/subversion/tests/cmdline/getopt_tests.py
index cb65c9f..64e6370 100755
--- a/subversion/tests/cmdline/getopt_tests.py
+++ b/subversion/tests/cmdline/getopt_tests.py
@@ -95,13 +95,41 @@
'Subversion command-line client, version X.Y.Z.'),
]
+# This is a trigger pattern that selects the secondary set of
+# delete/replace patterns
+switch_res_line = 'System information:'
+
+# This is a list of lines to delete after having seen switch_res_line.
+switched_del_lines_res = [
+ # In svn --version --verbose, dependent libs loaded
+ # shared libs are optional.
+ re.compile(r'^\* (loaded|linked)'),
+ # In svn --version --verbose, remove everything from
+ # the extended lists
+ re.compile(r'^ - '),
+ ]
+
+# This is a list of lines to search and replace text on after having
+# seen switch_res_line.
+switched_rep_lines_res = [
+ # We don't care about the actual canonical host
+ (re.compile('^\* running on.*$'), '* running on'),
+ ]
+
def process_lines(lines):
"delete lines that should not be compared and search and replace the rest"
output = [ ]
+ del_res = del_lines_res
+ rep_res = rep_lines_res
+
for line in lines:
+ if line.startswith(switch_res_line):
+ del_res = switched_del_lines_res
+ rep_res = switched_rep_lines_res
+
# Skip these lines from the output list.
delete_line = 0
- for delete_re in del_lines_res:
+ for delete_re in del_res:
if delete_re.match(line):
delete_line = 1
break
@@ -109,7 +137,7 @@
continue
# Search and replace text on the rest.
- for replace_re, replace_str in rep_lines_res:
+ for replace_re, replace_str in rep_res:
line = replace_re.sub(replace_str, line)
output.append(line)
@@ -179,6 +207,10 @@
"run svn --version --quiet"
run_one_test(sbox, 'svn--version--quiet', '--version', '--quiet')
+def getopt__version__verbose(sbox):
+ "run svn --version --verbose"
+ run_one_test(sbox, 'svn--version--verbose', '--version', '--verbose')
+
def getopt__help(sbox):
"run svn --help"
run_one_test(sbox, 'svn--help', '--help')
@@ -204,6 +236,7 @@
getopt_no_args,
getopt__version,
getopt__version__quiet,
+ getopt__version__verbose,
getopt__help,
getopt_help,
getopt_help_bogus_cmd,
diff --git a/subversion/tests/cmdline/getopt_tests_data/svn--version--verbose_stderr b/subversion/tests/cmdline/getopt_tests_data/svn--version--verbose_stderr
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/subversion/tests/cmdline/getopt_tests_data/svn--version--verbose_stderr
diff --git a/subversion/tests/cmdline/getopt_tests_data/svn--version--verbose_stdout b/subversion/tests/cmdline/getopt_tests_data/svn--version--verbose_stdout
new file mode 100644
index 0000000..ab183ee
--- /dev/null
+++ b/subversion/tests/cmdline/getopt_tests_data/svn--version--verbose_stdout
@@ -0,0 +1,90 @@
+svn, version 1.8.0-dev (under development)
+ compiled Sep 10 2012, 14:00:24 on i386-apple-darwin11.4.0
+
+Copyright (C) 2012 The Apache Software Foundation.
+This software consists of contributions made by many people;
+see the NOTICE file for more information.
+Subversion is open source software, see http://subversion.apache.org/
+
+The following repository access (RA) modules are available:
+
+* ra_svn : Module for accessing a repository using the svn network protocol.
+ - with Cyrus SASL authentication
+ - handles 'svn' scheme
+* ra_local : Module for accessing a repository on local disk.
+ - handles 'file' scheme
+* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
+ - handles 'http' scheme
+ - handles 'https' scheme
+
+System information:
+
+* running on i386-apple-darwin11.4.0
+ - Mac OS X 10.7.4 Lion, build 11E53
+* linked dependencies:
+ - APR 1.4.2 (compiled with 1.4.2)
+ - APR-Util 1.3.10 (compiled with 1.3.10)
+ - SQLite 3.7.13 (compiled with 3.7.13)
+* loaded shared libraries:
+ - /opt/subversion/bin/svn (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_client-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_wc-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_ra-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_diff-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_ra_local-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_repos-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_fs-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_fs_fs-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_fs_base-1.0.dylib (Intel 64-bit)
+ - /usr/local/lib/libdb-5.3.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_fs_util-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_ra_svn-1.0.dylib (Intel 64-bit)
+ - /usr/lib/libsasl2.2.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_ra_serf-1.0.dylib (Intel 64-bit)
+ - /usr/local/lib/libserf-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_delta-1.0.dylib (Intel 64-bit)
+ - /opt/subversion/lib/libsvn_subr-1.0.dylib (Intel 64-bit)
+ - /usr/lib/libexpat.1.dylib (Intel 64-bit)
+ - /usr/lib/libz.1.dylib (Intel 64-bit)
+ - /usr/local/lib/libsqlite3.0.8.6.dylib (Intel 64-bit)
+ - /usr/local/lib/libmagic.1.dylib (Intel 64-bit)
+ - /usr/lib/libaprutil-1.0.dylib (Intel 64-bit)
+ - /usr/lib/libapr-1.0.dylib (Intel 64-bit)
+ - /usr/lib/libSystem.B.dylib (Intel 64-bit)
+ - /usr/lib/libiconv.2.dylib (Intel 64-bit)
+ - /usr/lib/libpq.5.dylib (Intel 64-bit)
+ - /usr/lib/libsqlite3.dylib (Intel 64-bit)
+ - /usr/lib/libresolv.9.dylib (Intel 64-bit)
+ - /usr/lib/libssl.0.9.8.dylib (Intel 64-bit)
+ - /usr/lib/libcrypto.0.9.8.dylib (Intel 64-bit)
+ - /usr/lib/libicucore.A.dylib (Intel 64-bit)
+ - /usr/lib/libauto.dylib (Intel 64-bit)
+ - /usr/lib/libobjc.A.dylib (Intel 64-bit)
+ - /usr/lib/libstdc++.6.dylib (Intel 64-bit)
+ - /usr/lib/libpam.2.dylib (Intel 64-bit)
+ - /usr/lib/libbsm.0.dylib (Intel 64-bit)
+ - /usr/lib/libxar-nossl.dylib (Intel 64-bit)
+ - /usr/lib/libc++.1.dylib (Intel 64-bit)
+ - /usr/lib/libc++abi.dylib (Intel 64-bit)
+ - /usr/lib/libDiagnosticMessagesClient.dylib (Intel 64-bit)
+ - /usr/lib/libbz2.1.0.dylib (Intel 64-bit)
+ - /usr/lib/libxml2.2.dylib (Intel 64-bit)
+ - /usr/lib/liblangid.dylib (Intel 64-bit)
+ - /usr/lib/libCRFSuite.dylib (Intel 64-bit)
+ - /usr/lib/libxslt.1.dylib (Intel 64-bit)
+ - /usr/lib/sasl2/apop.so (Intel 64-bit)
+ - /usr/lib/sasl2/dhx.so (Intel 64-bit)
+ - /usr/lib/sasl2/digestmd5WebDAV.so (Intel 64-bit)
+ - /usr/lib/sasl2/libanonymous.2.so (Intel 64-bit)
+ - /usr/lib/sasl2/libcrammd5.2.so (Intel 64-bit)
+ - /usr/lib/sasl2/libdigestmd5.2.so (Intel 64-bit)
+ - /usr/lib/sasl2/libgssapiv2.2.so (Intel 64-bit)
+ - /usr/lib/sasl2/login.so (Intel 64-bit)
+ - /usr/lib/sasl2/libntlm.so (Intel 64-bit)
+ - /usr/lib/sasl2/libotp.2.so (Intel 64-bit)
+ - /usr/lib/sasl2/libplain.2.so (Intel 64-bit)
+ - /usr/lib/sasl2/libpps.so (Intel 64-bit)
+ - /usr/lib/sasl2/mschapv2.so (Intel 64-bit)
+ - /usr/lib/sasl2/shadow_auxprop.so (Intel 64-bit)
+ - /usr/lib/sasl2/smb_nt.so (Intel 64-bit)
+ - /usr/lib/sasl2/smb_ntlmv2.so (Intel 64-bit)
diff --git a/subversion/tests/cmdline/getopt_tests_data/svn--version_stdout b/subversion/tests/cmdline/getopt_tests_data/svn--version_stdout
index ffd51ba..346f218 100644
--- a/subversion/tests/cmdline/getopt_tests_data/svn--version_stdout
+++ b/subversion/tests/cmdline/getopt_tests_data/svn--version_stdout
@@ -2,8 +2,8 @@
compiled Dec 5 2002, 00:02:51
Copyright (C) 2010 The Apache Software Foundation.
-This software consists of contributions made by many people; see the NOTICE
-file for more information.
+This software consists of contributions made by many people;
+see the NOTICE file for more information.
Subversion is open source software, see http://subversion.apache.org/
The following repository access (RA) modules are available:
diff --git a/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout b/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
index 8944cd2..d709650 100644
--- a/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
+++ b/subversion/tests/cmdline/getopt_tests_data/svn_help_log_switch_stdout
@@ -31,12 +31,18 @@
and limits the scope of the displayed diff to the specified depth.
If the --search option is used, log messages are displayed only if the
- provided search pattern matches the author, date, log message text,
- or, if the --verbose option is also provided, a changed path.
- The search pattern may include glob syntax wildcards:
+ provided search pattern matches any of the author, date, log message
+ text (unless --quiet is used), or, if the --verbose option is also
+ provided, a changed path.
+ The search pattern may include "glob syntax" wildcards:
? matches any single character
* matches a sequence of arbitrary characters
- [...] matches any of the characters listed inside the brackets
+ [abc] matches any of the characters listed inside the brackets
+ If multiple --search options are provided, a log message is shown if
+ it matches any of the provided search patterns. If the --search-and
+ option is used, that option's argument is combined with the pattern
+ from the previous --search or --search-and option, and a log message
+ is shown only if it matches the combined search pattern.
If --limit is used in combination with --search, --limit restricts the
number of log messages searched, rather than restricting the output
to a particular number of matching log messages.
@@ -94,24 +100,17 @@
--diff : produce diff output
--diff-cmd ARG : use ARG as diff command
--internal-diff : override diff-cmd specified in config file
- -x [--extensions] ARG : Default: '-u'. When Subversion is invoking an
- external diff program, ARG is simply passed along
- to the program. But when Subversion is using its
- default internal diff implementation, or when
- Subversion is displaying blame annotations, ARG
- could be any of the following:
- -u (--unified):
- Output 3 lines of unified context.
- -b (--ignore-space-change):
- Ignore changes in the amount of white space.
- -w (--ignore-all-space):
- Ignore all white space.
- --ignore-eol-style:
- Ignore changes in EOL style.
- -p (--show-c-function):
- Show C function name in diff output.
+ -x [--extensions] ARG : Specify differencing options for external diff or
+ internal diff or blame. Default: '-u'. Options are
+ separated by spaces. Internal diff and blame take:
+ -u, --unified: Show 3 lines of unified context
+ -b, --ignore-space-change: Ignore changes in
+ amount of white space
+ -w, --ignore-all-space: Ignore all white space
+ --ignore-eol-style: Ignore changes in EOL style
+ -p, --show-c-function: Show C function name
--search ARG : use ARG as search pattern (glob syntax)
- --isearch ARG : like --search, but case-insensitive
+ --search-and ARG : combine ARG with the previous search pattern
Global options:
--username ARG : specify a username ARG
diff --git a/subversion/tests/cmdline/import_tests.py b/subversion/tests/cmdline/import_tests.py
index cdefb09..e9d88d5 100755
--- a/subversion/tests/cmdline/import_tests.py
+++ b/subversion/tests/cmdline/import_tests.py
@@ -25,11 +25,13 @@
######################################################################
# General modules
-import re, os.path
+import re, os.path, sys
# Our testing module
import svntest
from svntest import wc
+from prop_tests import create_inherited_ignores_config
+from svntest.main import SVN_PROP_INHERITABLE_IGNORES
# (abbreviation)
Skip = svntest.testcase.Skip_deco
@@ -407,6 +409,163 @@
sbox.ospath('A/mu'), other_repo_url + '/f')
#----------------------------------------------------------------------
+def import_inherited_ignores(sbox):
+ 'import and inherited ignores'
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Create this config file:
+ #
+ # [miscellany]
+ # global-ignores = *.boo *.goo
+ tmp_dir = os.path.abspath(svntest.main.temp_dir)
+ config_dir = os.path.join(tmp_dir, 'autoprops_config_' + sbox.name)
+ create_inherited_ignores_config(config_dir)
+
+ # Set some ignore properties.
+ sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.voo *.noo *.loo', '.')
+ sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.yoo\t*.doo', 'A/B')
+ sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.moo', 'A/D')
+ sbox.simple_propset('svn:ignore', '*.zoo\n*.foo\n*.poo', 'A/B/E')
+ sbox.simple_commit()
+
+ # Use this tree for importing:
+ #
+ # DIR1.noo
+ # DIR2.doo
+ # file1.txt
+ # DIR3.foo
+ # file2.txt
+ # DIR4.goo
+ # file3.txt
+ # file4.noo
+ # DIR5.moo
+ # file5.txt
+ # DIR6
+ # file6.foo
+ # DIR7
+ # file7.foo
+ # DIR8.noo
+ import_tree_dir = os.path.join(os.path.dirname(sys.argv[0]),
+ 'import_tests_data', 'import_tree')
+
+ # Relative WC paths of the imported tree.
+ dir1_path = os.path.join('DIR1.noo')
+ dir2_path = os.path.join('DIR2.doo')
+ file1_path = os.path.join('DIR2.doo', 'file1.txt')
+ dir3_path = os.path.join('DIR3.foo')
+ file2_path = os.path.join('DIR3.foo', 'file2.txt')
+ dir4_path = os.path.join('DIR4.goo')
+ file3_path = os.path.join('DIR4.goo', 'file3.txt')
+ file4_path = os.path.join('DIR4.goo', 'file4.txt')
+ dir5_path = os.path.join('DIR5.moo')
+ file5_path = os.path.join('DIR5.moo', 'file5.txt')
+ dir6_path = os.path.join('DIR6')
+ file6_path = os.path.join('DIR6', 'file6.foo')
+ dir7_path = os.path.join('DIR6', 'DIR7')
+ file7_path = os.path.join('DIR6', 'DIR7', 'file7.foo')
+ dir8_path = os.path.join('DIR6', 'DIR7', 'DIR8.noo')
+
+ # Import the tree to ^/A/B/E.
+ # We should never see any *.noo paths because those are blocked at the
+ # root of the repository by the svn:inheritable-ignores property. Likewise
+ # *.doo paths are blocked by the svn:inheritable-ignores on ^/A/B. Nor
+ # should we see and *.boo or *.goo paths, as those are blocked by the
+ # global-ignores config. Lastly, ^/A/B/E should not get any *.foo paths
+ # because of the svn:ignore property on ^/A/B/E, but non-immediate children
+ # of ^/A/B/E are permitted *.foo paths.
+ svntest.actions.run_and_verify_svn(None, None, [], 'import',
+ '--config-dir', config_dir,
+ import_tree_dir,
+ sbox.repo_url + '/A/B/E',
+ '-m', 'import')
+ E_path = os.path.join(wc_dir, 'A', 'B', 'E')
+ expected_output = svntest.verify.UnorderedOutput(
+ ["Updating '" + wc_dir + "':\n",
+ 'A ' + os.path.join(E_path, dir5_path) + '\n',
+ 'A ' + os.path.join(E_path, file5_path) + '\n',
+ 'A ' + os.path.join(E_path, dir6_path) + '\n',
+ 'A ' + os.path.join(E_path, file6_path) + '\n',
+ 'A ' + os.path.join(E_path, dir7_path) + '\n',
+ 'A ' + os.path.join(E_path, file7_path) + '\n',
+ 'Updated to revision 3.\n'])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)
+
+ # Import the tree to ^/A/B/E/Z. The only difference from above is that
+ # DIR3.foo and its child file2.txt are also imported. Why? Because now
+ # we are creating a new directory in ^/A/B/E, so the svn:ignore property
+ # set on ^/A/B/E doesn't apply.
+ svntest.actions.run_and_verify_svn(None, None, [], 'import',
+ '--config-dir', config_dir,
+ import_tree_dir,
+ sbox.repo_url + '/A/B/E/Z',
+ '-m', 'import')
+ Z_path = os.path.join(wc_dir, 'A', 'B', 'E', 'Z')
+ expected_output = svntest.verify.UnorderedOutput(
+ ["Updating '" + wc_dir + "':\n",
+ 'A ' + os.path.join(Z_path) + '\n',
+ 'A ' + os.path.join(Z_path, dir5_path) + '\n',
+ 'A ' + os.path.join(Z_path, file5_path) + '\n',
+ 'A ' + os.path.join(Z_path, dir6_path) + '\n',
+ 'A ' + os.path.join(Z_path, file6_path) + '\n',
+ 'A ' + os.path.join(Z_path, dir7_path) + '\n',
+ 'A ' + os.path.join(Z_path, file7_path) + '\n',
+ 'A ' + os.path.join(Z_path, dir3_path) + '\n',
+ 'A ' + os.path.join(Z_path, file2_path) + '\n',
+ 'Updated to revision 4.\n'])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)
+
+ # Import the tree to ^/A/B/F with the --no-ignore option.
+ # Now only the ignores present in the svn:inheritable-ignores property
+ # should be considered.
+ svntest.actions.run_and_verify_svn(None, None, [], 'import',
+ '--config-dir', config_dir,
+ '--no-ignore', import_tree_dir,
+ sbox.repo_url + '/A/B/F',
+ '-m', 'import')
+ F_path = os.path.join(wc_dir, 'A', 'B', 'F')
+ expected_output = svntest.verify.UnorderedOutput(
+ ["Updating '" + wc_dir + "':\n",
+ 'A ' + os.path.join(F_path, dir3_path) + '\n',
+ 'A ' + os.path.join(F_path, file2_path) + '\n',
+ 'A ' + os.path.join(F_path, dir4_path) + '\n',
+ 'A ' + os.path.join(F_path, file3_path) + '\n',
+ 'A ' + os.path.join(F_path, dir5_path) + '\n',
+ 'A ' + os.path.join(F_path, file5_path) + '\n',
+ 'A ' + os.path.join(F_path, dir6_path) + '\n',
+ 'A ' + os.path.join(F_path, file6_path) + '\n',
+ 'A ' + os.path.join(F_path, dir7_path) + '\n',
+ 'A ' + os.path.join(F_path, file7_path) + '\n',
+ 'Updated to revision 5.\n'])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)
+
+ # Try importing a single file into a directory which has svn:ignore set
+ # on it with a matching pattern of the imported file. The import should
+ # be a no-op.
+ svntest.actions.run_and_verify_svn(None, [], [], 'import',
+ '--config-dir', config_dir,
+ os.path.join(import_tree_dir,
+ 'DIR6', 'file6.foo'),
+ sbox.repo_url + '/A/B/E/file6.foo',
+ '-m', 'This import should fail!')
+
+ # Try the above, but this time with --no-ignore, this time the import
+ # should succeed.
+ svntest.actions.run_and_verify_svn(None, None, [], 'import', '--no-ignore',
+ '--config-dir', config_dir,
+ os.path.join(import_tree_dir,
+ 'DIR6', 'file6.foo'),
+ sbox.repo_url + '/A/B/E/file6.foo',
+ '-m', 'import')
+ expected_output = svntest.verify.UnorderedOutput(
+ ["Updating '" + wc_dir + "':\n",
+ 'A ' + os.path.join(E_path, 'file6.foo') + '\n',
+ 'Updated to revision 6.\n'])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'up', wc_dir)
+
+#----------------------------------------------------------------------
+
########################################################################
# Run the tests
@@ -419,6 +578,7 @@
import_no_ignores,
import_eol_style,
import_into_foreign_repo,
+ import_inherited_ignores,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR2.doo/file1.txt b/subversion/tests/cmdline/import_tests_data/import_tree/DIR2.doo/file1.txt
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR2.doo/file1.txt
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR3.foo/file2.txt b/subversion/tests/cmdline/import_tests_data/import_tree/DIR3.foo/file2.txt
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR3.foo/file2.txt
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR4.goo/file3.txt b/subversion/tests/cmdline/import_tests_data/import_tree/DIR4.goo/file3.txt
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR4.goo/file3.txt
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR4.goo/file4.noo b/subversion/tests/cmdline/import_tests_data/import_tree/DIR4.goo/file4.noo
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR4.goo/file4.noo
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR5.moo/file5.txt b/subversion/tests/cmdline/import_tests_data/import_tree/DIR5.moo/file5.txt
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR5.moo/file5.txt
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR6/DIR7/file7.foo b/subversion/tests/cmdline/import_tests_data/import_tree/DIR6/DIR7/file7.foo
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR6/DIR7/file7.foo
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/import_tests_data/import_tree/DIR6/file6.foo b/subversion/tests/cmdline/import_tests_data/import_tree/DIR6/file6.foo
new file mode 100644
index 0000000..8f868f6
--- /dev/null
+++ b/subversion/tests/cmdline/import_tests_data/import_tree/DIR6/file6.foo
@@ -0,0 +1 @@
+A file
diff --git a/subversion/tests/cmdline/iprop_authz_tests.py b/subversion/tests/cmdline/iprop_authz_tests.py
new file mode 100644
index 0000000..f27fb91
--- /dev/null
+++ b/subversion/tests/cmdline/iprop_authz_tests.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+#
+# iprop_authz_tests.py: iprop tests that need to write an authz file
+#
+# Subversion is a tool for revision control.
+# See http://subversion.apache.org for more information.
+#
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+######################################################################
+
+# General modules
+import os
+
+# Our testing module
+import svntest
+
+# (abbreviation)
+Skip = svntest.testcase.Skip_deco
+
+from svntest.main import write_restrictive_svnserve_conf
+from svntest.main import write_authz_file
+
+######################################################################
+# Tests
+
+#----------------------------------------------------------------------
+# Property inheritance with read restrictions on parent paths.
+@Skip(svntest.main.is_ra_type_file)
+def iprops_authz(sbox):
+ "property inheritance and read restricted parents"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # r2 - Set properties at various levels.
+ sbox.simple_propset('RootProp', 'Root-Prop-Val', '.')
+ sbox.simple_propset('BranchProp', 'Branch-Prop-Val', 'A')
+ sbox.simple_propset('RandomProp1', 'Random-Prop-Val-1', 'A/D')
+ sbox.simple_propset('RandomProp2', 'Random-Prop-Val-2', 'A/D/H')
+ sbox.simple_propset('FileProp1', 'File-Prop-Val-1', 'A/D/H/psi')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some properties',
+ wc_dir)
+
+ write_restrictive_svnserve_conf(sbox.repo_dir)
+
+ # Check that a restricted user can only see inherited props from
+ # parent paths which he has read access to.
+
+ # Grant access only to ^/A/D/H/psi. No inherited properties should
+ # be shown.
+ write_authz_file(sbox, {
+ "/A/D/H/psi" : svntest.main.wc_author + "=rw",})
+
+ expected_iprops = {}
+ expected_explicit_props = {'FileProp1' : 'File-Prop-Val-1'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D/H/psi', expected_iprops, expected_explicit_props)
+
+ # Grant access to ^/A/D/H/psi and the repos root but not the intermediate
+ # paths between the two.
+ write_authz_file(sbox, {
+ "/" : svntest.main.wc_author + "=rw",
+ "/A" : svntest.main.wc_author + "=",
+ "/A/D/H/psi" : svntest.main.wc_author + "=rw",})
+
+ expected_iprops = {
+ sbox.repo_url : {'RootProp' : 'Root-Prop-Val'}}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D/H/psi', expected_iprops, expected_explicit_props)
+
+ # Grant access to ^/A/D/H/psi, the repos root, and the intermediate path
+ # ^/A/D. Everything else is still blocked.
+ write_authz_file(sbox, {
+ "/" : svntest.main.wc_author + "=rw",
+ "/A" : svntest.main.wc_author + "=",
+ "/A/D" : svntest.main.wc_author + "=rw",
+ "/A/D/H" : svntest.main.wc_author + "=",
+ "/A/D/H/psi" : svntest.main.wc_author + "=rw",})
+
+ expected_iprops = {
+ sbox.repo_url : {'RootProp' : 'Root-Prop-Val'},
+ sbox.repo_url + '/A/D': {'RandomProp1' : 'Random-Prop-Val-1'}}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D/H/psi', expected_iprops, expected_explicit_props)
+
+ # Grant read access to everything except ^/A/D/H/psi. In this case we
+ # should get an authorization failed error. It doesn't matter that we can
+ # read the parents.
+ write_authz_file(sbox, {
+ "/" : svntest.main.wc_author + "=rw",
+ "/A/D/H/psi" : svntest.main.wc_author + "=",})
+ if sbox.repo_url.startswith("http"):
+ expected_err = ".*[Ff]orbidden.*"
+ else:
+ expected_err = ".*svn: E170001: Authorization failed.*"
+ svntest.actions.run_and_verify_svn(
+ None, None, expected_err, 'proplist', '-v',
+ '--show-inherited-props', sbox.repo_url + '/A/D/H/psi')
+
+########################################################################
+# Run the tests
+
+# list all tests here, starting with None:
+test_list = [ None,
+ iprops_authz,
+ ]
+
+serial_only = True
+
+if __name__ == '__main__':
+ svntest.main.run_tests(test_list)
+ # NOTREACHED
+
+### End of file.
diff --git a/subversion/tests/cmdline/iprop_tests.py b/subversion/tests/cmdline/iprop_tests.py
new file mode 100644
index 0000000..2ef9f81
--- /dev/null
+++ b/subversion/tests/cmdline/iprop_tests.py
@@ -0,0 +1,1657 @@
+#!/usr/bin/env python
+#
+# iprop_tests.py: testing versioned inherited properties
+#
+# Subversion is a tool for revision control.
+# See http://subversion.apache.org for more information.
+#
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+######################################################################
+
+# General modules
+import os
+
+# Our testing module
+import svntest
+
+# (abbreviation)
+Skip = svntest.testcase.Skip_deco
+SkipUnless = svntest.testcase.SkipUnless_deco
+XFail = svntest.testcase.XFail_deco
+Issues = svntest.testcase.Issues_deco
+Issue = svntest.testcase.Issue_deco
+Wimp = svntest.testcase.Wimp_deco
+Item = svntest.wc.StateItem
+
+######################################################################
+# Tests
+
+#----------------------------------------------------------------------
+# Working property inheritance, uniform revision WC.
+def iprops_basic_working(sbox):
+ "basic inherited working properties"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Paths of note.
+ mu_path = sbox.ospath('A/mu')
+ D_path = sbox.ospath('A/D')
+ psi_path = sbox.ospath('A/D/H/psi')
+ iota_path = sbox.ospath('iota')
+ alpha_path = sbox.ospath('A/B/E/alpha')
+ G_path = sbox.ospath('A/D/G')
+ rho_path = sbox.ospath('A/D/G/rho')
+
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val1', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val2', '.')
+ sbox.simple_propset('DirProp2', 'Dir-Prop-Val-Root', '.')
+ sbox.simple_propset('FileProp1', 'File-Prop-Val1', 'iota')
+ sbox.simple_propset('FileProp2', 'File-Prop-Val2', 'A/D/H/psi')
+ sbox.simple_propset('DirProp1', 'Dir-Prop-Val1', 'A/D')
+ sbox.simple_propset('DirProp2', 'Dir-Prop-Val2', 'A/D')
+ sbox.simple_propset('DirProp3', 'Dir-Prop-Val3', 'A/D')
+ sbox.simple_propset('SomeProp', 'Some-Prop-Val1', 'A/D/G')
+ sbox.simple_propset('SomeProp', 'Some-Prop-Val2', 'A/D/G/rho')
+
+ ### Proplist Directory Targets
+
+ # Proplist directory target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'DirProp2' : 'Dir-Prop-Val-Root',
+ 'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ wc_dir, expected_iprops, expected_explicit_props)
+
+ # Proplist directory target with only inherited props.
+ expected_iprops = {wc_dir : {'DirProp2' : 'Dir-Prop-Val-Root',
+ 'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ alpha_path, expected_iprops, expected_explicit_props)
+
+ # Proplist directory target with inherited and explicit props.
+ expected_iprops = {wc_dir : {'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2',
+ 'DirProp2' : 'Dir-Prop-Val-Root'}}
+ expected_explicit_props = {'DirProp1' : 'Dir-Prop-Val1',
+ 'DirProp2' : 'Dir-Prop-Val2',
+ 'DirProp3' : 'Dir-Prop-Val3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props)
+
+ ### Propget Directory Targets
+
+ # Propget directory target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'RootProp2' : 'Root-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ wc_dir, expected_iprops, expected_explicit_props, 'RootProp2')
+
+ # Propget directory target with only inherited props.
+ expected_iprops = {wc_dir : {'RootProp2': 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ alpha_path, expected_iprops, expected_explicit_props, 'RootProp2')
+
+ # Propget directory target with inherited and explicit props.
+ expected_iprops = {wc_dir : {'DirProp2' : 'Dir-Prop-Val-Root',}}
+ expected_explicit_props = {'DirProp2' : 'Dir-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'DirProp2')
+
+ ### Propget File Targets
+
+ # Propget file target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'FileProp1' : 'File-Prop-Val1'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ iota_path, expected_iprops, expected_explicit_props, 'FileProp1')
+
+ # Propget file target with only inherited props.
+ expected_iprops = {wc_dir : {'RootProp2': 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ alpha_path, expected_iprops, expected_explicit_props, 'RootProp2')
+
+ # Propget file target with inherited and explicit props.
+ expected_iprops = {G_path : {'SomeProp' : 'Some-Prop-Val1',}}
+ expected_explicit_props = {'SomeProp' : 'Some-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ rho_path, expected_iprops, expected_explicit_props, 'SomeProp')
+
+ ### Proplist File Targets
+
+ # Proplist file target with only inherited props.
+ expected_iprops = {wc_dir : {'DirProp2' : 'Dir-Prop-Val-Root',
+ 'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ mu_path, expected_iprops, expected_explicit_props)
+
+ # Proplist file target with inherited and explicit props.
+ expected_iprops = {wc_dir : {'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2',
+ 'DirProp2' : 'Dir-Prop-Val-Root'},
+ D_path : {'DirProp1' : 'Dir-Prop-Val1',
+ 'DirProp2' : 'Dir-Prop-Val2',
+ 'DirProp3' : 'Dir-Prop-Val3'}}
+ expected_explicit_props = {'FileProp2' : 'File-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ psi_path, expected_iprops, expected_explicit_props)
+
+ # Proplist file target with only explicit props.
+ svntest.actions.run_and_verify_svn(None, None, [], 'revert', wc_dir)
+ expected_iprops = {}
+ expected_explicit_props = {'FileProp1' : 'File-Prop-Val1'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ iota_path, expected_iprops, expected_explicit_props)
+
+#----------------------------------------------------------------------
+# Property inheritance with repository targets.
+def iprops_basic_repos(sbox):
+ "basic inherited repository properties"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Paths of note.
+ D_path = sbox.ospath('A/D')
+ alpha_path = sbox.ospath('A/B/E/alpha')
+
+ sbox.simple_propset('FileProp1', 'File-Prop-Val1', 'iota')
+ sbox.simple_propset('FileProp2', 'File-Prop-Val2', 'A/D/H/psi')
+ sbox.simple_propset('SomeProp', 'Some-Prop-Val2', 'A/D/G/rho')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some file properties',
+ wc_dir)
+
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val1', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val2', '.')
+ sbox.simple_propset('DirProp2', 'Dir-Prop-Val-Root', '.')
+ sbox.simple_propset('DirProp1', 'Dir-Prop-Val1', 'A/D')
+ sbox.simple_propset('DirProp2', 'Dir-Prop-Val2', 'A/D')
+ sbox.simple_propset('DirProp3', 'Dir-Prop-Val3', 'A/D')
+ sbox.simple_propset('SomeProp', 'Some-Prop-Val1', 'A/D/G')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some dir properties',
+ wc_dir)
+
+ ### Proplist Directory Targets
+
+ # Proplist directory target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'DirProp2' : 'Dir-Prop-Val-Root',
+ 'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url, expected_iprops, expected_explicit_props)
+
+ # Proplist directory target with only inherited props.
+ expected_iprops = {sbox.repo_url : {'DirProp2' : 'Dir-Prop-Val-Root',
+ 'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/B/E/alpha', expected_iprops, expected_explicit_props)
+
+ # Proplist directory target with inherited and explicit props.
+ expected_iprops = {sbox.repo_url : {'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2',
+ 'DirProp2' : 'Dir-Prop-Val-Root'}}
+ expected_explicit_props = {'DirProp1' : 'Dir-Prop-Val1',
+ 'DirProp2' : 'Dir-Prop-Val2',
+ 'DirProp3' : 'Dir-Prop-Val3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props)
+
+ ### Propget Directory Targets
+
+ # Propget directory target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'RootProp2' : 'Root-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url, expected_iprops, expected_explicit_props, 'RootProp2')
+
+ # Propget directory target with only inherited props.
+ expected_iprops = {sbox.repo_url : {'RootProp2': 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/B/E/alpha', expected_iprops, expected_explicit_props,
+ 'RootProp2')
+
+ # Propget directory target with inherited and explicit props.
+ expected_iprops = {sbox.repo_url : {'DirProp2' : 'Dir-Prop-Val-Root',}}
+ expected_explicit_props = {'DirProp2' : 'Dir-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp2')
+
+ ### Proplist File Targets
+
+ # Proplist file target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'FileProp1' : 'File-Prop-Val1'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/iota', expected_iprops, expected_explicit_props,
+ 'FileProp1', 2)
+
+ # Proplist file target with only inherited props.
+ expected_iprops = {sbox.repo_url : {'RootProp1' : 'Root-Prop-Val1'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/mu', expected_iprops, expected_explicit_props,
+ 'RootProp1')
+
+ # Proplist file target with inherited and explicit props.
+ expected_iprops = {sbox.repo_url : {'RootProp1' : 'Root-Prop-Val1',
+ 'RootProp2' : 'Root-Prop-Val2',
+ 'DirProp2' : 'Dir-Prop-Val-Root'},
+ sbox.repo_url + '/A/D' : {'DirProp1' : 'Dir-Prop-Val1',
+ 'DirProp2' : 'Dir-Prop-Val2',
+ 'DirProp3' : 'Dir-Prop-Val3'}}
+ expected_explicit_props = {'FileProp2' : 'File-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D/H/psi', expected_iprops, expected_explicit_props)
+
+ ### Propget File Targets
+
+ # Propget file target with only explicit props.
+ expected_iprops = {}
+ expected_explicit_props = {'FileProp1' : 'File-Prop-Val1'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/iota', expected_iprops, expected_explicit_props,
+ 'FileProp1', 2)
+
+ # Propget file target with only inherited props.
+ expected_iprops = {sbox.repo_url : {'RootProp2': 'Root-Prop-Val2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/B/E/alpha', expected_iprops, expected_explicit_props,
+ 'RootProp2')
+
+ # Propget file target with inherited and explicit props.
+ expected_iprops = {sbox.repo_url + '/A/D/G' : {
+ 'SomeProp' : 'Some-Prop-Val1',}}
+ expected_explicit_props = {'SomeProp' : 'Some-Prop-Val2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D/G/rho', expected_iprops, expected_explicit_props,
+ 'SomeProp')
+
+#----------------------------------------------------------------------
+# Property inheritance in a WC with switched subtrees.
+def iprops_switched_subtrees(sbox):
+ "inherited properties in switched subtrees"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Paths of note.
+ branch2_path = sbox.ospath('branch2')
+ branch2_B_path = sbox.ospath('branch2/B')
+ branch2_lambda_path = sbox.ospath('branch2/B/lambda')
+
+ # r2-3 - Create two branches
+ svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A',
+ sbox.repo_url + '/branch1', '-m', 'Make branch1')
+
+ svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A',
+ sbox.repo_url + '/branch2', '-m', 'Make branch2')
+
+ # Create a root property and two branch properties
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ sbox.simple_propset('Root-Prop-1', 'Root-Prop-Val1', '.')
+ sbox.simple_propset('Branch-Name', 'Feature #1', 'branch1')
+ sbox.simple_propset('Branch-Name', 'Feature #2', 'branch2')
+
+ # Switch a subtree of branch2 to branch1:
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/branch1/B',
+ branch2_B_path)
+
+ # Check for inherited props on branch2/B/lambda. Since the prop changes
+ # made above have not been committed, there should be none.
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ branch2_B_path, expected_iprops, expected_explicit_props)
+
+ # r4 - Commit the prop changes made above.
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some dir properties',
+ wc_dir)
+
+ # Again check for inherited props on branch2/B/lambda. And again there
+ # should be none because branch2/B is switched to ^/branch1/B@3.
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ branch2_lambda_path, expected_iprops, expected_explicit_props)
+
+ # Now update the WC, now branch2/B is switched to ^/branch1/B@4
+ # which does inherit properties from ^/branch1 and ^/. The inherited
+ # properties cache should be updated to reflect this when asking what
+ # properties branch2/B/lambda inherits.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ expected_iprops = {
+ sbox.repo_url : {'Root-Prop-1' : 'Root-Prop-Val1'},
+ sbox.repo_url + '/branch1' : {'Branch-Name' : 'Feature #1'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ branch2_lambda_path, expected_iprops, expected_explicit_props)
+
+ # Now update the WC back to r3, where there are no properties. The
+ # inheritable properties cache for the WC-root at branch2/B should be
+ # cleared and no inheritable properties found for branch2/B/lambda.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', '-r3', wc_dir)
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ branch2_lambda_path, expected_iprops, expected_explicit_props)
+ # Update back to HEAD=r4 before continuing.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # Now unswitch branch2/B and check branch2/B/lambda's inherited props.
+ # Now no iprop cache for branch2/B should exist and branch2/B/lambda
+ # should inherit from branch2 and '.'.
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/branch2/B',
+ branch2_B_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ expected_iprops = {
+ ### Working copy parents! ###
+ wc_dir : {'Root-Prop-1' : 'Root-Prop-Val1'},
+ branch2_path : {'Branch-Name' : 'Feature #2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ branch2_lambda_path, expected_iprops, expected_explicit_props)
+
+ # Now switch the root of the WC to ^/branch2 and check the inherited
+ # properties on B/lambda. It should inherit the explicit property
+ # on the WC path '.' (i.e. ^/branch2) and the property on the root
+ # of the repos via the inherited props cache.
+ svntest.main.run_svn(None, 'switch', '--ignore-ancestry',
+ sbox.repo_url + '/branch2', wc_dir)
+ expected_iprops = {
+ ### Root if a repos parent ###
+ sbox.repo_url : {'Root-Prop-1' : 'Root-Prop-Val1'},
+ ### Branch root is a working copy parent ###
+ wc_dir : {'Branch-Name' : 'Feature #2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('B/lambda'),
+ expected_iprops, expected_explicit_props)
+
+ # Check that switched files have properties cached too.
+ # Switch the root of the WC to ^/A, then switch mu to ^/branch1/mu.
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/A', wc_dir)
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/branch1/mu',
+ sbox.ospath('mu'))
+ expected_iprops = {
+ sbox.repo_url : {'Root-Prop-1' : 'Root-Prop-Val1'},
+ sbox.repo_url + '/branch1' : {'Branch-Name' : 'Feature #1'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('mu'),
+ expected_iprops, expected_explicit_props)
+
+#----------------------------------------------------------------------
+# Property inheritance with pegged wc and repos targets.
+def iprops_pegged_wc_targets(sbox):
+ "iprops of pegged wc targets at operative revs"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Paths of note.
+ C_path = sbox.ospath('A/C')
+ D_path = sbox.ospath('A/D')
+ G_path = sbox.ospath('A/D/G')
+ alpha_path = sbox.ospath('A/B/E/alpha')
+ replaced_alpha_path = sbox.ospath('A/D/G/E/alpha')
+
+ # r2 - Set some root properties and a property on A/D, and make an edit
+ # to A/B/E/alpha.
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val-1-set-in-r2', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val-2-set-in-r2', '.')
+ sbox.simple_propset('D-Prop', 'D-Prop-Val-set-in-r2', 'A/D')
+ svntest.main.file_write(alpha_path, "Edit in r2.\n")
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some properties',
+ wc_dir)
+
+ # r3 - Change all of the properties.
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val-1-set-in-r3', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val-2-set-in-r3', '.')
+ sbox.simple_propset('D-Prop', 'D-Prop-Val-set-in-r3', 'A/D')
+ svntest.main.run_svn(None, 'commit', '-m', 'Modify some properties',
+ wc_dir)
+
+ # Set some working properties.
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val-1-WORKING', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val-2-WORKING', '.')
+ sbox.simple_propset('D-Prop', 'D-Prop-Val-WORKING', 'A/D')
+
+ ### Peg Revision = HEAD
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | HEAD | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'HEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | HEAD | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'HEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | HEAD | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'HEAD', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | HEAD | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'HEAD',
+ '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | HEAD | COMMITTED
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'HEAD',
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | HEAD | COMMITTED
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'HEAD',
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | HEAD | PREV
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'HEAD',
+ '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | HEAD | PREV
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'HEAD',
+ '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | HEAD | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'HEAD',
+ '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | HEAD | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'HEAD',
+ '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | HEAD | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'HEAD',
+ '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | HEAD | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'HEAD',
+ '-rHEAD')
+
+ ### Peg Revision = Unspecified
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | unspecified | unspecified
+ expected_iprops = {
+ wc_dir : {'RootProp1' : 'Root-Prop-Val-1-WORKING',
+ 'RootProp2' : 'Root-Prop-Val-2-WORKING'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-WORKING'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props)
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | unspecified | unspecified
+ expected_iprops = {
+ wc_dir : {'RootProp1' : 'Root-Prop-Val-1-WORKING'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | unspecified | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, None, '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | unspecified | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', None,
+ '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | unspecified | revision=COMMITTED (i.e. r3)
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, None,
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | unspecified | COMMITTED
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', None,
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | unspecified | revision=PREV (i.e. r2)
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, None, '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | unspecified | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', None,
+ '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | unspecified | revision=BASE (i.e. r3)
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, None, '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | unspecified | revision=BASE (i.e. r3)
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', None,
+ '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | unspecified | revision=HEAD (i.e. r3)
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, None, '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | unspecified | revision=HEAD (i.e. r3)
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', None,
+ '-rHEAD')
+
+ ### Peg Revision = rN
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | revision=1 | unspecified
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, '1')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | revision=1 | unspecified
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', '1')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | revision=1 | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, '1', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | revision=1 | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', '1', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | revision=1 | COMMITTED
+ # The last committed revision for A/D is r3.
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, '1',
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | revision=1 | COMMITTED
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', '1',
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | revision=3 | PREV
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, '3', '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | revision=3 | PREV
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', '3',
+ '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | revision=1 | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, '1', '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | revision=1 | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', '1',
+ '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | revision=2 | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, '2', '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | revision=1 | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', '1',
+ '-rHEAD')
+
+ ### Peg Revision = PREV
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | PREV | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'PREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | PREV | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'PREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | PREV | revision=3
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'PREV', '-r3')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | PREV | revision=3
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'PREV',
+ '-r3')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | PREV | COMMITTED
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'PREV',
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | PREV | COMMITTED
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'PREV',
+ '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | PREV | PREV
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'PREV', '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | PREV | PREV
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'PREV',
+ '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | PREV | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'PREV', '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | PREV | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'PREV',
+ '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | PREV | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, None, 'PREV', '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | PREV | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ D_path, expected_iprops, expected_explicit_props, 'RootProp1', 'PREV',
+ '-rHEAD')
+
+ ### Peg Revision = BASE
+
+ # Replace A/D/G with a copy of ^/A/B.
+ # Check inherited props on base of A/D/G/E/alpha.
+ # Inherited props should always come from the repository parent of
+ # ^/A/B/E/alpha and so should not include the property (working or
+ # otherwise) on A/D.
+ svntest.actions.run_and_verify_svn(None, None, [], 'delete', G_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'copy',
+ sbox.repo_url + '/A/B', G_path)
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | BASE | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | BASE | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE')
+
+# Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | BASE | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | BASE | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | BASE | COMMITTED
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE', '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | BASE | revision=2
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE', '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | BASE | COMMITTED
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ # so PREV=r1, but there are no properties at all in r1.
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE', '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | BASE | COMMITTED
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ # so PREV=r1, but there are no properties at all in r1.
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE', '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | BASE | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE', '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | BASE | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE', '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | BASE | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE', '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | BASE | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE', '-rHEAD')
+
+ ### Peg Revision = COMMITTED
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | COMMITTED | unspecified
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | COMMITTED | unspecified
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | COMMITTED | revision=3
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED', '-r3')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | COMMITTED | revision=3
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED', '-r3')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | COMMITTED | COMMITTED
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED', '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | COMMITTED | COMMITTED
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED', '-rCOMMITTED')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | COMMITTED | PREV
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ # so PREV=r1, but there are no properties at all in r1.
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED', '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | COMMITTED | PREV
+ # The most recent change on the copy source, ^/A/B/E/alpha is r2
+ # so PREV=r1, but there are no properties at all in r1.
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED', '-rPREV')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | COMMITTED | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED', '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | COMMITTED | BASE
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED', '-rBASE')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | WC | COMMITTED | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED', '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | WC | COMMITTED | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED', '-rHEAD')
+
+ # Revert the replacement with history of A/D/G and once again
+ # replace A/D/G, but this time without history (using and export
+ # of A/B.
+ svntest.actions.run_and_verify_svn(None, None, [], 'revert', G_path, '-R')
+ svntest.actions.run_and_verify_svn(None, None, [], 'delete', G_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'export',
+ sbox.repo_url + '/A/B', G_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'add', G_path)
+ # Set a working prop on a file within the replaced tree, we should *never*
+ # see this property if asking about the
+ # file@[HEAD | PREV | COMMITTED | BASE]
+ sbox.simple_propset('FileProp', 'File-Prop-WORKING-NO-BASE',
+ 'A/D/G/E/alpha')
+
+ # There is no HEAD, PREV, COMMITTED, or BASE revs for A/D/G/E/alpha in this
+ # case # so be sure requests for such error out or return nothing as per the
+ # existing behavior for proplist and propget sans the --show-inherited-props
+ # option.
+ #
+ # proplist/propget WC-PATH@HEAD
+ svntest.actions.run_and_verify_svn(
+ None, None,
+ ".*Unknown node kind for '" + sbox.repo_url + "/A/D/G/E/alpha'\n",
+ 'pl', '-v', '--show-inherited-props', replaced_alpha_path + '@HEAD')
+ svntest.actions.run_and_verify_svn(
+ None, None,
+ ".*'" + sbox.repo_url + "/A/D/G/E/alpha' does not exist in revision 3\n",
+ 'pg', 'RootProp1', '-v', '--show-inherited-props',
+ replaced_alpha_path + '@HEAD')
+ # proplist/propget WC-PATH@PREV
+ svntest.actions.run_and_verify_svn(
+ None, None,
+ ".*Path '.*alpha' has no committed revision\n",
+ 'pl', '-v', '--show-inherited-props', replaced_alpha_path + '@PREV')
+ svntest.actions.run_and_verify_svn(
+ None, None,
+ ".*Path '.*alpha' has no committed revision\n",
+ 'pg', 'RootProp1', '-v', '--show-inherited-props', replaced_alpha_path + '@PREV')
+ # proplist/propget WC-PATH@COMMITTED
+ expected_iprops = {}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'COMMITTED')
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'COMMITTED')
+ # proplist/propget WC-PATH@BASE
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props, None,
+ 'BASE')
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ replaced_alpha_path, expected_iprops, expected_explicit_props,
+ 'RootProp1', 'BASE')
+
+#----------------------------------------------------------------------
+# Property inheritance with pegged repos targets at operative revs.
+def iprops_pegged_url_targets(sbox):
+ "iprops of pegged url targets at operative revs"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # r2 - Set some root properties and some properties on A/D.
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val-1-set-in-r2', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val-2-set-in-r2', '.')
+ sbox.simple_propset('DirProp', 'Dir-Prop-Val-set-in-r2', '.')
+ sbox.simple_propset('DirProp', 'Dir-Prop-Val-set-in-r2-on-D', 'A/D')
+ sbox.simple_propset('D-Prop', 'D-Prop-Val-set-in-r2', 'A/D')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some properties',
+ wc_dir)
+
+ # r3 - Make another change to all of the properties set in r2.
+ sbox.simple_propset('RootProp1', 'Root-Prop-Val-1-set-in-r3', '.')
+ sbox.simple_propset('RootProp2', 'Root-Prop-Val-2-set-in-r3', '.')
+ sbox.simple_propset('DirProp', 'Dir-Prop-Val-set-in-r3', '.')
+ sbox.simple_propset('DirProp', 'Dir-Prop-Val-set-in-r3-on-D', 'A/D')
+ sbox.simple_propset('D-Prop', 'D-Prop-Val-set-in-r3', 'A/D')
+ svntest.main.run_svn(None, 'commit', '-m', 'Modify some properties',
+ wc_dir)
+
+ ### Peg Revision = Unspecified
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | unspecified | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props)
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | unspecified | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | unspecified | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props, None,
+ None, '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | unspecified | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', None, '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | unspecified | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ None, None, '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | unspecified | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', None, '-rHEAD')
+
+ ### Peg Revision = rN
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | revision=2 | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ None, '2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | revision=2 | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', '2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | revision=2 | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props, None,
+ '2', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | revision=2 | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', '2', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | revision=2 | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ None, '2', '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | revision=2 | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', '2', '-rHEAD')
+
+ ### Peg Revision = HEAD
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | HEAD | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ None, 'HEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | HEAD | unspecified
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', 'HEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | HEAD | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r2',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r2',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props, None,
+ 'HEAD', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | HEAD | revision=2
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r2'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r2-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', 'HEAD', '-r2')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # proplist | URL | HEAD | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'RootProp1' : 'Root-Prop-Val-1-set-in-r3',
+ 'RootProp2' : 'Root-Prop-Val-2-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'D-Prop' : 'D-Prop-Val-set-in-r3',
+ 'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ None, 'HEAD', '-rHEAD')
+
+ # Operation | Target | Peg Revision | Operative Revision
+ # propget | URL | HEAD | HEAD
+ expected_iprops = {
+ sbox.repo_url : {'DirProp' : 'Dir-Prop-Val-set-in-r3'}}
+ expected_explicit_props = {'DirProp' : 'Dir-Prop-Val-set-in-r3-on-D'}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.repo_url + '/A/D', expected_iprops, expected_explicit_props,
+ 'DirProp', 'HEAD', '-rHEAD')
+
+#----------------------------------------------------------------------
+# Inherited property caching during shallow updates.
+def iprops_shallow_operative_depths(sbox):
+ "iprop caching works with shallow updates"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # r2 - Create a branch..
+ svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A',
+ sbox.repo_url + '/branch1', '-m', 'Make branch1')
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # r3 - Create a root property and some branch properties
+ sbox.simple_propset('Root-Prop-1', 'Root-Prop-Val1', '.')
+ sbox.simple_propset('Branch-Name', 'Feature #1', 'branch1')
+ sbox.simple_propset('Branch-Name', 'Trunk', 'A')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add some properties',
+ wc_dir)
+
+ # r4 - Change the root and a branch properties added in r3.
+ sbox.simple_propset('Root-Prop-1', 'Root-Prop-Val1.1', '.')
+ sbox.simple_propset('Branch-Name', 'Feature No. 1', 'branch1')
+ sbox.simple_propset('Branch-Name', 'Trunk Branch', 'A')
+ svntest.main.run_svn(None, 'commit', '-m', 'Change some properties',
+ wc_dir)
+
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # Switch the WC to ^/branch1:
+ svntest.main.run_svn(None, 'switch', '--ignore-ancestry',
+ sbox.repo_url + '/branch1', wc_dir)
+ # Switch the B to ^/A/B:
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/A/B',
+ sbox.ospath('B'))
+ # Switch the mu to ^/A/mu:
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/A/mu',
+ sbox.ospath('mu'))
+ # Update the whole WC back to r3.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', '-r3', wc_dir)
+
+ # Check the inherited props on B/E within the switched subtree
+ # and the switched file mu. The props should all be inherited
+ # from repository locations and reflect the values at r3.
+ expected_iprops = {
+ sbox.repo_url : {'Root-Prop-1' : 'Root-Prop-Val1'},
+ sbox.repo_url + '/A' : {'Branch-Name' : 'Trunk'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('B/E'), expected_iprops, expected_explicit_props)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('mu'), expected_iprops, expected_explicit_props)
+
+ # Update only the root of the WC (to HEAD=r4) using a shallow update.
+ # Again check the inherited props on B/E. This shouldn't affect the
+ # switched subtree at all, the props it inherits should still reflect
+ # the values at r3.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up',
+ '--depth=empty', wc_dir)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('B/E'), expected_iprops, expected_explicit_props)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('mu'), expected_iprops, expected_explicit_props)
+
+ # Update the root of the WC (to HEAD=r4) at depth=files. B/E should
+ # still inherit vales from r3, but mu should now inherit props from r4.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up',
+ '--depth=files', wc_dir)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('B/E'), expected_iprops, expected_explicit_props)
+ expected_iprops = {
+ sbox.repo_url : {'Root-Prop-1' : 'Root-Prop-Val1.1'},
+ sbox.repo_url + '/A' : {'Branch-Name' : 'Trunk Branch'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('mu'), expected_iprops, expected_explicit_props)
+
+ # Update the root of the WC (to HEAD=r4) at depth=immediates. Now both B/E
+ # and mu inherit props from r4.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up',
+ '--depth=immediates', wc_dir)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('B/E'), expected_iprops, expected_explicit_props)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('mu'), expected_iprops, expected_explicit_props)
+
+#----------------------------------------------------------------------
+# Inherited property caching by directory externals.
+def iprops_with_directory_externals(sbox):
+ "iprop caching works with directory externals"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Create a second repository with the original greek tree
+ repo_dir = sbox.repo_dir
+ other_repo_dir, other_repo_url = sbox.add_repo_path("other")
+ other_wc_dir = sbox.add_wc_path("other")
+ svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 1)
+ svntest.actions.run_and_verify_svn(None, None, [], 'co', other_repo_url,
+ other_wc_dir)
+
+ # Create a root property on the first WC.
+ sbox.simple_propset('Prime-Root-Prop', 'Root-Prop-Val1', '.')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add a root property',
+ wc_dir)
+
+ # Create a root property on the "other" WC.
+ svntest.actions.run_and_verify_svn(None, None, [], 'ps', 'Other-Root-Prop',
+ 'Root-Prop-Val-from-other', other_wc_dir)
+ svntest.main.run_svn(None, 'commit', '-m', 'Add a root property',
+ other_wc_dir)
+
+ # Switch the root of the first WC to a repository non-root, it will
+ # now have cached iprops from the first repos.
+ svntest.main.run_svn(None, 'switch', sbox.repo_url + '/A/B',
+ wc_dir, '--ignore-ancestry')
+
+ # Create an external in the first WC that points to a location in the
+ # "other" WC.
+ sbox.simple_propset('svn:externals',
+ other_repo_url + '/A/D/G X-Other-Repos',
+ 'E')
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci',
+ '-m', 'Add external point to other WC',
+ wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # Create an external in the first WC that points to a location in the
+ # same WC.
+ sbox.simple_propset('svn:externals',
+ sbox.repo_url + '/A/D/H X-Same-Repos',
+ 'F')
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'Add external pointing to same repos',
+ wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # Check the properties inherited by the external from the same repository.
+ # It should inherit the props from the root of the same repository.
+ expected_iprops = {
+ sbox.repo_url : {'Prime-Root-Prop' : 'Root-Prop-Val1'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('F/X-Same-Repos'), expected_iprops, expected_explicit_props)
+
+ # Check the properties inherited by the external from the "other"
+ # repository. It should inherit from the root of the other repos,
+ # despite being located in the first repository's WC.
+ expected_iprops = {
+ other_repo_url : {'Other-Root-Prop' : 'Root-Prop-Val-from-other'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('E/X-Other-Repos'), expected_iprops, expected_explicit_props)
+
+#----------------------------------------------------------------------
+# Inherited property caching by file externals.
+def iprops_with_file_externals(sbox):
+ "iprop caching works with file externals"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Create a root property.
+ sbox.simple_propset('Prime-Root-Prop', 'Root-Prop-Val1', '.')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add a root property',
+ wc_dir)
+
+ # Create a "branch" property on 'A/D'.
+ sbox.simple_propset('Prime-Branch-Prop', 'Branch-Prop-Val1', 'A/D')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add a branch property',
+ wc_dir)
+
+ # Create two file externals, one pegged to a fixed revision.
+ sbox.simple_propset('svn:externals',
+ sbox.repo_url + '/A/D/H/psi file-external',
+ 'A/B/E')
+ sbox.simple_propset('svn:externals',
+ sbox.repo_url + '/A/D/H/psi@4 file-external-pegged',
+ 'A/B/F')
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'Add a file external', wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # Check the properties inherited by the external files. Both should
+ # inherit the properties from ^/ and ^/A/D.
+ expected_iprops = {
+ sbox.repo_url : {'Prime-Root-Prop' : 'Root-Prop-Val1'},
+ sbox.repo_url + '/A/D' : {'Prime-Branch-Prop' : 'Branch-Prop-Val1'}}
+ expected_explicit_props = {}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('A/B/E/file-external'), expected_iprops,
+ expected_explicit_props)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('A/B/F/file-external-pegged'), expected_iprops,
+ expected_explicit_props)
+
+ # Modify the "branch" property on 'A/D'.
+ sbox.simple_propset('Prime-Branch-Prop', 'Branch-Prop-Val2', 'A/D')
+ svntest.main.run_svn(None, 'commit', '-m', 'Add a branch property',
+ wc_dir)
+
+ # There should be no change in the external file's
+ # inherited properties until...
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('A/B/E/file-external'), expected_iprops,
+ expected_explicit_props)
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('A/B/F/file-external-pegged'), expected_iprops,
+ expected_explicit_props)
+
+ # ...We update the external:
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ # The pegged file external's iprops should remain unchanged.
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('A/B/F/file-external-pegged'), expected_iprops,
+ expected_explicit_props)
+ # But the other's should be updated.
+ expected_iprops = {
+ sbox.repo_url : {'Prime-Root-Prop' : 'Root-Prop-Val1'},
+ sbox.repo_url + '/A/D' : {'Prime-Branch-Prop' : 'Branch-Prop-Val2'}}
+ svntest.actions.run_and_verify_inherited_prop_xml(
+ sbox.ospath('A/B/E/file-external'), expected_iprops,
+ expected_explicit_props)
+
+########################################################################
+# Run the tests
+
+# list all tests here, starting with None:
+test_list = [ None,
+ iprops_basic_working,
+ iprops_basic_repos,
+ iprops_switched_subtrees,
+ iprops_pegged_wc_targets,
+ iprops_pegged_url_targets,
+ iprops_shallow_operative_depths,
+ iprops_with_directory_externals,
+ iprops_with_file_externals,
+ ]
+
+if __name__ == '__main__':
+ svntest.main.run_tests(test_list)
+ # NOTREACHED
+
+### End of file.
diff --git a/subversion/tests/cmdline/log_tests.py b/subversion/tests/cmdline/log_tests.py
index b3b5bcc..59e36cf 100755
--- a/subversion/tests/cmdline/log_tests.py
+++ b/subversion/tests/cmdline/log_tests.py
@@ -2296,14 +2296,51 @@
log_chain = parse_log_output(output)
check_log_chain(log_chain, [7, 6, 3])
- # case-insensitive search
+ # search is case-insensitive
exit_code, output, err = svntest.actions.run_and_verify_svn(
- None, None, [], 'log', '--isearch',
+ None, None, [], 'log', '--search',
'FOR REVISION [367]')
log_chain = parse_log_output(output)
check_log_chain(log_chain, [7, 6, 3])
+ # multi-pattern search
+ exit_code, output, err = svntest.actions.run_and_verify_svn(
+ None, None, [], 'log',
+ '--search', 'for revision 3',
+ '--search', 'for revision 6',
+ '--search', 'for revision 7')
+
+ log_chain = parse_log_output(output)
+ check_log_chain(log_chain, [7, 6, 3])
+
+ # combined pattern search
+ exit_code, output, err = svntest.actions.run_and_verify_svn(
+ None, None, [], 'log', '--verbose',
+ '--search', 'for revision 8',
+ '--search-and', 'test the code',
+ '--search', 'for revision 7',
+ '--search-and', 'this won\'t match ',
+ '--search', 'psi',
+ '--search-and', 'multiple lines',
+ '--search-and', 'revision 6') # don't match r4
+
+ log_chain = parse_log_output(output)
+ check_log_chain(log_chain, [8, 6])
+
+ exit_code, output, err = svntest.actions.run_and_verify_svn(
+ None, None, [], 'log', '--verbose',
+ '--search', 'for revision 8',
+ '--search-and', 'this won\'t match ',
+ '--search', 'for revision 7',
+ '--search', 'psi',
+ '--search-and', 'multiple lines',
+ '--search-and', 'revision 4') # don't match r6
+
+ log_chain = parse_log_output(output)
+ check_log_chain(log_chain, [7, 4])
+
+
@SkipUnless(server_has_mergeinfo)
def merge_sensitive_log_with_search(sbox):
"test 'svn log -g --search'"
diff --git a/subversion/tests/cmdline/merge_symmetric_tests.py b/subversion/tests/cmdline/merge_automatic_tests.py
old mode 100755
new mode 100644
similarity index 94%
rename from subversion/tests/cmdline/merge_symmetric_tests.py
rename to subversion/tests/cmdline/merge_automatic_tests.py
index 2f9fa4e..0b56eb2
--- a/subversion/tests/cmdline/merge_symmetric_tests.py
+++ b/subversion/tests/cmdline/merge_automatic_tests.py
@@ -1,1022 +1,1022 @@
-#!/usr/bin/env python
-#
-# merge_symmetric_tests.py: testing "symmetric merge" scenarios
-#
-# Subversion is a tool for revision control.
-# See http://subversion.apache.org for more information.
-#
-# ====================================================================
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-######################################################################
-
-# General modules
-import shutil, sys, re, os
-import time
-
-# Our testing module
-import svntest
-from svntest import main, wc, verify, actions
-
-# (abbreviation)
-Item = wc.StateItem
-Skip = svntest.testcase.Skip_deco
-SkipUnless = svntest.testcase.SkipUnless_deco
-XFail = svntest.testcase.XFail_deco
-Issues = svntest.testcase.Issues_deco
-Issue = svntest.testcase.Issue_deco
-Wimp = svntest.testcase.Wimp_deco
-
-from svntest.main import SVN_PROP_MERGEINFO
-from svntest.main import server_has_mergeinfo
-from merge_tests import local_path
-from merge_tests import expected_merge_output
-from merge_tests import svn_merge
-from merge_tests import set_up_branch
-
-#----------------------------------------------------------------------
-
-# Merging scenarios to test
-#
-# Merge once
-#
-# A (--?---
-# ( \
-# B (--?--x
-#
-# Merge twice in same direction
-#
-# A (--o-----?---
-# ( \ \
-# B (--o--x--?--x
-#
-# Merge to and fro
-#
-# A (--o-----?--x
-# ( \ /
-# B (--o--x--?---
-#
-# A (--o-----o-----?--x
-# ( \ \ /
-# B (--o--x--o--x--?---
-#
-# A (--o-----o--x--?--x
-# ( \ / /
-# B (--o--x--o-----?---
-#
-# A (--o-----o--x--?---
-# ( \ / \
-# B (--o--x--o-----?--x
-#
-# Merge with cherry-picks
-#
-# Cherry1, fwd
-# A (--o-----o-[o]----o---
-# ( \ \ \
-# B (--o--x--?-----c-----x
-#
-# Cherry2, fwd
-# A (--o-----?-----c--o---
-# ( \ / \
-# B (--o--x--o-[o]-------x
-#
-# Cherry3, fwd
-# A (--o-----?-------c--o----
-# ( \_____ / \
-# ( \ / \
-# B (--o--o-[o]-x-/---------x
-# \__/
-#
-# Cherry1, back
-# A (--o-----o-[o]-------x
-# ( \ \ /
-# B (--o--x--?-----c--o---
-#
-# Cherry2, back
-# A (--o-----?-----c-----x
-# ( \ / /
-# B (--o--x--o-[o]----o---
-#
-# Cherry3, back
-# A (--o-----?-------c------x
-# ( \_____ / /
-# ( \ / /
-# B (--o--o-[o]-x-/-----o----
-# \__/
-#
-# Criss-cross merge
-#
-# A (--o--?--x--?----
-# ( \ / \
-# ( X \
-# ( / \ \
-# B (--o--?--x--?---x
-#
-# Subtree mergeinfo
-#
-# subtree to, fro
-# A (--o-o-o-o---------x
-# ( \ \ /
-# ( \ \ /
-# B ( o--o------s--
-#
-# merge to, reverse cherry subtree to, merge to
-# A (--o-o-o-o------------------
-# ( \ \ \ \
-# ( \ \ \ \
-# B ( o--o------x-------rcs----x
-#
-# Sparse WC
-#
-# ...
-#
-# Mixed-rev WC
-#
-# ...
-#
-#
-# Key to diagrams:
-#
-# o - an original change
-# ? - an original change or no-op (test both)
-# x - a branch root merge
-# c - a cherry-pick merge
-# [o] - source range of a cherry-pick merge
-# s - a subtree merge
-# r - reverse merge
-
-
-########################################################################
-
-def assert_equal(a, b):
- """Assert that two generic Python objects are equal. If not, raise an
- exception giving their values. Rationale: During debugging, it's
- easier to see what's wrong if we see the values rather than just
- an indication that the assertion failed."""
- if a != b:
- raise Exception("assert_equal failed: a = (%s), b = (%s)" % (a, b))
-
-def logical_changes_in_branch(sbox, branch):
- """Return the set of logical changes that are actually in branch BRANCH
- (at its current working version), by examining the state of the
- branch files and directories rather than its mergeinfo.
-
- Each logical change is described by its branch and revision number
- as a string such as 'A1'."""
- changes = set()
- for propname in sbox.simple_proplist(branch + '/D').keys():
- if propname.startswith('prop-'):
- changes.add(propname[5:])
- return changes
-
-def get_mergeinfo_change(sbox, target):
- """Return a list of revision numbers representing the mergeinfo change
- on TARGET (working version against base). Non-recursive."""
- exit, out, err = actions.run_and_verify_svn(None, None, [],
- 'diff', '--depth=empty',
- sbox.ospath(target))
- merged_revs = []
- for line in out:
- match = re.match(r' Merged /(\w+):r(.*)', line)
- if match:
- for r_range in match.group(2).split(','):
- if '-' in r_range:
- r_start, r_end = r_range.split('-')
- else:
- r_start = r_end = r_range
- merged_revs += range(int(r_start), int(r_end) + 1)
- return merged_revs
-
-def get_3ways_from_output(output):
- """Scan the list of lines OUTPUT for indications of 3-way merges.
- Return a list of (base, source-right) tuples."""
- ### Problem: test suite strips debugging output within run_and_verify_...()
- ### so we don't see it here. And relying on debug output is a temporary
- ### measure only. Better to access svn_client_find_symmetric_merge()
- ### directly, via bindings?
-
- merges = []
- for line in output:
- print "## " + line,
- # Extract "A1" from a line like "DBG: merge.c:11336: base svn://.../A@1"
- match = re.search(r'merge\.c:.* base .* /(\w+)@([0-9-]+)', line)
- if match:
- base = match.group(1) + match.group(2)
- match = re.search(r'merge\.c:.* right.* /(\w+)@([0-9-]+)', line)
- if match:
- right = match.group(1) + match.group(2)
- assert base is not None
- merges.append((base, right))
- base = None
- return merges
-
-def make_branches(sbox):
- """Make branches A and B."""
- sbox.build()
- sbox.simple_copy('A', 'B')
- sbox.simple_commit()
- os.chdir(sbox.wc_dir)
- sbox.wc_dir = ''
-
-def modify_branch(sbox, branch, number, conflicting=False):
- """Commit a modification to branch BRANCH. The actual modification depends
- on NUMBER. If CONFLICTING=True, the change will be of a kind that
- conflicts with any other change that has CONFLICTING=True. We don't
- modify (properties on) the branch root node itself, to make it easier
- for the tests to distinguish mergeinfo changes from these mods."""
- uniq = branch + str(number) # something like 'A1' or 'B2'
- if conflicting:
- sbox.simple_propset('conflict', uniq, branch + '/C')
- else:
- # Make some changes. We add a property, which we will read later in
- # logical_changes_in_branch() to check that the correct logical
- # changes were merged. We add a file, so that we will notice if
- # Subversion tries to merge this same logical change into a branch
- # that already has it (it will raise a tree conflict).
- sbox.simple_propset('prop-' + uniq, uniq, branch + '/D')
- sbox.simple_copy(branch + '/mu', branch + '/mu-' + uniq)
- sbox.simple_commit()
-
-def expected_symmetric_merge_output(target, expect_3ways):
- """Calculate the expected output."""
-
- # (This is rather specific to the current implementation.)
-
- # Match a notification for each rev-range.
- if expect_3ways:
- rev_ranges = []
- for base, right in expect_3ways:
- if base[0] == right[0]:
- base_rev = int(base[1:])
- right_rev = int(right[1:])
- rev_ranges += [(base_rev + 1, right_rev)];
- else:
- rev_ranges = None
-
- # Match any content modifications; but not of the root of the branch
- # because we don't intentionally modify the branch root node in most
- # tests and we don't want to accidentally overlook a mergeinfo change.
- lines = ["(A |D |[UG] | [UG]|[UG][UG]) " + target + os.path.sep + ".*\n"]
-
- # Match mergeinfo changes. (### Subtrees are not yet supported here.)
- lines += [" [UG] " + target + "\n"]
-
- # At the moment, the symmetric merge code sometimes says 'Merging
- # differences between repository URLs' and sometimes 'Merging r3 through
- # r5', but it's not trivial to predict which, so expect either form.
- lines += ["--- Merging .* into '%s':\n" % (target,),
- "--- Recording mergeinfo for merge .* into '%s':\n" % (target,)]
-
- return expected_merge_output(rev_ranges, lines, target=target)
-
-def symmetric_merge(sbox, source, target, args=[],
- expect_changes=None, expect_mi=None, expect_3ways=None):
- """Do a complete, automatic merge from path SOURCE to path TARGET, and
- commit. Verify the output and that there is no error.
- ### TODO: Verify the changes made.
-
- ARGS are additional arguments passed to svn merge."""
-
- source = local_path(source)
- target = local_path(target)
-
- # First, update the WC target because mixed-rev is not fully supported.
- sbox.simple_update(target)
-
- before_changes = logical_changes_in_branch(sbox, target)
-
- exp_out = expected_symmetric_merge_output(target, expect_3ways)
- exit, out, err = svntest.actions.run_and_verify_svn(None, exp_out, [],
- 'merge',
- '^/' + source, target,
- *args)
-
- if expect_changes is not None:
- after_changes = logical_changes_in_branch(sbox, target)
- merged_changes = after_changes - before_changes
- assert_equal(merged_changes, set(expect_changes))
- reversed_changes = before_changes - after_changes
- assert_equal(reversed_changes, set())
-
- if expect_mi is not None:
- actual_mi_change = get_mergeinfo_change(sbox, target)
- assert_equal(actual_mi_change, expect_mi)
-
- if expect_3ways is not None:
- ### actual_3ways = get_3ways_from_output(out)
- ### assert_equal(actual_3ways, expect_3ways)
- pass
-
- sbox.simple_commit()
-
-def three_way_merge(base_node, source_right_node):
- return (base_node, source_right_node)
-
-def three_way_merge_no_op(base_node, source_right_node):
- return (base_node, source_right_node)
-
-def cherry_pick(sbox, rev, source, target):
- """Cherry-pick merge revision REV from branch SOURCE to branch TARGET
- (both WC-relative paths), and commit."""
- sbox.simple_update(target)
- svn_merge(rev, source, target)
- sbox.simple_commit()
-
-no_op_commit__n = 0
-def no_op_commit(sbox):
- """Commit a new revision that does not affect the branches under test."""
-
- global no_op_commit__n
- sbox.simple_propset('foo', str(no_op_commit__n), 'iota')
- no_op_commit__n += 1
- sbox.simple_commit('iota')
-
-
-#----------------------------------------------------------------------
-
-def init_mod_merge_mod(sbox, mod_6, mod_7):
- """Modify both branches, merge A -> B, optionally modify again.
- MOD_6 is True to modify A in r6, MOD_7 is True to modify B in r7,
- otherwise make no-op commits for r6 and/or r7."""
-
- # A (--o------?-
- # ( \
- # B (---o--x---?
- # 2 34 5 67
-
- make_branches(sbox)
- modify_branch(sbox, 'A', 3)
- modify_branch(sbox, 'B', 4)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A3'],
- expect_mi=[2, 3, 4],
- expect_3ways=[three_way_merge('A1', 'A4')])
-
- if mod_6:
- modify_branch(sbox, 'A', 6)
- else:
- no_op_commit(sbox) # r6
-
- if mod_7:
- modify_branch(sbox, 'B', 7)
- else:
- no_op_commit(sbox) # r7
-
-########################################################################
-
-# Merge once
-
-@SkipUnless(server_has_mergeinfo)
-def merge_once_1(sbox):
- """merge_once_1"""
-
- # A (------
- # ( \
- # B (-----x
- # 2 34 5
-
- make_branches(sbox)
- no_op_commit(sbox) # r3
- no_op_commit(sbox) # r4
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=[],
- expect_mi=[2, 3, 4],
- expect_3ways=[three_way_merge_no_op('A1', 'A4')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_once_2(sbox):
- """merge_once_2"""
-
- # A (-o----
- # ( \
- # B (-----x
- # 2 34 5
-
- make_branches(sbox)
- modify_branch(sbox, 'A', 3)
- no_op_commit(sbox) # r4
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A3'],
- expect_mi=[2, 3, 4],
- expect_3ways=[three_way_merge('A1', 'A4')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_once_3(sbox):
- """merge_once_3"""
-
- # A (------
- # ( \
- # B (--o--x
- # 2 34 5
-
- make_branches(sbox)
- no_op_commit(sbox) # r3
- modify_branch(sbox, 'B', 4)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=[],
- expect_mi=[2, 3, 4],
- expect_3ways=[three_way_merge_no_op('A1', 'A4')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_once_4(sbox):
- """merge_once_4"""
-
- # A (-o----
- # ( \
- # B (--o--x
- # 2 34 5
-
- make_branches(sbox)
- modify_branch(sbox, 'A', 3)
- modify_branch(sbox, 'B', 4)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A3'],
- expect_mi=[2, 3, 4],
- expect_3ways=[three_way_merge('A1', 'A4')])
-
-#----------------------------------------------------------------------
-
-# Merge twice in same direction
-
-@SkipUnless(server_has_mergeinfo)
-def merge_twice_same_direction_1(sbox):
- """merge_twice_same_direction_1"""
-
- # A (--o-----------
- # ( \ \
- # B (---o--x------x
- # 2 34 5 67 8
-
- init_mod_merge_mod(sbox, mod_6=False, mod_7=False)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=[],
- expect_mi=[5, 6, 7],
- expect_3ways=[three_way_merge_no_op('A4', 'A7')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_twice_same_direction_2(sbox):
- """merge_twice_same_direction_2"""
-
- # A (--o------o----
- # ( \ \
- # B (---o--x---o--x
- # 2 34 5 67 8
-
- init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A6'],
- expect_mi=[5, 6, 7],
- expect_3ways=[three_way_merge('A4', 'A7')])
-
-#----------------------------------------------------------------------
-
-# Merge to and fro
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_1_1(sbox):
- """merge_to_and_fro_1_1"""
-
- # A (--o----------x
- # ( \ /
- # B (---o--x-------
- # 2 34 5 67 8
-
- init_mod_merge_mod(sbox, mod_6=False, mod_7=False)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=['B4'],
- expect_mi=[2, 3, 4, 5, 6, 7],
- expect_3ways=[three_way_merge('A4', 'B7')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_1_2(sbox):
- """merge_to_and_fro_1_2"""
-
- # A (--o------o---x
- # ( \ /
- # B (---o--x---o---
- # 2 34 5 67 8
-
- init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=['B4', 'B7'],
- expect_mi=[2, 3, 4, 5, 6, 7],
- expect_3ways=[three_way_merge('A4', 'B7')])
-
-def init_merge_to_and_fro_2(sbox, mod_9, mod_10):
- """Set up branches A and B for the merge_to_and_fro_2 scenarios.
- MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10,
- otherwise make no-op commits for r9 and/or r10."""
-
- # A (--o------o------?-
- # ( \ \
- # B (---o--x---o--x---?
- # 2 34 5 67 8--90
-
- init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A6'],
- expect_mi=[5, 6, 7],
- expect_3ways=[three_way_merge('A4', 'A7')])
-
- if mod_9:
- modify_branch(sbox, 'A', 9)
- else:
- no_op_commit(sbox) # r9
-
- if mod_10:
- modify_branch(sbox, 'B', 10)
- else:
- no_op_commit(sbox) # r10
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_2_1(sbox):
- """merge_to_and_fro_2_1"""
-
- # A (--o------o----------x
- # ( \ \ /
- # B (---o--x---o--x-------
- # 2 34 5 67 8 90 1
-
- init_merge_to_and_fro_2(sbox, mod_9=False, mod_10=False)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=['B4', 'B7'],
- expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10],
- expect_3ways=[three_way_merge('A7', 'B10')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_2_2(sbox):
- """merge_to_and_fro_2_2"""
-
- # A (--o------o------o---x
- # ( \ \ /
- # B (---o--x---o--x---o---
- # 2 34 5 67 8 90 1
-
- init_merge_to_and_fro_2(sbox, mod_9=True, mod_10=True)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=['B4', 'B7', 'B10'],
- expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10],
- expect_3ways=[three_way_merge('A7', 'B10')])
-
-def init_merge_to_and_fro_3(sbox, mod_9, mod_10):
- """Set up branches A and B for the merge_to_and_fro_3/4 scenarios.
- MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10,
- otherwise make no-op commits for r9 and/or r10."""
-
- # A (--o------o---x--?-
- # ( \ /
- # B (---o--x---o------?
- # 2 34 5 67 8 90
-
- init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=['B4', 'B7'],
- expect_mi=[2, 3, 4, 5, 6, 7],
- expect_3ways=[three_way_merge('A4', 'B7')])
-
- if mod_9:
- modify_branch(sbox, 'A', 9)
- else:
- no_op_commit(sbox) # r9
-
- if mod_10:
- modify_branch(sbox, 'B', 10)
- else:
- no_op_commit(sbox) # r10
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_3_1(sbox):
- """merge_to_and_fro_3_1"""
-
- # A (--o------o---x------x
- # ( \ / /
- # B (---o--x---o----------
- # 2 34 5 67 8 90 1
-
- init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=[],
- expect_mi=[8, 9, 10],
- expect_3ways=[three_way_merge_no_op('B7', 'B10')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_3_2(sbox):
- """merge_to_and_fro_3_2"""
-
- # A (--o------o---x--o---x
- # ( \ / /
- # B (---o--x---o------o---
- # 2 34 5 67 8 90 1
-
- init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True)
-
- symmetric_merge(sbox, 'B', 'A',
- expect_changes=['B10'],
- expect_mi=[8, 9, 10],
- expect_3ways=[three_way_merge('B7', 'B10')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_4_1(sbox):
- """merge_to_and_fro_4_1"""
-
- # A (--o------o---x-------
- # ( \ / \
- # B (---o--x---o---------x
- # 2 34 5 67 8 90 1
-
- init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A6'],
- expect_mi=[5, 6, 7, 8, 9, 10],
- expect_3ways=[three_way_merge_no_op('B7', 'A10')])
-
-@SkipUnless(server_has_mergeinfo)
-def merge_to_and_fro_4_2(sbox):
- """merge_to_and_fro_4_2"""
-
- # A (--o------o---x--o----
- # ( \ / \
- # B (---o--x---o------o--x
- # 2 34 5 67 8 90 1
-
- init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A6', 'A9'],
- expect_mi=[5, 6, 7, 8, 9, 10],
- expect_3ways=[three_way_merge('B7', 'A10')])
-
-#----------------------------------------------------------------------
-
-# Cherry-pick scenarios
-
-@SkipUnless(server_has_mergeinfo)
-def cherry1_fwd(sbox):
- """cherry1_fwd"""
-
- # A (--o------o--[o]----o---
- # ( \ \ \
- # B (---o--x---------c-----x
- # 2 34 5 67 8 9 0 1
-
- init_mod_merge_mod(sbox, mod_6=True, mod_7=False)
- modify_branch(sbox, 'A', 8)
- cherry_pick(sbox, 8, 'A', 'B')
- modify_branch(sbox, 'A', 10)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A6', 'A10'], # and NOT A8
- expect_mi=[5, 6, 7, 9, 10],
- expect_3ways=[three_way_merge('A4', 'A7'),
- three_way_merge('A8', 'A10')])
-
-@SkipUnless(server_has_mergeinfo)
-@XFail()
-def cherry2_fwd(sbox):
- """cherry2_fwd"""
-
- # A (--o-------------c--o---
- # ( \ / \
- # B (---o--x---o-[o]-------x
- # 2 34 5 67 8 9 0 1
-
- init_mod_merge_mod(sbox, mod_6=False, mod_7=True)
- modify_branch(sbox, 'B', 8)
- cherry_pick(sbox, 8, 'B', 'A')
- modify_branch(sbox, 'A', 10)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A10'], # and NOT A9
- expect_mi=[5, 6, 7, 8, 9, 10],
- expect_3ways=[three_way_merge('A9', 'A10')])
-
-@SkipUnless(server_has_mergeinfo)
-@XFail()
-def cherry3_fwd(sbox):
- """cherry3_fwd"""
-
- # A (--o--------------c--o----
- # ( \ / \
- # ( \ / \
- # B (---o--o-[o]-x-/---------x
- # \__/
- # 2 34 5 6 7 8 9 0
-
- make_branches(sbox)
- modify_branch(sbox, 'A', 3)
- modify_branch(sbox, 'B', 4)
- modify_branch(sbox, 'B', 5)
- modify_branch(sbox, 'B', 6)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A3'],
- expect_mi=[2, 3, 4, 5, 6],
- expect_3ways=[three_way_merge('A1', 'A6')])
-
- cherry_pick(sbox, 6, 'B', 'A')
- modify_branch(sbox, 'A', 9)
-
- symmetric_merge(sbox, 'A', 'B',
- expect_changes=['A9'], # and NOT A8
- expect_mi=[7, 8, 9],
- expect_3ways=[three_way_merge('A8', 'A9')])
-
-#----------------------------------------------------------------------
-# Symmetric merges ignore subtree mergeinfo during reintegrate.
-@SkipUnless(server_has_mergeinfo)
-@XFail()
-def subtree_to_and_fro(sbox):
- "reintegrate considers source subtree mergeinfo"
-
-# A (--o-o-o-o---------x
-# ( \ \ /
-# ( \ \ /
-# B ( o--o------s--
-
- # Some paths we'll care about.
- A_COPY_gamma_path = sbox.ospath('A_COPY/D/gamma')
- psi_path = sbox.ospath('A/D/H/psi')
- A_COPY_D_path = sbox.ospath('A_COPY/D')
- A_path = sbox.ospath('A')
-
- sbox.build()
- wc_dir = sbox.wc_dir
-
- # Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then
- # make a few edits under A in r3-6:
- wc_disk, wc_status = set_up_branch(sbox)
-
- # r7 - Edit a file on the branch.
- svntest.main.file_write(A_COPY_gamma_path, "Branch edit to 'gamma'.\n")
- svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
- '-m', 'Edit a file on our branch')
-
- # r8 - Do a subtree sync merge from ^/A/D to A_COPY/D.
- # Note that among other things this changes A_COPY/D/H/psi.
- svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
- svntest.actions.run_and_verify_svn(None, None, [], 'merge',
- sbox.repo_url + '/A/D', A_COPY_D_path)
- svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
- '-m', 'Symmetric subtree merge')
-
- # r9 - Make an edit to A/D/H/psi.
- svntest.main.file_write(psi_path, "Trunk Edit to 'psi'.\n")
- svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
- '-m', 'Edit a file on our trunk')
-
- # Now reintegrate ^/A_COPY back to A. To the symmetric merge code the
- # subtree merge to A_COPY/D just looks like any other branch edit, it is
- # not considered a merge. So the changes which exist on A/D and were
- # merged to A_COPY/D, are merged *back* to A, resulting in a conflict:
- #
- # C:\SVN\src-trunk\Debug\subversion\tests\cmdline\svn-test-work\
- # working_copies\merge_symmetric_tests-18>svn merge ^^/A_COPY A
- # DBG: merge.c:11461: base on source: file:///C:/SVN/src-trunk/Debug/
- # subversion/tests/cmdline/svn-test-work/repositories/
- # merge_symmetric_tests-18/A@1
- # DBG: merge.c:11462: base on target: file:///C:/SVN/src-trunk/Debug/
- # subversion/tests/cmdline/svn-test-work/repositories/
- # merge_symmetric_tests-18/A@1
- # DBG: merge.c:11567: yca file:///C:/SVN/src-trunk/Debug/subversion/
- # tests/cmdline/svn-test-work/repositories/merge_symmetric_tests-18/A@1
- # DBG: merge.c:11568: base file:///C:/SVN/src-trunk/Debug/subversion/
- # tests/cmdline/svn-test-work/repositories/merge_symmetric_tests-18/A@1
- # DBG: merge.c:11571: right file:///C:/SVN/src-trunk/Debug/subversion/
- # tests/cmdline/svn-test-work/repositories/merge_symmetric_tests-18/
- # A_COPY@8
- # Conflict discovered in file 'A\D\H\psi'.
- # Select: (p) postpone, (df) diff-full, (e) edit,
- # (mc) mine-conflict, (tc) theirs-conflict,
- # (s) show all options: p
- # --- Merging r2 through r9 into 'A':
- # C A\D\H\psi
- # U A\D\gamma
- # --- Recording mergeinfo for merge of r2 through r9 into 'A':
- # U A
- # Summary of conflicts:
- # Text conflicts: 1
- svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
- exit_code, out, err = svntest.actions.run_and_verify_svn(
- None, [], svntest.verify.AnyOutput,
- 'merge', sbox.repo_url + '/A_COPY', A_path)
-
- # The 'old' merge produced a warning that reintegrate could not be used.
- # Not claiming this is perfect, but it's better(?) than a conflict:
- svntest.verify.verify_outputs("Symmetric Reintegrate failed, but not "
- "in the way expected",
- err, None,
- "(svn: E195016: Reintegrate can only be used if "
- "revisions 2 through 9 were previously "
- "merged from .*/A to the reintegrate source, "
- "but this is not the case:\n)"
- "|( A_COPY\n)"
- "|( Missing ranges: /A:5\n)"
- "|(\n)"
- "|(.*apr_err.*)", # In case of debug build
- None,
- True) # Match *all* lines of stdout
-
-#----------------------------------------------------------------------
-# Symmetric merges ignore subtree mergeinfo gaps older than the last rev
-# synced to the target root.
-@SkipUnless(server_has_mergeinfo)
-def merge_to_reverse_cherry_subtree_to_merge_to(sbox):
- "sync merge considers target subtree mergeinfo"
-
- # A (--o-o-o-o------------------
- # ( \ \ \ \
- # ( \ \ \ \
- # B ( o--o------x-------rc-----x
-
- # Some paths we'll care about.
- A_COPY_path = sbox.ospath('A_COPY')
- A_COPY_B_path = sbox.ospath('A_COPY/B')
- A_COPY_beta_path = sbox.ospath('A_COPY/B/E/beta')
-
- sbox.build()
- wc_dir = sbox.wc_dir
-
- # Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then
- # make a few edits under A in r3-6:
- wc_disk, wc_status = set_up_branch(sbox)
-
- # Sync merge ^/A to A_COPY, then reverse merge r5 from ^/A/B to A_COPY/B.
- # This results in mergeinfo on the target which makes it appear that the
- # branch is synced up to r6, but the subtree mergeinfo on A_COPY/B reveals
- # that r5 has not been merged to that subtree:
- #
- # Properties on 'A_COPY':
- # svn:mergeinfo
- # /A:2-6
- # Properties on 'A_COPY\B':
- # svn:mergeinfo
- # /A/B:2-4,6
- svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
- svntest.actions.run_and_verify_svn(None, None, [], 'merge',
- sbox.repo_url + '/A', A_COPY_path)
- svntest.actions.run_and_verify_svn(None, None, [], 'merge', '-c-5',
- sbox.repo_url + '/A/B',
- A_COPY_B_path)
- svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, '-m',
- 'sync merge and reverse subtree merge')
-
- # Try a symmetric sync merge from ^/A to A_COPY. Revision 5 should be
- # merged to A_COPY/B as its subtree mergeinfo reveals that rev is missing,
- # like so:
- #
- # >svn merge ^/A A_COPY
- # --- Merging r5 into 'A_COPY\B':
- # U A_COPY\B\E\beta
- # --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY':
- # U A_COPY
- # --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY\B':
- # U A_COPY\B
- # --- Eliding mergeinfo from 'A_COPY\B':
- # U A_COPY\B
- #
- # But the merge ignores the subtree mergeinfo and considers
- # only the mergeinfo on the target itself (and thus is a no-op but for
- # the mergeinfo change on the root of the merge target):
- #
- # >svn merge ^/A A_COPY
- # --- Recording mergeinfo for merge of r7 into 'A_COPY':
- # U A_COPY
- #
- # >svn diff
- # Index: A_COPY
- # ===================================================================
- # --- A_COPY (revision 7)
- # +++ A_COPY (working copy)
- #
- # Property changes on: A_COPY
- # ___________________________________________________________________
- # Modified: svn:mergeinfo
- # Merged /A:r7
- svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
- expected_output = wc.State(A_COPY_path, {
- 'B/E/beta' : Item(status='U '),
- })
- expected_mergeinfo_output = wc.State(A_COPY_path, {
- '' : Item(status=' U'),
- 'B' : Item(status=' U'),
- })
- expected_elision_output = wc.State(A_COPY_path, {
- 'B' : Item(status=' U'),
- })
- expected_status = wc.State(A_COPY_path, {
- '' : Item(status=' M'),
- 'B' : Item(status=' M'),
- 'mu' : Item(status=' '),
- 'B/E' : Item(status=' '),
- 'B/E/alpha' : Item(status=' '),
- 'B/E/beta' : Item(status='M '),
- 'B/lambda' : Item(status=' '),
- 'B/F' : Item(status=' '),
- 'C' : Item(status=' '),
- 'D' : Item(status=' '),
- 'D/G' : Item(status=' '),
- 'D/G/pi' : Item(status=' '),
- 'D/G/rho' : Item(status=' '),
- 'D/G/tau' : Item(status=' '),
- 'D/gamma' : Item(status=' '),
- 'D/H' : Item(status=' '),
- 'D/H/chi' : Item(status=' '),
- 'D/H/psi' : Item(status=' '),
- 'D/H/omega' : Item(status=' '),
- })
- expected_status.tweak(wc_rev='7')
- expected_disk = wc.State('', {
- '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-7'}),
- 'B' : Item(),
- 'mu' : Item("This is the file 'mu'.\n"),
- 'B/E' : Item(),
- 'B/E/alpha' : Item("This is the file 'alpha'.\n"),
- 'B/E/beta' : Item("New content"),
- 'B/lambda' : Item("This is the file 'lambda'.\n"),
- 'B/F' : Item(),
- 'C' : Item(),
- 'D' : Item(),
- 'D/G' : Item(),
- 'D/G/pi' : Item("This is the file 'pi'.\n"),
- 'D/G/rho' : Item("New content"),
- 'D/G/tau' : Item("This is the file 'tau'.\n"),
- 'D/gamma' : Item("This is the file 'gamma'.\n"),
- 'D/H' : Item(),
- 'D/H/chi' : Item("This is the file 'chi'.\n"),
- 'D/H/psi' : Item("New content"),
- 'D/H/omega' : Item("New content"),
- })
- expected_skip = wc.State(A_COPY_path, { })
- svntest.actions.run_and_verify_merge(A_COPY_path, None, None,
- sbox.repo_url + '/A', None,
- expected_output,
- expected_mergeinfo_output,
- expected_elision_output,
- expected_disk,
- expected_status,
- expected_skip,
- None, None, None, None,
- None, 1, 0, A_COPY_path)
-
-########################################################################
-# Run the tests
-
-
-# list all tests here, starting with None:
-test_list = [ None,
- merge_once_1,
- merge_once_2,
- merge_once_3,
- merge_once_4,
- merge_twice_same_direction_1,
- merge_twice_same_direction_2,
- merge_to_and_fro_1_1,
- merge_to_and_fro_1_2,
- merge_to_and_fro_2_1,
- merge_to_and_fro_2_2,
- merge_to_and_fro_3_1,
- merge_to_and_fro_3_2,
- merge_to_and_fro_4_1,
- merge_to_and_fro_4_2,
- cherry1_fwd,
- cherry2_fwd,
- cherry3_fwd,
- subtree_to_and_fro,
- merge_to_reverse_cherry_subtree_to_merge_to,
- ]
-
-if __name__ == '__main__':
- svntest.main.run_tests(test_list)
- # NOTREACHED
-
-
-### End of file.
+#!/usr/bin/env python
+#
+# merge_automatic_tests.py: testing "automatic merge" scenarios
+#
+# Subversion is a tool for revision control.
+# See http://subversion.apache.org for more information.
+#
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+######################################################################
+
+# General modules
+import shutil, sys, re, os
+import time
+
+# Our testing module
+import svntest
+from svntest import main, wc, verify, actions
+
+# (abbreviation)
+Item = wc.StateItem
+Skip = svntest.testcase.Skip_deco
+SkipUnless = svntest.testcase.SkipUnless_deco
+XFail = svntest.testcase.XFail_deco
+Issues = svntest.testcase.Issues_deco
+Issue = svntest.testcase.Issue_deco
+Wimp = svntest.testcase.Wimp_deco
+
+from svntest.main import SVN_PROP_MERGEINFO
+from svntest.main import server_has_mergeinfo
+from merge_tests import local_path
+from merge_tests import expected_merge_output
+from merge_tests import svn_merge
+from merge_tests import set_up_branch
+
+#----------------------------------------------------------------------
+
+# Merging scenarios to test
+#
+# Merge once
+#
+# A (--?---
+# ( \
+# B (--?--x
+#
+# Merge twice in same direction
+#
+# A (--o-----?---
+# ( \ \
+# B (--o--x--?--x
+#
+# Merge to and fro
+#
+# A (--o-----?--x
+# ( \ /
+# B (--o--x--?---
+#
+# A (--o-----o-----?--x
+# ( \ \ /
+# B (--o--x--o--x--?---
+#
+# A (--o-----o--x--?--x
+# ( \ / /
+# B (--o--x--o-----?---
+#
+# A (--o-----o--x--?---
+# ( \ / \
+# B (--o--x--o-----?--x
+#
+# Merge with cherry-picks
+#
+# Cherry1, fwd
+# A (--o-----o-[o]----o---
+# ( \ \ \
+# B (--o--x--?-----c-----x
+#
+# Cherry2, fwd
+# A (--o-----?-----c--o---
+# ( \ / \
+# B (--o--x--o-[o]-------x
+#
+# Cherry3, fwd
+# A (--o-----?-------c--o----
+# ( \_____ / \
+# ( \ / \
+# B (--o--o-[o]-x-/---------x
+# \__/
+#
+# Cherry1, back
+# A (--o-----o-[o]-------x
+# ( \ \ /
+# B (--o--x--?-----c--o---
+#
+# Cherry2, back
+# A (--o-----?-----c-----x
+# ( \ / /
+# B (--o--x--o-[o]----o---
+#
+# Cherry3, back
+# A (--o-----?-------c------x
+# ( \_____ / /
+# ( \ / /
+# B (--o--o-[o]-x-/-----o----
+# \__/
+#
+# Criss-cross merge
+#
+# A (--o--?--x--?----
+# ( \ / \
+# ( X \
+# ( / \ \
+# B (--o--?--x--?---x
+#
+# Subtree mergeinfo
+#
+# subtree to, fro
+# A (--o-o-o-o---------x
+# ( \ \ /
+# ( \ \ /
+# B ( o--o------s--
+#
+# merge to, reverse cherry subtree to, merge to
+# A (--o-o-o-o------------------
+# ( \ \ \ \
+# ( \ \ \ \
+# B ( o--o------x-------rcs----x
+#
+# Sparse WC
+#
+# ...
+#
+# Mixed-rev WC
+#
+# ...
+#
+#
+# Key to diagrams:
+#
+# o - an original change
+# ? - an original change or no-op (test both)
+# x - a branch root merge
+# c - a cherry-pick merge
+# [o] - source range of a cherry-pick merge
+# s - a subtree merge
+# r - reverse merge
+
+
+########################################################################
+
+def assert_equal(a, b):
+ """Assert that two generic Python objects are equal. If not, raise an
+ exception giving their values. Rationale: During debugging, it's
+ easier to see what's wrong if we see the values rather than just
+ an indication that the assertion failed."""
+ if a != b:
+ raise Exception("assert_equal failed: a = (%s), b = (%s)" % (a, b))
+
+def logical_changes_in_branch(sbox, branch):
+ """Return the set of logical changes that are actually in branch BRANCH
+ (at its current working version), by examining the state of the
+ branch files and directories rather than its mergeinfo.
+
+ Each logical change is described by its branch and revision number
+ as a string such as 'A1'."""
+ changes = set()
+ for propname in sbox.simple_proplist(branch + '/D').keys():
+ if propname.startswith('prop-'):
+ changes.add(propname[5:])
+ return changes
+
+def get_mergeinfo_change(sbox, target):
+ """Return a list of revision numbers representing the mergeinfo change
+ on TARGET (working version against base). Non-recursive."""
+ exit, out, err = actions.run_and_verify_svn(None, None, [],
+ 'diff', '--depth=empty',
+ sbox.ospath(target))
+ merged_revs = []
+ for line in out:
+ match = re.match(r' Merged /(\w+):r(.*)', line)
+ if match:
+ for r_range in match.group(2).split(','):
+ if '-' in r_range:
+ r_start, r_end = r_range.split('-')
+ else:
+ r_start = r_end = r_range
+ merged_revs += range(int(r_start), int(r_end) + 1)
+ return merged_revs
+
+def get_3ways_from_output(output):
+ """Scan the list of lines OUTPUT for indications of 3-way merges.
+ Return a list of (base, source-right) tuples."""
+ ### Problem: test suite strips debugging output within run_and_verify_...()
+ ### so we don't see it here. And relying on debug output is a temporary
+ ### measure only. Better to access svn_client_find_automatic_merge()
+ ### directly, via bindings?
+
+ merges = []
+ for line in output:
+ print "## " + line,
+ # Extract "A1" from a line like "DBG: merge.c:11336: base svn://.../A@1"
+ match = re.search(r'merge\.c:.* base .* /(\w+)@([0-9-]+)', line)
+ if match:
+ base = match.group(1) + match.group(2)
+ match = re.search(r'merge\.c:.* right.* /(\w+)@([0-9-]+)', line)
+ if match:
+ right = match.group(1) + match.group(2)
+ assert base is not None
+ merges.append((base, right))
+ base = None
+ return merges
+
+def make_branches(sbox):
+ """Make branches A and B."""
+ sbox.build()
+ sbox.simple_copy('A', 'B')
+ sbox.simple_commit()
+ os.chdir(sbox.wc_dir)
+ sbox.wc_dir = ''
+
+def modify_branch(sbox, branch, number, conflicting=False):
+ """Commit a modification to branch BRANCH. The actual modification depends
+ on NUMBER. If CONFLICTING=True, the change will be of a kind that
+ conflicts with any other change that has CONFLICTING=True. We don't
+ modify (properties on) the branch root node itself, to make it easier
+ for the tests to distinguish mergeinfo changes from these mods."""
+ uniq = branch + str(number) # something like 'A1' or 'B2'
+ if conflicting:
+ sbox.simple_propset('conflict', uniq, branch + '/C')
+ else:
+ # Make some changes. We add a property, which we will read later in
+ # logical_changes_in_branch() to check that the correct logical
+ # changes were merged. We add a file, so that we will notice if
+ # Subversion tries to merge this same logical change into a branch
+ # that already has it (it will raise a tree conflict).
+ sbox.simple_propset('prop-' + uniq, uniq, branch + '/D')
+ sbox.simple_copy(branch + '/mu', branch + '/mu-' + uniq)
+ sbox.simple_commit()
+
+def expected_automatic_merge_output(target, expect_3ways):
+ """Calculate the expected output."""
+
+ # (This is rather specific to the current implementation.)
+
+ # Match a notification for each rev-range.
+ if expect_3ways:
+ rev_ranges = []
+ for base, right in expect_3ways:
+ if base[0] == right[0]:
+ base_rev = int(base[1:])
+ right_rev = int(right[1:])
+ rev_ranges += [(base_rev + 1, right_rev)];
+ else:
+ rev_ranges = None
+
+ # Match any content modifications; but not of the root of the branch
+ # because we don't intentionally modify the branch root node in most
+ # tests and we don't want to accidentally overlook a mergeinfo change.
+ lines = ["(A |D |[UG] | [UG]|[UG][UG]) " + target + os.path.sep + ".*\n"]
+
+ # Match mergeinfo changes. (### Subtrees are not yet supported here.)
+ lines += [" [UG] " + target + "\n"]
+
+ # At the moment, the automatic merge code sometimes says 'Merging
+ # differences between repository URLs' and sometimes 'Merging r3 through
+ # r5', but it's not trivial to predict which, so expect either form.
+ lines += ["--- Merging .* into '%s':\n" % (target,),
+ "--- Recording mergeinfo for merge .* into '%s':\n" % (target,)]
+
+ return expected_merge_output(rev_ranges, lines, target=target)
+
+def automatic_merge(sbox, source, target, args=[],
+ expect_changes=None, expect_mi=None, expect_3ways=None):
+ """Do a complete, automatic merge from path SOURCE to path TARGET, and
+ commit. Verify the output and that there is no error.
+ ### TODO: Verify the changes made.
+
+ ARGS are additional arguments passed to svn merge."""
+
+ source = local_path(source)
+ target = local_path(target)
+
+ # First, update the WC target because mixed-rev is not fully supported.
+ sbox.simple_update(target)
+
+ before_changes = logical_changes_in_branch(sbox, target)
+
+ exp_out = expected_automatic_merge_output(target, expect_3ways)
+ exit, out, err = svntest.actions.run_and_verify_svn(None, exp_out, [],
+ 'merge',
+ '^/' + source, target,
+ *args)
+
+ if expect_changes is not None:
+ after_changes = logical_changes_in_branch(sbox, target)
+ merged_changes = after_changes - before_changes
+ assert_equal(merged_changes, set(expect_changes))
+ reversed_changes = before_changes - after_changes
+ assert_equal(reversed_changes, set())
+
+ if expect_mi is not None:
+ actual_mi_change = get_mergeinfo_change(sbox, target)
+ assert_equal(actual_mi_change, expect_mi)
+
+ if expect_3ways is not None:
+ ### actual_3ways = get_3ways_from_output(out)
+ ### assert_equal(actual_3ways, expect_3ways)
+ pass
+
+ sbox.simple_commit()
+
+def three_way_merge(base_node, source_right_node):
+ return (base_node, source_right_node)
+
+def three_way_merge_no_op(base_node, source_right_node):
+ return (base_node, source_right_node)
+
+def cherry_pick(sbox, rev, source, target):
+ """Cherry-pick merge revision REV from branch SOURCE to branch TARGET
+ (both WC-relative paths), and commit."""
+ sbox.simple_update(target)
+ svn_merge(rev, source, target)
+ sbox.simple_commit()
+
+no_op_commit__n = 0
+def no_op_commit(sbox):
+ """Commit a new revision that does not affect the branches under test."""
+
+ global no_op_commit__n
+ sbox.simple_propset('foo', str(no_op_commit__n), 'iota')
+ no_op_commit__n += 1
+ sbox.simple_commit('iota')
+
+
+#----------------------------------------------------------------------
+
+def init_mod_merge_mod(sbox, mod_6, mod_7):
+ """Modify both branches, merge A -> B, optionally modify again.
+ MOD_6 is True to modify A in r6, MOD_7 is True to modify B in r7,
+ otherwise make no-op commits for r6 and/or r7."""
+
+ # A (--o------?-
+ # ( \
+ # B (---o--x---?
+ # 2 34 5 67
+
+ make_branches(sbox)
+ modify_branch(sbox, 'A', 3)
+ modify_branch(sbox, 'B', 4)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A3'],
+ expect_mi=[2, 3, 4],
+ expect_3ways=[three_way_merge('A1', 'A4')])
+
+ if mod_6:
+ modify_branch(sbox, 'A', 6)
+ else:
+ no_op_commit(sbox) # r6
+
+ if mod_7:
+ modify_branch(sbox, 'B', 7)
+ else:
+ no_op_commit(sbox) # r7
+
+########################################################################
+
+# Merge once
+
+@SkipUnless(server_has_mergeinfo)
+def merge_once_1(sbox):
+ """merge_once_1"""
+
+ # A (------
+ # ( \
+ # B (-----x
+ # 2 34 5
+
+ make_branches(sbox)
+ no_op_commit(sbox) # r3
+ no_op_commit(sbox) # r4
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=[],
+ expect_mi=[2, 3, 4],
+ expect_3ways=[three_way_merge_no_op('A1', 'A4')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_once_2(sbox):
+ """merge_once_2"""
+
+ # A (-o----
+ # ( \
+ # B (-----x
+ # 2 34 5
+
+ make_branches(sbox)
+ modify_branch(sbox, 'A', 3)
+ no_op_commit(sbox) # r4
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A3'],
+ expect_mi=[2, 3, 4],
+ expect_3ways=[three_way_merge('A1', 'A4')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_once_3(sbox):
+ """merge_once_3"""
+
+ # A (------
+ # ( \
+ # B (--o--x
+ # 2 34 5
+
+ make_branches(sbox)
+ no_op_commit(sbox) # r3
+ modify_branch(sbox, 'B', 4)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=[],
+ expect_mi=[2, 3, 4],
+ expect_3ways=[three_way_merge_no_op('A1', 'A4')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_once_4(sbox):
+ """merge_once_4"""
+
+ # A (-o----
+ # ( \
+ # B (--o--x
+ # 2 34 5
+
+ make_branches(sbox)
+ modify_branch(sbox, 'A', 3)
+ modify_branch(sbox, 'B', 4)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A3'],
+ expect_mi=[2, 3, 4],
+ expect_3ways=[three_way_merge('A1', 'A4')])
+
+#----------------------------------------------------------------------
+
+# Merge twice in same direction
+
+@SkipUnless(server_has_mergeinfo)
+def merge_twice_same_direction_1(sbox):
+ """merge_twice_same_direction_1"""
+
+ # A (--o-----------
+ # ( \ \
+ # B (---o--x------x
+ # 2 34 5 67 8
+
+ init_mod_merge_mod(sbox, mod_6=False, mod_7=False)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=[],
+ expect_mi=[5, 6, 7],
+ expect_3ways=[three_way_merge_no_op('A4', 'A7')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_twice_same_direction_2(sbox):
+ """merge_twice_same_direction_2"""
+
+ # A (--o------o----
+ # ( \ \
+ # B (---o--x---o--x
+ # 2 34 5 67 8
+
+ init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A6'],
+ expect_mi=[5, 6, 7],
+ expect_3ways=[three_way_merge('A4', 'A7')])
+
+#----------------------------------------------------------------------
+
+# Merge to and fro
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_1_1(sbox):
+ """merge_to_and_fro_1_1"""
+
+ # A (--o----------x
+ # ( \ /
+ # B (---o--x-------
+ # 2 34 5 67 8
+
+ init_mod_merge_mod(sbox, mod_6=False, mod_7=False)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=['B4'],
+ expect_mi=[2, 3, 4, 5, 6, 7],
+ expect_3ways=[three_way_merge('A4', 'B7')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_1_2(sbox):
+ """merge_to_and_fro_1_2"""
+
+ # A (--o------o---x
+ # ( \ /
+ # B (---o--x---o---
+ # 2 34 5 67 8
+
+ init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=['B4', 'B7'],
+ expect_mi=[2, 3, 4, 5, 6, 7],
+ expect_3ways=[three_way_merge('A4', 'B7')])
+
+def init_merge_to_and_fro_2(sbox, mod_9, mod_10):
+ """Set up branches A and B for the merge_to_and_fro_2 scenarios.
+ MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10,
+ otherwise make no-op commits for r9 and/or r10."""
+
+ # A (--o------o------?-
+ # ( \ \
+ # B (---o--x---o--x---?
+ # 2 34 5 67 8--90
+
+ init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A6'],
+ expect_mi=[5, 6, 7],
+ expect_3ways=[three_way_merge('A4', 'A7')])
+
+ if mod_9:
+ modify_branch(sbox, 'A', 9)
+ else:
+ no_op_commit(sbox) # r9
+
+ if mod_10:
+ modify_branch(sbox, 'B', 10)
+ else:
+ no_op_commit(sbox) # r10
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_2_1(sbox):
+ """merge_to_and_fro_2_1"""
+
+ # A (--o------o----------x
+ # ( \ \ /
+ # B (---o--x---o--x-------
+ # 2 34 5 67 8 90 1
+
+ init_merge_to_and_fro_2(sbox, mod_9=False, mod_10=False)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=['B4', 'B7'],
+ expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10],
+ expect_3ways=[three_way_merge('A7', 'B10')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_2_2(sbox):
+ """merge_to_and_fro_2_2"""
+
+ # A (--o------o------o---x
+ # ( \ \ /
+ # B (---o--x---o--x---o---
+ # 2 34 5 67 8 90 1
+
+ init_merge_to_and_fro_2(sbox, mod_9=True, mod_10=True)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=['B4', 'B7', 'B10'],
+ expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10],
+ expect_3ways=[three_way_merge('A7', 'B10')])
+
+def init_merge_to_and_fro_3(sbox, mod_9, mod_10):
+ """Set up branches A and B for the merge_to_and_fro_3/4 scenarios.
+ MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10,
+ otherwise make no-op commits for r9 and/or r10."""
+
+ # A (--o------o---x--?-
+ # ( \ /
+ # B (---o--x---o------?
+ # 2 34 5 67 8 90
+
+ init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=['B4', 'B7'],
+ expect_mi=[2, 3, 4, 5, 6, 7],
+ expect_3ways=[three_way_merge('A4', 'B7')])
+
+ if mod_9:
+ modify_branch(sbox, 'A', 9)
+ else:
+ no_op_commit(sbox) # r9
+
+ if mod_10:
+ modify_branch(sbox, 'B', 10)
+ else:
+ no_op_commit(sbox) # r10
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_3_1(sbox):
+ """merge_to_and_fro_3_1"""
+
+ # A (--o------o---x------x
+ # ( \ / /
+ # B (---o--x---o----------
+ # 2 34 5 67 8 90 1
+
+ init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=[],
+ expect_mi=[8, 9, 10],
+ expect_3ways=[three_way_merge_no_op('B7', 'B10')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_3_2(sbox):
+ """merge_to_and_fro_3_2"""
+
+ # A (--o------o---x--o---x
+ # ( \ / /
+ # B (---o--x---o------o---
+ # 2 34 5 67 8 90 1
+
+ init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True)
+
+ automatic_merge(sbox, 'B', 'A',
+ expect_changes=['B10'],
+ expect_mi=[8, 9, 10],
+ expect_3ways=[three_way_merge('B7', 'B10')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_4_1(sbox):
+ """merge_to_and_fro_4_1"""
+
+ # A (--o------o---x-------
+ # ( \ / \
+ # B (---o--x---o---------x
+ # 2 34 5 67 8 90 1
+
+ init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A6'],
+ expect_mi=[5, 6, 7, 8, 9, 10],
+ expect_3ways=[three_way_merge_no_op('B7', 'A10')])
+
+@SkipUnless(server_has_mergeinfo)
+def merge_to_and_fro_4_2(sbox):
+ """merge_to_and_fro_4_2"""
+
+ # A (--o------o---x--o----
+ # ( \ / \
+ # B (---o--x---o------o--x
+ # 2 34 5 67 8 90 1
+
+ init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A6', 'A9'],
+ expect_mi=[5, 6, 7, 8, 9, 10],
+ expect_3ways=[three_way_merge('B7', 'A10')])
+
+#----------------------------------------------------------------------
+
+# Cherry-pick scenarios
+
+@SkipUnless(server_has_mergeinfo)
+def cherry1_fwd(sbox):
+ """cherry1_fwd"""
+
+ # A (--o------o--[o]----o---
+ # ( \ \ \
+ # B (---o--x---------c-----x
+ # 2 34 5 67 8 9 0 1
+
+ init_mod_merge_mod(sbox, mod_6=True, mod_7=False)
+ modify_branch(sbox, 'A', 8)
+ cherry_pick(sbox, 8, 'A', 'B')
+ modify_branch(sbox, 'A', 10)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A6', 'A10'], # and NOT A8
+ expect_mi=[5, 6, 7, 9, 10],
+ expect_3ways=[three_way_merge('A4', 'A7'),
+ three_way_merge('A8', 'A10')])
+
+@SkipUnless(server_has_mergeinfo)
+@XFail()
+def cherry2_fwd(sbox):
+ """cherry2_fwd"""
+
+ # A (--o-------------c--o---
+ # ( \ / \
+ # B (---o--x---o-[o]-------x
+ # 2 34 5 67 8 9 0 1
+
+ init_mod_merge_mod(sbox, mod_6=False, mod_7=True)
+ modify_branch(sbox, 'B', 8)
+ cherry_pick(sbox, 8, 'B', 'A')
+ modify_branch(sbox, 'A', 10)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A10'], # and NOT A9
+ expect_mi=[5, 6, 7, 8, 9, 10],
+ expect_3ways=[three_way_merge('A9', 'A10')])
+
+@SkipUnless(server_has_mergeinfo)
+@XFail()
+def cherry3_fwd(sbox):
+ """cherry3_fwd"""
+
+ # A (--o--------------c--o----
+ # ( \ / \
+ # ( \ / \
+ # B (---o--o-[o]-x-/---------x
+ # \__/
+ # 2 34 5 6 7 8 9 0
+
+ make_branches(sbox)
+ modify_branch(sbox, 'A', 3)
+ modify_branch(sbox, 'B', 4)
+ modify_branch(sbox, 'B', 5)
+ modify_branch(sbox, 'B', 6)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A3'],
+ expect_mi=[2, 3, 4, 5, 6],
+ expect_3ways=[three_way_merge('A1', 'A6')])
+
+ cherry_pick(sbox, 6, 'B', 'A')
+ modify_branch(sbox, 'A', 9)
+
+ automatic_merge(sbox, 'A', 'B',
+ expect_changes=['A9'], # and NOT A8
+ expect_mi=[7, 8, 9],
+ expect_3ways=[three_way_merge('A8', 'A9')])
+
+#----------------------------------------------------------------------
+# Automatic merges ignore subtree mergeinfo during reintegrate.
+@SkipUnless(server_has_mergeinfo)
+@XFail()
+def subtree_to_and_fro(sbox):
+ "reintegrate considers source subtree mergeinfo"
+
+# A (--o-o-o-o---------x
+# ( \ \ /
+# ( \ \ /
+# B ( o--o------s--
+
+ # Some paths we'll care about.
+ A_COPY_gamma_path = sbox.ospath('A_COPY/D/gamma')
+ psi_path = sbox.ospath('A/D/H/psi')
+ A_COPY_D_path = sbox.ospath('A_COPY/D')
+ A_path = sbox.ospath('A')
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then
+ # make a few edits under A in r3-6:
+ wc_disk, wc_status = set_up_branch(sbox)
+
+ # r7 - Edit a file on the branch.
+ svntest.main.file_write(A_COPY_gamma_path, "Branch edit to 'gamma'.\n")
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
+ '-m', 'Edit a file on our branch')
+
+ # r8 - Do a subtree sync merge from ^/A/D to A_COPY/D.
+ # Note that among other things this changes A_COPY/D/H/psi.
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'merge',
+ sbox.repo_url + '/A/D', A_COPY_D_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
+ '-m', 'Automatic subtree merge')
+
+ # r9 - Make an edit to A/D/H/psi.
+ svntest.main.file_write(psi_path, "Trunk Edit to 'psi'.\n")
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
+ '-m', 'Edit a file on our trunk')
+
+ # Now reintegrate ^/A_COPY back to A. To the automatic merge code the
+ # subtree merge to A_COPY/D just looks like any other branch edit, it is
+ # not considered a merge. So the changes which exist on A/D and were
+ # merged to A_COPY/D, are merged *back* to A, resulting in a conflict:
+ #
+ # C:\SVN\src-trunk\Debug\subversion\tests\cmdline\svn-test-work\
+ # working_copies\merge_automatic_tests-18>svn merge ^^/A_COPY A
+ # DBG: merge.c:11461: base on source: file:///C:/SVN/src-trunk/Debug/
+ # subversion/tests/cmdline/svn-test-work/repositories/
+ # merge_automatic_tests-18/A@1
+ # DBG: merge.c:11462: base on target: file:///C:/SVN/src-trunk/Debug/
+ # subversion/tests/cmdline/svn-test-work/repositories/
+ # merge_automatic_tests-18/A@1
+ # DBG: merge.c:11567: yca file:///C:/SVN/src-trunk/Debug/subversion/
+ # tests/cmdline/svn-test-work/repositories/merge_automatic_tests-18/A@1
+ # DBG: merge.c:11568: base file:///C:/SVN/src-trunk/Debug/subversion/
+ # tests/cmdline/svn-test-work/repositories/merge_automatic_tests-18/A@1
+ # DBG: merge.c:11571: right file:///C:/SVN/src-trunk/Debug/subversion/
+ # tests/cmdline/svn-test-work/repositories/merge_automatic_tests-18/
+ # A_COPY@8
+ # Conflict discovered in file 'A\D\H\psi'.
+ # Select: (p) postpone, (df) diff-full, (e) edit,
+ # (mc) mine-conflict, (tc) theirs-conflict,
+ # (s) show all options: p
+ # --- Merging r2 through r9 into 'A':
+ # C A\D\H\psi
+ # U A\D\gamma
+ # --- Recording mergeinfo for merge of r2 through r9 into 'A':
+ # U A
+ # Summary of conflicts:
+ # Text conflicts: 1
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ exit_code, out, err = svntest.actions.run_and_verify_svn(
+ None, [], svntest.verify.AnyOutput,
+ 'merge', sbox.repo_url + '/A_COPY', A_path)
+
+ # The 'old' merge produced a warning that reintegrate could not be used.
+ # Not claiming this is perfect, but it's better(?) than a conflict:
+ svntest.verify.verify_outputs("Automatic Reintegrate failed, but not "
+ "in the way expected",
+ err, None,
+ "(svn: E195016: Reintegrate can only be used if "
+ "revisions 2 through 9 were previously "
+ "merged from .*/A to the reintegrate source, "
+ "but this is not the case:\n)"
+ "|( A_COPY\n)"
+ "|( Missing ranges: /A:5\n)"
+ "|(\n)"
+ "|(.*apr_err.*)", # In case of debug build
+ None,
+ True) # Match *all* lines of stdout
+
+#----------------------------------------------------------------------
+# Automatic merges ignore subtree mergeinfo gaps older than the last rev
+# synced to the target root.
+@SkipUnless(server_has_mergeinfo)
+def merge_to_reverse_cherry_subtree_to_merge_to(sbox):
+ "sync merge considers target subtree mergeinfo"
+
+ # A (--o-o-o-o------------------
+ # ( \ \ \ \
+ # ( \ \ \ \
+ # B ( o--o------x-------rc-----x
+
+ # Some paths we'll care about.
+ A_COPY_path = sbox.ospath('A_COPY')
+ A_COPY_B_path = sbox.ospath('A_COPY/B')
+ A_COPY_beta_path = sbox.ospath('A_COPY/B/E/beta')
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then
+ # make a few edits under A in r3-6:
+ wc_disk, wc_status = set_up_branch(sbox)
+
+ # Sync merge ^/A to A_COPY, then reverse merge r5 from ^/A/B to A_COPY/B.
+ # This results in mergeinfo on the target which makes it appear that the
+ # branch is synced up to r6, but the subtree mergeinfo on A_COPY/B reveals
+ # that r5 has not been merged to that subtree:
+ #
+ # Properties on 'A_COPY':
+ # svn:mergeinfo
+ # /A:2-6
+ # Properties on 'A_COPY\B':
+ # svn:mergeinfo
+ # /A/B:2-4,6
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'merge',
+ sbox.repo_url + '/A', A_COPY_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'merge', '-c-5',
+ sbox.repo_url + '/A/B',
+ A_COPY_B_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, '-m',
+ 'sync merge and reverse subtree merge')
+
+ # Try an automatic sync merge from ^/A to A_COPY. Revision 5 should be
+ # merged to A_COPY/B as its subtree mergeinfo reveals that rev is missing,
+ # like so:
+ #
+ # >svn merge ^/A A_COPY
+ # --- Merging r5 into 'A_COPY\B':
+ # U A_COPY\B\E\beta
+ # --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY':
+ # U A_COPY
+ # --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY\B':
+ # U A_COPY\B
+ # --- Eliding mergeinfo from 'A_COPY\B':
+ # U A_COPY\B
+ #
+ # But the merge ignores the subtree mergeinfo and considers
+ # only the mergeinfo on the target itself (and thus is a no-op but for
+ # the mergeinfo change on the root of the merge target):
+ #
+ # >svn merge ^/A A_COPY
+ # --- Recording mergeinfo for merge of r7 into 'A_COPY':
+ # U A_COPY
+ #
+ # >svn diff
+ # Index: A_COPY
+ # ===================================================================
+ # --- A_COPY (revision 7)
+ # +++ A_COPY (working copy)
+ #
+ # Property changes on: A_COPY
+ # ___________________________________________________________________
+ # Modified: svn:mergeinfo
+ # Merged /A:r7
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ expected_output = wc.State(A_COPY_path, {
+ 'B/E/beta' : Item(status='U '),
+ })
+ expected_mergeinfo_output = wc.State(A_COPY_path, {
+ '' : Item(status=' U'),
+ 'B' : Item(status=' U'),
+ })
+ expected_elision_output = wc.State(A_COPY_path, {
+ 'B' : Item(status=' U'),
+ })
+ expected_status = wc.State(A_COPY_path, {
+ '' : Item(status=' M'),
+ 'B' : Item(status=' M'),
+ 'mu' : Item(status=' '),
+ 'B/E' : Item(status=' '),
+ 'B/E/alpha' : Item(status=' '),
+ 'B/E/beta' : Item(status='M '),
+ 'B/lambda' : Item(status=' '),
+ 'B/F' : Item(status=' '),
+ 'C' : Item(status=' '),
+ 'D' : Item(status=' '),
+ 'D/G' : Item(status=' '),
+ 'D/G/pi' : Item(status=' '),
+ 'D/G/rho' : Item(status=' '),
+ 'D/G/tau' : Item(status=' '),
+ 'D/gamma' : Item(status=' '),
+ 'D/H' : Item(status=' '),
+ 'D/H/chi' : Item(status=' '),
+ 'D/H/psi' : Item(status=' '),
+ 'D/H/omega' : Item(status=' '),
+ })
+ expected_status.tweak(wc_rev='7')
+ expected_disk = wc.State('', {
+ '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-7'}),
+ 'B' : Item(),
+ 'mu' : Item("This is the file 'mu'.\n"),
+ 'B/E' : Item(),
+ 'B/E/alpha' : Item("This is the file 'alpha'.\n"),
+ 'B/E/beta' : Item("New content"),
+ 'B/lambda' : Item("This is the file 'lambda'.\n"),
+ 'B/F' : Item(),
+ 'C' : Item(),
+ 'D' : Item(),
+ 'D/G' : Item(),
+ 'D/G/pi' : Item("This is the file 'pi'.\n"),
+ 'D/G/rho' : Item("New content"),
+ 'D/G/tau' : Item("This is the file 'tau'.\n"),
+ 'D/gamma' : Item("This is the file 'gamma'.\n"),
+ 'D/H' : Item(),
+ 'D/H/chi' : Item("This is the file 'chi'.\n"),
+ 'D/H/psi' : Item("New content"),
+ 'D/H/omega' : Item("New content"),
+ })
+ expected_skip = wc.State(A_COPY_path, { })
+ svntest.actions.run_and_verify_merge(A_COPY_path, None, None,
+ sbox.repo_url + '/A', None,
+ expected_output,
+ expected_mergeinfo_output,
+ expected_elision_output,
+ expected_disk,
+ expected_status,
+ expected_skip,
+ None, None, None, None,
+ None, 1, 0, A_COPY_path)
+
+########################################################################
+# Run the tests
+
+
+# list all tests here, starting with None:
+test_list = [ None,
+ merge_once_1,
+ merge_once_2,
+ merge_once_3,
+ merge_once_4,
+ merge_twice_same_direction_1,
+ merge_twice_same_direction_2,
+ merge_to_and_fro_1_1,
+ merge_to_and_fro_1_2,
+ merge_to_and_fro_2_1,
+ merge_to_and_fro_2_2,
+ merge_to_and_fro_3_1,
+ merge_to_and_fro_3_2,
+ merge_to_and_fro_4_1,
+ merge_to_and_fro_4_2,
+ cherry1_fwd,
+ cherry2_fwd,
+ cherry3_fwd,
+ subtree_to_and_fro,
+ merge_to_reverse_cherry_subtree_to_merge_to,
+ ]
+
+if __name__ == '__main__':
+ svntest.main.run_tests(test_list)
+ # NOTREACHED
+
+
+### End of file.
diff --git a/subversion/tests/cmdline/merge_tests.py b/subversion/tests/cmdline/merge_tests.py
index f59e3a3..b814c56 100755
--- a/subversion/tests/cmdline/merge_tests.py
+++ b/subversion/tests/cmdline/merge_tests.py
@@ -5799,14 +5799,14 @@
expected_mergeinfo_output = wc.State(A_COPY_D_path, {
'' : Item(status=' G'),
'H' : Item(status=' G'),
- 'H/psi' : Item(status=' G')
+ 'H/psi' : Item(status=' U')
})
expected_elision_output = wc.State(A_COPY_D_path, {
})
expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5,6*'})
expected_disk_D.tweak('H', props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'})
expected_disk_D.tweak('H/psi', contents="New content",
- props={SVN_PROP_MERGEINFO :'/A/D/H/psi:5,8'})
+ props={SVN_PROP_MERGEINFO :'/A/D/H/psi:5'})
expected_status_D.tweak('H/psi', status='MM')
svntest.actions.run_and_verify_merge(A_COPY_D_path, '4', '5',
sbox.repo_url + '/A/D', None,
@@ -5869,7 +5869,7 @@
'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}),
'D/H/chi' : Item("This is the file 'chi'.\n"),
'D/H/psi' : Item("New content",
- props={SVN_PROP_MERGEINFO : '/A/D/H/psi:5,8'}),
+ props={SVN_PROP_MERGEINFO : '/A/D/H/psi:5'}),
'D/H/omega' : Item("New content"),
})
expected_skip = wc.State(A_COPY_path, { })
@@ -17826,6 +17826,60 @@
None, None, None, None,
None, 1, 0)
+#----------------------------------------------------------------------
+@SkipUnless(server_has_mergeinfo)
+def merge_with_externals_with_mergeinfo(sbox):
+ "merge with externals with mergeinfo"
+
+ # Some paths we'll care about.
+ A_path = sbox.ospath('A')
+ A_COPY_path = sbox.ospath('A_COPY')
+ file_external_path = sbox.ospath('A/file-external')
+ mu_COPY_path = sbox.ospath('A_COPY/mu')
+ mu_path = sbox.ospath('A/mu')
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Make a branch of ^/A and then make a few edits under A in r3-6:
+ wc_disk, wc_status = set_up_branch(sbox)
+
+ svntest.main.file_write(mu_COPY_path, "branch edit")
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'file edit on the branch', wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+
+ # Create a file external under 'A' and set some bogus mergeinfo
+ # on it (the fact that this mergeinfo is bogus has no bearing on
+ # this test).
+ svntest.actions.run_and_verify_svn(None, None, [], 'propset',
+ 'svn:externals',
+ '^/iota file-external', A_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'set file external', wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+ svntest.actions.run_and_verify_svn(None, None, [], 'ps', SVN_PROP_MERGEINFO,
+ "/bogus-mergeinfo:5", file_external_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'set mergeinfo on file external',
+ file_external_path)
+
+ # Sync merge ^/A to A_COPY and then reintegrate A_COPY back to A.
+ svntest.actions.run_and_verify_svn(None, None, [], 'merge',
+ sbox.repo_url + '/A', A_COPY_path)
+ svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
+ 'sync merge', wc_dir)
+ # This was segfaulting, see
+ # http://svn.haxx.se/dev/archive-2012-10/0364.shtml
+ svntest.actions.run_and_verify_svn(
+ None,
+ expected_merge_output(None,
+ ['U ' + mu_path + '\n',
+ ' U ' + A_path + '\n'],
+ two_url=True),
+ [], 'merge', '--reintegrate', sbox.repo_url + '/A_COPY',
+ A_path)
+
########################################################################
# Run the tests
@@ -17961,6 +18015,7 @@
reverse_merge_with_rename,
merge_adds_then_deletes_subtree,
merge_with_added_subtrees_with_mergeinfo,
+ merge_with_externals_with_mergeinfo,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/merge_tree_conflict_tests.py b/subversion/tests/cmdline/merge_tree_conflict_tests.py
index e5106ad..d8d2878 100755
--- a/subversion/tests/cmdline/merge_tree_conflict_tests.py
+++ b/subversion/tests/cmdline/merge_tree_conflict_tests.py
@@ -1671,7 +1671,6 @@
#----------------------------------------------------------------------
# ra_serf causes duplicate notifications with this test:
-@XFail(svntest.main.is_ra_type_dav_serf)
@Issue(3802)
def merge_replace_causes_tree_conflict(sbox):
"replace vs. edit tree-conflicts"
diff --git a/subversion/tests/cmdline/mergeinfo_tests.py b/subversion/tests/cmdline/mergeinfo_tests.py
index 9003044..846a019 100755
--- a/subversion/tests/cmdline/mergeinfo_tests.py
+++ b/subversion/tests/cmdline/mergeinfo_tests.py
@@ -72,7 +72,8 @@
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
[],
sbox.repo_url + '/A',
- sbox.repo_url + '/A2')
+ sbox.repo_url + '/A2',
+ "--show-revs=merged")
def mergeinfo(sbox):
"'mergeinfo' on a path with mergeinfo"
@@ -94,7 +95,8 @@
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
['3'],
sbox.repo_url + '/A',
- sbox.ospath('A2'))
+ sbox.ospath('A2'),
+ "--show-revs=merged")
@SkipUnless(server_has_mergeinfo)
def explicit_mergeinfo_source(sbox):
@@ -132,13 +134,17 @@
# Check using each of our recorded merge sources (as paths and URLs).
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
- ['2', '4'], url(B2), path(B))
+ ['2', '4'], url(B2), path(B),
+ "--show-revs=merged")
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
- ['2', '4'], path(B2), path(B))
+ ['2', '4'], path(B2), path(B),
+ "--show-revs=merged")
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
- ['3', '5'], url(B3), path(B))
+ ['3', '5'], url(B3), path(B),
+ "--show-revs=merged")
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
- ['3', '5'], path(B3), path(B))
+ ['3', '5'], path(B3), path(B),
+ "--show-revs=merged")
@SkipUnless(server_has_mergeinfo)
def mergeinfo_non_source(sbox):
@@ -162,7 +168,8 @@
# Check on a source we haven't "merged" from.
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
- [], H2_url, H_path)
+ [], H2_url, H_path,
+ "--show-revs=merged")
#----------------------------------------------------------------------
# Issue #3138
@@ -238,7 +245,8 @@
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
['4','6*'],
sbox.repo_url + '/A',
- A_COPY_path)
+ A_COPY_path,
+ '--show-revs', 'merged')
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
['3','5','6*'],
sbox.repo_url + '/A',
@@ -249,7 +257,8 @@
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
['4'],
sbox.repo_url + '/A/D',
- D_COPY_path)
+ D_COPY_path,
+ '--show-revs', 'merged')
svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
['3','6'],
sbox.repo_url + '/A/D',
diff --git a/subversion/tests/cmdline/patch_tests.py b/subversion/tests/cmdline/patch_tests.py
index 3664eb0..df78203 100755
--- a/subversion/tests/cmdline/patch_tests.py
+++ b/subversion/tests/cmdline/patch_tests.py
@@ -3997,24 +3997,57 @@
patch_file_path = make_patch_path(sbox)
iota_path = sbox.ospath('iota')
+ mu_path = sbox.ospath('A/mu')
iota_contents = [
"This is the file iota."
]
+ mu_contents = [
+ "context\n",
+ "context\n",
+ "context\n",
+ "context\n",
+ "This is the file mu.\n",
+ "context\n",
+ "context\n",
+ "context\n",
+ "context", # no newline at end of file
+ ]
+
svntest.main.file_write(iota_path, ''.join(iota_contents))
+ svntest.main.file_write(mu_path, ''.join(mu_contents))
expected_output = svntest.wc.State(wc_dir, {
'iota' : Item(verb='Sending'),
+ 'A/mu' : Item(verb='Sending'),
})
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.tweak('iota', wc_rev=2)
+ expected_status.tweak('A/mu', wc_rev=2)
svntest.actions.run_and_verify_commit(wc_dir, expected_output,
expected_status, None, wc_dir)
unidiff_patch = [
- "--- iota\t(revision 1)\n",
+ "Index: A/mu\n",
+ "===================================================================\n",
+ "--- A/mu\t(revision 2)\n",
+ "+++ A/mu\t(working copy)\n",
+ "@@ -2,8 +2,8 @@ context\n",
+ " context\n",
+ " context\n",
+ " context\n",
+ "-This is the file mu.\n",
+ "+It is really the file mu.\n",
+ " context\n",
+ " context\n",
+ " context\n",
+ " context\n",
+ "\\ No newline at end of file\n",
+ "Index: iota\n",
+ "===================================================================\n",
+ "--- iota\t(revision 2)\n",
"+++ iota\t(working copy)\n",
- "@@ -1,7 +1,7 @@\n",
- "-This is the file iota.\n"
+ "@@ -1 +1 @@\n",
+ "-This is the file iota.\n",
"\\ No newline at end of file\n",
"+It is really the file 'iota'.\n",
"\\ No newline at end of file\n",
@@ -4025,15 +4058,29 @@
iota_contents = [
"It is really the file 'iota'."
]
+ mu_contents = [
+ "context\n",
+ "context\n",
+ "context\n",
+ "context\n",
+ "It is really the file mu.\n",
+ "context\n",
+ "context\n",
+ "context\n",
+ "context", # no newline at end of file
+ ]
expected_output = [
+ 'U %s\n' % sbox.ospath('A/mu'),
'U %s\n' % sbox.ospath('iota'),
]
expected_disk = svntest.main.greek_state.copy()
expected_disk.tweak('iota', contents=''.join(iota_contents))
+ expected_disk.tweak('A/mu', contents=''.join(mu_contents))
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.tweak('iota', status='M ', wc_rev=2)
+ expected_status.tweak('A/mu', status='M ', wc_rev=2)
expected_skip = wc.State('', { })
@@ -4099,6 +4146,61 @@
1, # check-props
1) # dry-run
+
+def patch_git_with_index_line(sbox):
+ "apply git patch with 'index' line"
+
+ sbox.build(read_only = True)
+ wc_dir = sbox.wc_dir
+ patch_file_path = make_patch_path(sbox)
+
+ unidiff_patch = [
+ "diff --git a/src/tools/ConsoleRunner/hi.txt b/src/tools/ConsoleRunner/hi.txt\n",
+ "new file mode 100644\n",
+ "index 0000000..c82a38f\n",
+ "--- /dev/null\n",
+ "+++ b/src/tools/ConsoleRunner/hi.txt\n",
+ "@@ -0,0 +1 @@\n",
+ "+hihihihihihi\n",
+ "\ No newline at end of file\n",
+ ]
+
+ svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))
+
+ expected_output = [
+ 'A %s\n' % sbox.ospath('src'),
+ 'A %s\n' % sbox.ospath('src/tools'),
+ 'A %s\n' % sbox.ospath('src/tools/ConsoleRunner'),
+ 'A %s\n' % sbox.ospath('src/tools/ConsoleRunner/hi.txt'),
+ ]
+
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.add({
+ 'src/' : Item(status='A ', wc_rev=0),
+ 'src/tools' : Item(status='A ', wc_rev=0),
+ 'src/tools/ConsoleRunner/' : Item(status='A ', wc_rev=0),
+ 'src/tools/ConsoleRunner/hi.txt' : Item(status='A ', wc_rev=0),
+ })
+
+ expected_disk = svntest.main.greek_state.copy()
+ expected_disk.add({'src' : Item(),
+ 'src/tools' : Item(),
+ 'src/tools/ConsoleRunner' : Item(),
+ 'src/tools/ConsoleRunner/hi.txt' :
+ Item(contents="hihihihihihi")
+ })
+
+ expected_skip = wc.State('', { })
+
+ svntest.actions.run_and_verify_patch(wc_dir, os.path.abspath(patch_file_path),
+ expected_output,
+ expected_disk,
+ expected_status,
+ expected_skip,
+ None, # expected err
+ 1, # check-props
+ 1) # dry-run
+
########################################################################
#Run the tests
@@ -4143,6 +4245,7 @@
patch_delete_and_skip,
patch_target_no_eol_at_eof,
patch_add_and_delete,
+ patch_git_with_index_line,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/prop_tests.py b/subversion/tests/cmdline/prop_tests.py
index 1f2015b..8427861 100755
--- a/subversion/tests/cmdline/prop_tests.py
+++ b/subversion/tests/cmdline/prop_tests.py
@@ -33,6 +33,8 @@
import svntest
from svntest.main import SVN_PROP_MERGEINFO
+from svntest.main import SVN_PROP_INHERITABLE_IGNORES
+from svntest import wc
# (abbreviation)
Skip = svntest.testcase.Skip_deco
@@ -2047,7 +2049,7 @@
sbox.build(create_wc=False)
repo_url = sbox.repo_url
- # From this point on, similar to ../libsvn_fs-fs-test.c:revision_props().
+ # From this point on, similar to ../libsvn_fs/fs-test.c:revision_props().
s1 = "violet"
s2 = "wrong value"
@@ -2507,6 +2509,167 @@
svntest.actions.run_and_verify_svn(None, expected_output, [],
'proplist', '-R', wc_dir, '-r', 'BASE')
+def create_inherited_ignores_config(config_dir):
+ "create config stuffs for inherited ignores tests"
+
+ # contents of the file 'config'
+ config_contents = '''\
+[miscellany]
+global-ignores = *.boo *.goo
+'''
+
+ svntest.main.create_config_dir(config_dir, config_contents)
+
+def inheritable_ignores(sbox):
+ "inheritable ignores with svn:ignores and config"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ tmp_dir = os.path.abspath(svntest.main.temp_dir)
+ config_dir = os.path.join(tmp_dir, 'autoprops_config_' + sbox.name)
+ create_inherited_ignores_config(config_dir)
+
+ sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.doo', 'A/B')
+ sbox.simple_propset(SVN_PROP_INHERITABLE_IGNORES, '*.moo', 'A/D')
+ sbox.simple_propset('svn:ignore', '*.foo', 'A/B/E')
+ sbox.simple_commit()
+
+ # Some directories and files that should be added because they don't
+ # match any applicable ignores.
+ X_dir_path = os.path.join(wc_dir, 'ADD-ME-DIR-X')
+ Y_dir_path = os.path.join(wc_dir, 'A', 'ADD-ME-DIR-Y.doo')
+ Z_dir_path = os.path.join(wc_dir, 'A', 'D', 'G', 'ADD-ME-DIR-Z.doo')
+ os.mkdir(X_dir_path)
+ os.mkdir(Y_dir_path)
+ os.mkdir(Z_dir_path)
+
+ # Some directories and files that should be ignored when adding
+ # because they match an ignore pattern (unless of course they are
+ # the direct target of an add, which we always add).
+ boo_dir_path = os.path.join(wc_dir, 'IGNORE-ME-DIR.boo')
+ goo_dir_path = os.path.join(wc_dir, 'IGNORE-ME-DIR.boo', 'IGNORE-ME-DIR.goo')
+ doo_dir_path = os.path.join(wc_dir, 'A', 'B', 'IGNORE-ME-DIR.doo')
+ moo_dir_path = os.path.join(wc_dir, 'A', 'D', 'IGNORE-ME-DIR.moo')
+ foo_dir_path = os.path.join(wc_dir, 'A', 'B', 'E', 'IGNORE-ME-DIR.foo')
+ os.mkdir(boo_dir_path)
+ os.mkdir(goo_dir_path)
+ os.mkdir(doo_dir_path)
+ os.mkdir(moo_dir_path)
+ os.mkdir(foo_dir_path)
+ boo_file_path = sbox.ospath('ADD-ME-DIR-X/ignore-me-file.boo')
+ goo_file_path = sbox.ospath('A/D/G/ignore-me-file.goo')
+ doo_file_path = sbox.ospath('A/B/IGNORE-ME-DIR.doo/ignore-me-file.doo')
+ doo_file2_path = sbox.ospath('A/B/E/ignore-me-file.doo')
+ moo_file_path = sbox.ospath('A/D/ignore-me-file.moo')
+ foo_file_path = sbox.ospath('A/B/E/ignore-me-file.foo')
+ svntest.main.file_write(boo_file_path, 'I should not be versioned!\n')
+ svntest.main.file_write(goo_file_path, 'I should not be versioned!\n')
+ svntest.main.file_write(doo_file_path, 'I should not be versioned!\n')
+ svntest.main.file_write(doo_file2_path, 'I should not be versioned!\n')
+ svntest.main.file_write(moo_file_path, 'I should not be versioned!\n')
+ svntest.main.file_write(foo_file_path, 'I should not be versioned!\n')
+
+ # Some directories and files that don't match any ignore pattern
+ # but are located within a subtree that does match and so shouldn't
+ # be added.
+ roo_file_path = sbox.ospath('A/B/IGNORE-ME-DIR.doo/ignore-me-file.roo')
+ svntest.main.file_write(roo_file_path, 'I should not be versioned!\n')
+
+ # Check (non-verbose) status with the custom config. We should only see
+ # the three unversioned directories which don't match any of the ignore
+ # patterns and aren't proper subtrees of an unversioned or ignored
+ # subtree.
+ expected_output = svntest.verify.UnorderedOutput(
+ ['? ' + X_dir_path + '\n',
+ '? ' + Y_dir_path + '\n',
+ '? ' + Z_dir_path + '\n',])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'st',
+ '--config-dir', config_dir, wc_dir)
+
+ # Check status without the custom config.
+ # Should be the same as above except the *.boo and *.goo paths
+ # now show up as unversioned '?'.
+ expected_output = svntest.verify.UnorderedOutput(
+ ['? ' + X_dir_path + '\n',
+ '? ' + Y_dir_path + '\n',
+ '? ' + Z_dir_path + '\n',
+ '? ' + boo_dir_path + '\n',
+ '? ' + goo_file_path + '\n',])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'st', wc_dir)
+
+ # Check status with the custom config and --no-ignore.
+ expected_output = svntest.verify.UnorderedOutput(
+ ['? ' + X_dir_path + '\n',
+ '? ' + Y_dir_path + '\n',
+ '? ' + Z_dir_path + '\n',
+ 'I ' + boo_dir_path + '\n',
+ 'I ' + doo_dir_path + '\n',
+ 'I ' + doo_file2_path + '\n',
+ 'I ' + moo_dir_path + '\n',
+ 'I ' + foo_dir_path + '\n',
+ 'I ' + goo_file_path + '\n',
+ 'I ' + moo_file_path + '\n',
+ 'I ' + foo_file_path + '\n',])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'st',
+ '--config-dir', config_dir,
+ '--no-ignore', wc_dir)
+
+ # Check status without the custom config and --no-ignore.
+ # Should be the same as above except the *.boo and *.goo paths
+ # are reported as unversioned '?' rather than ignored 'I'.
+ expected_output = svntest.verify.UnorderedOutput(
+ ['? ' + X_dir_path + '\n',
+ '? ' + Y_dir_path + '\n',
+ '? ' + Z_dir_path + '\n',
+ '? ' + boo_dir_path + '\n',
+ 'I ' + doo_dir_path + '\n',
+ 'I ' + doo_file2_path + '\n',
+ 'I ' + moo_dir_path + '\n',
+ 'I ' + foo_dir_path + '\n',
+ '? ' + goo_file_path + '\n',
+ 'I ' + moo_file_path + '\n',
+ 'I ' + foo_file_path + '\n',])
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'st',
+ '--no-ignore', wc_dir)
+
+ # Perform the add with the --force flag, targeting the root of the WC.
+ ### Note: You have to be inside the working copy or else Subversion
+ ### will think you're trying to add the working copy to its parent
+ ### directory, and will (possibly, if the parent directory isn't
+ ### versioned) fail -- see also schedule_tests.py 11 "'svn add'
+ ### should traverse already-versioned dirs"
+ saved_wd = os.getcwd()
+ os.chdir(sbox.wc_dir)
+ expected = svntest.verify.UnorderedOutput(
+ ['A ' + 'ADD-ME-DIR-X\n',
+ 'A ' + os.path.join('A', 'ADD-ME-DIR-Y.doo') + '\n',
+ 'A ' + os.path.join('A', 'D', 'G', 'ADD-ME-DIR-Z.doo') + '\n'])
+ svntest.actions.run_and_verify_svn("Adds in spite of ignores", expected,
+ [], 'add', '.', '--force',
+ '--config-dir', config_dir)
+ os.chdir(saved_wd)
+
+ # Now revert and try the add with the --no-ignore flag, only the
+ # svn:inheritable-ignores should be enforced.
+ svntest.actions.run_and_verify_svn(None, None, [], 'revert', wc_dir, '-R')
+ saved_wd = os.getcwd()
+ os.chdir(sbox.wc_dir)
+ expected = svntest.verify.UnorderedOutput(
+ ['A ' + 'ADD-ME-DIR-X\n',
+ 'A ' + os.path.join('A', 'ADD-ME-DIR-Y.doo') + '\n',
+ 'A ' + os.path.join('A', 'D', 'G', 'ADD-ME-DIR-Z.doo') + '\n',
+ 'A ' + os.path.join('ADD-ME-DIR-X', 'ignore-me-file.boo') + '\n',
+ 'A ' + 'IGNORE-ME-DIR.boo' + '\n',
+ 'A ' + os.path.join('IGNORE-ME-DIR.boo',
+ 'IGNORE-ME-DIR.goo') + '\n',
+ 'A ' + os.path.join('A', 'B', 'E', 'IGNORE-ME-DIR.foo') + '\n',
+ 'A ' + os.path.join('A', 'B', 'E', 'ignore-me-file.foo') + '\n',
+ 'A ' + os.path.join('A', 'D', 'G', 'ignore-me-file.goo') + '\n'])
+ svntest.actions.run_and_verify_svn("Adds in spite of ignores", expected,
+ [], 'add', '.', '--force','--no-ignore',
+ '--config-dir', config_dir)
+
########################################################################
# Run the tests
@@ -2549,6 +2712,7 @@
propget_redirection,
file_matching_dir_prop_reject,
pristine_props_listed,
+ inheritable_ignores,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/resolve_tests.py b/subversion/tests/cmdline/resolve_tests.py
index 5f65b83..5f43672 100755
--- a/subversion/tests/cmdline/resolve_tests.py
+++ b/subversion/tests/cmdline/resolve_tests.py
@@ -25,7 +25,7 @@
######################################################################
# General modules
-import shutil, sys, re, os
+import shutil, sys, re, os, stat
import time
# Our testing module
@@ -106,7 +106,6 @@
# Test for issue #3707 'property conflicts not handled correctly by
# svn resolve'.
@Issue(3707)
-@XFail()
def prop_conflict_resolution(sbox):
"resolving prop conflicts"
@@ -225,8 +224,8 @@
# 2) 'A/mu' - An incoming prop edit on a local prop modification.
# 3) 'A/D/gamma' - An local, non-conflicted prop edit
#
- # This currently fails because svn resolve --accept=[theirs-conflict |
- # theirs-full] removes the conflicts, but doesn't install 'their' version
+ # Previously this failed because svn resolve --accept=[theirs-conflict |
+ # theirs-full] removed the conflicts, but didn't install 'their' version
# of the conflicted properties.
do_prop_conflicting_up_and_resolve('mine-full',
['local_edit\n'],
@@ -244,6 +243,35 @@
[], # Prop deleted
['incoming-conflict\n'])
+@SkipUnless(svntest.main.is_posix_os)
+def auto_resolve_executable_file(sbox):
+ "resolve file with executable bit set"
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Mark iota as executable
+ sbox.simple_propset("svn:executable", '*', 'iota')
+ sbox.simple_commit() # r2
+
+ # Make a change to iota in r3
+ svntest.main.file_write(sbox.ospath('iota'), "boo\n")
+ sbox.simple_commit() # r3
+
+ # Update back to r2, and tweak iota to provoke a text conflict
+ sbox.simple_update(revision=2)
+ svntest.main.file_write(sbox.ospath('iota'), "bzzt\n")
+
+ # Get permission bits of iota
+ mode = os.stat(sbox.ospath('iota'))[stat.ST_MODE]
+
+ # Update back to r3, and auto-resolve the text conflict.
+ svntest.main.run_svn(False, 'update', wc_dir, '--accept', 'theirs-full')
+
+ # permission bits of iota should be unaffected
+ if mode != os.stat(sbox.ospath('iota'))[stat.ST_MODE]:
+ raise svntest.Failure
+
+
########################################################################
# Run the tests
@@ -251,6 +279,7 @@
test_list = [ None,
automatic_conflict_resolution,
prop_conflict_resolution,
+ auto_resolve_executable_file,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/schedule_tests.py b/subversion/tests/cmdline/schedule_tests.py
index 62c47da..d27fbf7 100755
--- a/subversion/tests/cmdline/schedule_tests.py
+++ b/subversion/tests/cmdline/schedule_tests.py
@@ -563,6 +563,7 @@
# Regression test for issue #939:
# Recursive 'svn add' should still traverse already-versioned dirs.
@Issue(939)
+@Issue(4241)
def add_recursive_already_versioned(sbox):
"'svn add' should traverse already-versioned dirs"
@@ -592,8 +593,8 @@
### or else Subversion will think you're trying to add the working copy
### to its parent directory, and will (possibly, if the parent directory
### isn't versioned) fail.
- #svntest.main.run_svn(None, 'add', '--force', wc_dir)
- #svntest.actions.run_and_verify_status(wc_dir, expected_status)
+ svntest.main.run_svn(None, 'add', '--force', wc_dir)
+ svntest.actions.run_and_verify_status(wc_dir, expected_status)
# Now revert, and do the adds again from inside the working copy.
svntest.main.run_svn(None, 'revert', '--recursive', wc_dir)
diff --git a/subversion/tests/cmdline/special_tests.py b/subversion/tests/cmdline/special_tests.py
index 9417c20..489edcb 100755
--- a/subversion/tests/cmdline/special_tests.py
+++ b/subversion/tests/cmdline/special_tests.py
@@ -759,7 +759,6 @@
# (disk and metadata).
@Issue(3884)
@SkipUnless(svntest.main.is_posix_os)
-@XFail()
def merge_foreign_symlink(sbox):
"merge symlink-add from foreign repos"
@@ -790,7 +789,7 @@
# Verify special status.
expected_disk = svntest.main.greek_state.copy()
expected_disk.add({
- 'A/zeta': Item(props={ 'svn:special': '*' })
+ 'A/zeta': Item(contents="link target", props={ 'svn:special': '*' })
})
svntest.actions.verify_disk(sbox.ospath(''), expected_disk, True)
@@ -820,7 +819,7 @@
wc_uuid = svntest.actions.get_wc_uuid(wc_dir)
expected_info = [{
'Path' : re.escape(os.path.join(symlink_path)),
- 'Working Copy Root Path' : re.escape(os.path.abspath(symlink_path)),
+ 'Working Copy Root Path' : re.escape(os.path.abspath(wc_dir)),
'Repository Root' : sbox.repo_url,
'Repository UUID' : wc_uuid,
'Revision' : '1',
@@ -829,7 +828,7 @@
}, {
'Name' : 'iota',
'Path' : re.escape(os.path.join(symlink_path, 'iota')),
- 'Working Copy Root Path' : re.escape(os.path.abspath(symlink_path)),
+ 'Working Copy Root Path' : re.escape(os.path.abspath(wc_dir)),
'Repository Root' : sbox.repo_url,
'Repository UUID' : wc_uuid,
'Revision' : '1',
diff --git a/subversion/tests/cmdline/stat_tests.py b/subversion/tests/cmdline/stat_tests.py
index c224fed..8175331 100755
--- a/subversion/tests/cmdline/stat_tests.py
+++ b/subversion/tests/cmdline/stat_tests.py
@@ -986,25 +986,19 @@
#----------------------------------------------------------------------
-@Issue(2030)
-def status_unversioned_dir(sbox):
- "status on unversioned dir"
- sbox.build(read_only = True, create_wc = False)
- dir = sbox.wc_dir
- svntest.main.safe_rmtree(sbox.wc_dir)
- os.mkdir(dir)
+def status_unversioned_dir_in_wc(sbox):
+ "status on unversioned dir in working copy"
+ sbox.build(read_only = True)
- # Depending on whether you run the tests below a working copy
- # or not, the error message might either be something like
- # svn: warning: W155007: '...copies/stat_tests-19' is not a working copy
- # or
- # svn: warning: W155010: The node '...copies/stat_tests-19' was not found.
+ # Create two unversioned directories within the test working copy
+ path = sbox.ospath('1/2')
+ os.makedirs(path)
- expected_err = "svn: warning: W1550(07|10): .*'.*(/|\\\\)" + \
- os.path.basename(dir) + \
- "' (is not a working copy|was not found)"
+ expected_err = "svn: warning: (W155007|W155010): .*'.*(/|\\\\)" + \
+ os.path.basename(path) + \
+ "' was not found"
svntest.actions.run_and_verify_svn2(None, [], expected_err, 0,
- "status", dir, dir)
+ "status", path)
#----------------------------------------------------------------------
@@ -1883,14 +1877,6 @@
os.chdir(wc_dir)
svntest.actions.run_and_verify_svn(None, [ "? subdir\n" ], [], 'st')
-########################################################################
-# Run the tests
-
-
-def simple_lock(sbox, relpath):
- path = os.path.join(sbox.wc_dir, relpath)
- svntest.actions.run_and_verify_svn(None, None, [], 'lock', path)
-
#----------------------------------------------------------------------
# Regression test for issue #3855 "status doesn't show 'K' on a locked
# deleted node".
@@ -1902,7 +1888,8 @@
iota_path = sbox.ospath('iota')
sbox.simple_rm('iota')
- simple_lock(sbox, 'iota')
+ svntest.actions.run_and_verify_svn(None, None, [], 'lock',
+ os.path.join(sbox.wc_dir, 'iota'))
svntest.actions.run_and_verify_svn(None, ['D K %s\n' % iota_path], [],
'status', iota_path)
@@ -2036,6 +2023,19 @@
sbox.ospath('A/mu'),
sbox.ospath('no-file'))
+# Skip this test is a .svn dir exists in the root directory
+@Skip(lambda: os.path.exists("/%s" % svntest.main.get_admin_name()))
+def status_unversioned_dir(sbox):
+ "status on unversioned dir"
+ sbox.build(read_only = True, create_wc = False)
+
+ # Run svn status on "/", which we assume exists and isn't a WC.
+ # This should work on UNIX-like systems and Windows systems
+ expected_err = "svn: warning: W1550(07|10): .*'.*(/|\\\\)" + \
+ "' is not a working copy"
+ svntest.actions.run_and_verify_svn2(None, [], expected_err, 0,
+ "status", "/")
+
########################################################################
# Run the tests
@@ -2060,7 +2060,7 @@
missing_dir_in_anchor,
status_in_xml,
status_ignored_dir,
- status_unversioned_dir,
+ status_unversioned_dir_in_wc,
status_missing_dir,
status_nonrecursive_update_different_cwd,
status_add_plus_conflict,
@@ -2080,6 +2080,7 @@
wclock_status,
modified_modulo_translation,
status_not_present,
+ status_unversioned_dir,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/svnadmin_tests.py b/subversion/tests/cmdline/svnadmin_tests.py
index e4281c2..ca1751c 100755
--- a/subversion/tests/cmdline/svnadmin_tests.py
+++ b/subversion/tests/cmdline/svnadmin_tests.py
@@ -78,6 +78,12 @@
"source" % src_dirent)
# Compare all files in this directory
for src_file in src_files:
+ # Exclude temporary files
+ if src_file == 'rev-prop-atomicsShm':
+ continue
+ if src_file == 'rev-prop-atomicsMutex':
+ continue
+
src_path = os.path.join(src_dirpath, src_file)
dst_path = os.path.join(dst_dirpath, src_file)
if not os.path.isfile(dst_path):
@@ -1824,7 +1830,8 @@
@Issue(4213)
def recover_old(sbox):
"recover --pre-1.4-compatible"
- sbox.build(create_wc=False)
+ svntest.main.safe_rmtree(sbox.repo_dir, 1)
+ svntest.main.create_repos(sbox.repo_dir, minor_version=0)
svntest.main.run_svnadmin("recover", sbox.repo_dir)
diff --git a/subversion/tests/cmdline/svndumpfilter_tests.py b/subversion/tests/cmdline/svndumpfilter_tests.py
index 63a3d27..459b7b6 100755
--- a/subversion/tests/cmdline/svndumpfilter_tests.py
+++ b/subversion/tests/cmdline/svndumpfilter_tests.py
@@ -679,6 +679,33 @@
+@Issue(4234)
+def dumpfilter_targets_expect_leading_slash_prefixes(sbox):
+ "dumpfilter targets expect leading '/' in prefixes"
+ ## See http://subversion.tigris.org/issues/show_bug.cgi?id=4234. ##
+
+ test_create(sbox)
+
+ dumpfile_location = os.path.join(os.path.dirname(sys.argv[0]),
+ 'svndumpfilter_tests_data',
+ 'greek_tree.dump')
+ dumpfile = open(dumpfile_location).read()
+
+ (fd, targets_file) = tempfile.mkstemp(dir=svntest.main.temp_dir)
+ try:
+ targets = open(targets_file, 'w')
+
+ # Removing the leading slash in path prefixes should work.
+ targets.write('A/D/H\n')
+ targets.write('A/D/G\n')
+ targets.close()
+ _simple_dumpfilter_test(sbox, dumpfile,
+ 'exclude', '/A/B/E', '--targets', targets_file)
+ finally:
+ os.close(fd)
+ os.remove(targets_file)
+
+
########################################################################
# Run the tests
@@ -693,6 +720,7 @@
dropped_but_not_renumbered_empty_revs,
match_empty_prefix,
accepts_deltas,
+ dumpfilter_targets_expect_leading_slash_prefixes,
]
if __name__ == '__main__':
diff --git a/subversion/tests/cmdline/svnlook_tests.py b/subversion/tests/cmdline/svnlook_tests.py
index d9ffba6..202fbce 100755
--- a/subversion/tests/cmdline/svnlook_tests.py
+++ b/subversion/tests/cmdline/svnlook_tests.py
@@ -698,9 +698,14 @@
'bogus_rev_val\n',
' bogus_prop\n',
' svn:log\n', ' svn:author\n',
- # internal property, not really expected
- ' svn:check-locks\n',
- ' bogus_rev_prop\n', ' svn:date\n']
+ ' svn:check-locks\n', # internal prop, not really expected
+ ' bogus_rev_prop\n',
+ ' svn:date\n',
+ ' svn:txn-client-compat-version\n',
+ ]
+ # ra_dav and ra_svn add the user-agent ephemeral property
+ if svntest.main.is_ra_type_dav() or svntest.main.is_ra_type_svn():
+ expected_data.append(' svn:txn-user-agent\n')
verify_logfile(logfilepath, svntest.verify.UnorderedOutput(expected_data))
def property_delete(sbox):
diff --git a/subversion/tests/cmdline/svntest/actions.py b/subversion/tests/cmdline/svntest/actions.py
index b69fed1..b22be1d 100644
--- a/subversion/tests/cmdline/svntest/actions.py
+++ b/subversion/tests/cmdline/svntest/actions.py
@@ -35,7 +35,7 @@
from cStringIO import StringIO
import svntest
-from svntest import main, verify, tree, wc
+from svntest import main, verify, tree, wc, sandbox
from svntest import Failure
logger = logging.getLogger()
@@ -1573,6 +1573,90 @@
pprint.pformat(expected_entries).splitlines(),
pprint.pformat(actual_entries).splitlines())))
+def run_and_verify_inherited_prop_xml(path_or_url,
+ expected_inherited_props,
+ expected_explicit_props,
+ propname=None,
+ peg_rev=None,
+ *args):
+ """If PROPNAME is None, then call run_and_verify_svn with proplist -v --xml
+ --show-inherited-props on PATH_OR_URL, otherwise call run_and_verify_svn
+ with propget PROPNAME --xml --show-inherited-props.
+
+ PATH_OR_URL is pegged at PEG_REV if the latter is not None. If PEG_REV
+ is none, then PATH_OR_URL is pegged at HEAD if a url.
+
+ EXPECTED_INHERITED_PROPS is a (possibly empty) dict mapping working copy
+ paths or URLs to dicts of inherited properties. EXPECTED_EXPLICIT_PROPS is
+ a (possibly empty) dict of the explicit properties expected on PATH_OR_URL.
+
+ Returns on success, raises on failure if EXPECTED_INHERITED_PROPS or
+ EXPECTED_EXPLICIT_PROPS don't match the results of proplist/propget.
+ """
+
+ if peg_rev is None:
+ if sandbox.is_url(path_or_url):
+ path_or_url = path_or_url + '@HEAD'
+ else:
+ path_or_url = path_or_url + '@' + str(peg_rev)
+
+ if (propname):
+ exit_code, output, errput = svntest.actions.run_and_verify_svn(
+ None, None, [], 'propget', propname, '--xml',
+ '--show-inherited-props', path_or_url, *args)
+ else:
+ exit_code, output, errput = svntest.actions.run_and_verify_svn(
+ None, None, [], 'proplist', '-v', '--xml', '--show-inherited-props',
+ path_or_url, *args)
+
+ if len(errput) > 0:
+ raise Failure
+
+ # Props inherited from within the WC are keyed on absolute paths.
+ expected_iprops = {}
+ for x in expected_inherited_props:
+ if sandbox.is_url(x):
+ expected_iprops[x] = expected_inherited_props[x]
+ else:
+ expected_iprops[os.path.abspath(x)] = expected_inherited_props[x]
+
+ actual_iprops = {}
+ actual_explicit_props = {}
+
+ doc = parseString(''.join(output))
+ targets = doc.getElementsByTagName('target')
+ for t in targets:
+
+ # Create actual inherited props.
+ iprops = t.getElementsByTagName('inherited_property')
+
+ if len(iprops) > 0:
+ actual_iprops[t.getAttribute('path')]={}
+
+ for i in iprops:
+ actual_iprops[t.getAttribute('path')][i.getAttribute('name')] = \
+ i.firstChild.nodeValue
+
+ # Create actual explicit props.
+ xprops = t.getElementsByTagName('property')
+
+ for x in xprops:
+ actual_explicit_props[x.getAttribute('name')] = x.firstChild.nodeValue
+
+ if expected_explicit_props != actual_explicit_props:
+ raise svntest.Failure(
+ 'Actual and expected explicit props do not match\n' +
+ '\n'.join(difflib.ndiff(
+ pprint.pformat(expected_explicit_props).splitlines(),
+ pprint.pformat(actual_explicit_props).splitlines())))
+
+ if expected_iprops != actual_iprops:
+ raise svntest.Failure(
+ 'Actual and expected inherited props do not match\n' +
+ '\n'.join(difflib.ndiff(
+ pprint.pformat(expected_iprops).splitlines(),
+ pprint.pformat(actual_iprops).splitlines())))
+
def run_and_verify_diff_summarize_xml(error_re_string = [],
expected_prefix = None,
expected_paths = [],
diff --git a/subversion/tests/cmdline/svntest/main.py b/subversion/tests/cmdline/svntest/main.py
index f0a8ea1..bae35f9 100644
--- a/subversion/tests/cmdline/svntest/main.py
+++ b/subversion/tests/cmdline/svntest/main.py
@@ -174,6 +174,12 @@
# Constant for the merge info property.
SVN_PROP_MERGEINFO = "svn:mergeinfo"
+# Constant for the inheritabled auto-props property.
+SVN_PROP_INHERITABLE_AUTOPROPS = "svn:inheritable-auto-props"
+
+# Constant for the inheritabled ignores property.
+SVN_PROP_INHERITABLE_IGNORES = "svn:inheritable-ignores"
+
# Where we want all the repositories and working copies to live.
# Each test will have its own!
general_repo_dir = os.path.join(work_dir, "repositories")
@@ -952,12 +958,16 @@
dump_re = re.compile(r'^\* Dumped revision (\d+)\.\r?$')
expect_revision = 0
+ dump_failed = False
for dump_line in dump_stderr:
match = dump_re.match(dump_line)
if not match or match.group(1) != str(expect_revision):
logger.warn('ERROR: dump failed: %s', dump_line.strip())
- raise SVNRepositoryCopyFailure
- expect_revision += 1
+ dump_failed = True
+ else:
+ expect_revision += 1
+ if dump_failed:
+ raise SVNRepositoryCopyFailure
if expect_revision != head_revision + 1:
logger.warn('ERROR: dump failed; did not see revision %s', head_revision)
raise SVNRepositoryCopyFailure
@@ -1229,10 +1239,12 @@
"""A thread that runs test cases in their own processes.
Receives test numbers to run from the queue, and saves results into
the results field."""
- def __init__(self, queue):
+ def __init__(self, queue, progress_func, tests_total):
threading.Thread.__init__(self)
self.queue = queue
self.results = []
+ self.progress_func = progress_func
+ self.tests_total = tests_total
def run(self):
while True:
@@ -1243,6 +1255,11 @@
self.run_one(next_index)
+ # signal progress
+ if self.progress_func:
+ self.progress_func(self.tests_total - self.queue.qsize(),
+ self.tests_total)
+
def run_one(self, index):
command = os.path.abspath(sys.argv[0])
@@ -1292,7 +1309,8 @@
def list(self, milestones_dict=None):
"""Print test doc strings. MILESTONES_DICT is an optional mapping
- of issue numbers to target milestones."""
+ of issue numbers to an list containing target milestones and who
+ the issue is assigned to."""
if options.mode_filter.upper() == 'ALL' \
or options.mode_filter.upper() == self.pred.list_mode().upper() \
or (options.mode_filter.upper() == 'PASS' \
@@ -1302,6 +1320,7 @@
if self.pred.issues:
if not options.milestone_filter or milestones_dict is None:
issues = self.pred.issues
+ tail += " [%s]" % ','.join(['#%s' % str(i) for i in issues])
else: # Limit listing by requested target milestone(s).
filter_issues = []
matches_filter = False
@@ -1310,13 +1329,16 @@
# If any one of them matches the MILESTONE_FILTER then we'll print
# them all.
for issue in self.pred.issues:
- # A safe starting assumption.
+ # Some safe starting assumptions.
milestone = 'unknown'
+ assigned_to = 'unknown'
if milestones_dict:
if milestones_dict.has_key(str(issue)):
- milestone = milestones_dict[str(issue)]
+ milestone = milestones_dict[str(issue)][0]
+ assigned_to = milestones_dict[str(issue)][1]
- filter_issues.append(str(issue) + '(' + milestone + ')')
+ filter_issues.append(
+ str(issue) + '(' + milestone + '/' + assigned_to + ')')
pattern = re.compile(options.milestone_filter)
if pattern.match(milestone):
matches_filter = True
@@ -1324,9 +1346,12 @@
# Did at least one of the associated issues meet our filter?
if matches_filter:
issues = filter_issues
-
- tail += " [%s]" % ','.join(['#%s' % str(i) for i in issues])
-
+ # Wrap the issue#/target-milestone/assigned-to string
+ # to the next line and add a line break to enhance
+ # readability.
+ tail += "\n %s" % '\n '.join(
+ ['#%s' % str(i) for i in issues])
+ tail += '\n'
# If there is no filter or this test made if through
# the filter then print it!
if options.milestone_filter is None or len(issues):
@@ -1499,7 +1524,8 @@
for num in testnums:
number_queue.put(num)
- threads = [ TestSpawningThread(number_queue) for i in range(parallel) ]
+ threads = [ TestSpawningThread(number_queue, progress_func,
+ len(testnums)) for i in range(parallel) ]
for t in threads:
t.start()
@@ -1512,10 +1538,6 @@
results += t.results
results.sort()
- # signal some kind of progress
- if progress_func:
- progress_func(len(testnums), len(testnums))
-
# terminate the line of dots
print("")
@@ -1666,7 +1688,12 @@
sys.exit(execute_tests(test_list, serial_only))
-def get_target_milestones_for_issues(issue_numbers):
+def get_issue_details(issue_numbers):
+ """For each issue number in ISSUE_NUMBERS query the issue
+ tracker and determine what the target milestone is and
+ who the issue is assigned to. Return this information
+ as a dictionary mapping issue numbers to a list
+ [target_milestone, assigned_to]"""
xml_url = "http://subversion.tigris.org/issues/xml.cgi?id="
issue_dict = {}
@@ -1694,14 +1721,17 @@
xmldoc = xml.dom.minidom.parse(issue_xml_f)
issue_xml_f.close()
- # Get the target milestone for each issue.
+ # For each issue: Get the target milestone and who
+ # the issue is assigned to.
issue_element = xmldoc.getElementsByTagName('issue')
for i in issue_element:
issue_id_element = i.getElementsByTagName('issue_id')
issue_id = issue_id_element[0].childNodes[0].nodeValue
milestone_element = i.getElementsByTagName('target_milestone')
milestone = milestone_element[0].childNodes[0].nodeValue
- issue_dict[issue_id] = milestone
+ assignment_element = i.getElementsByTagName('assigned_to')
+ assignment = assignment_element[0].childNodes[0].nodeValue
+ issue_dict[issue_id] = [milestone, assignment]
except:
print "ERROR: Unable to parse target milestones from issue tracker"
raise
@@ -1894,10 +1924,13 @@
options.mode_filter.upper() == test_mode or
(options.mode_filter.upper() == 'PASS' and test_mode == '')):
issues_dict[issue]=issue
- milestones_dict = get_target_milestones_for_issues(issues_dict.keys())
+ milestones_dict = get_issue_details(issues_dict.keys())
- header = "Test # Mode Test Description\n" \
- "------ ----- ----------------"
+ header = "Test # Mode Test Description\n"
+ if options.milestone_filter:
+ header += " Issue#(Target Mileston/Assigned To)\n"
+ header += "------ ----- ----------------"
+
printed_header = False
for testnum in testnums:
test_mode = TestRunner(test_list[testnum], testnum).get_mode().upper()
diff --git a/subversion/tests/cmdline/tree_conflict_tests.py b/subversion/tests/cmdline/tree_conflict_tests.py
index 9a369ab..0112d12 100755
--- a/subversion/tests/cmdline/tree_conflict_tests.py
+++ b/subversion/tests/cmdline/tree_conflict_tests.py
@@ -652,7 +652,7 @@
"merge file: modify onto not-file"
sbox2 = sbox.clone_dependent()
test_tc_merge(sbox, f_mods, br_scen = f_dels + f_moves + f_rpl_d)
- test_tc_merge(sbox2, f_mods, wc_scen = f_dels)
+ test_tc_merge(sbox2, f_mods, wc_scen = f_dels + f_moves)
# Note: See UC4 in notes/tree-conflicts/use-cases.txt.
def merge_file_del_onto_not_same(sbox):
diff --git a/subversion/tests/cmdline/update_tests.py b/subversion/tests/cmdline/update_tests.py
index c0bd917..024c2c7 100755
--- a/subversion/tests/cmdline/update_tests.py
+++ b/subversion/tests/cmdline/update_tests.py
@@ -5514,9 +5514,12 @@
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
+ # Attempt the update, expecting an error. (Sometimes the error
+ # strings says "No such revision", sometimes "No such target
+ # revision".)
svntest.actions.run_and_verify_update(wc_dir,
None, None, None,
- ".*No such revision",
+ "E160006.*No such.*revision",
None, None,
None, None, None, wc_dir, '-r', '2')
diff --git a/subversion/tests/cmdline/upgrade_tests.py b/subversion/tests/cmdline/upgrade_tests.py
index be3f42e..7885e1d 100755
--- a/subversion/tests/cmdline/upgrade_tests.py
+++ b/subversion/tests/cmdline/upgrade_tests.py
@@ -50,7 +50,7 @@
Issue = svntest.testcase.Issue_deco
Wimp = svntest.testcase.Wimp_deco
-wc_is_too_old_regex = (".*Working copy '.*' is too old \(format \d+.*\).*")
+wc_is_too_old_regex = (".*is too old \(format \d+.*\).*")
def get_current_format():
@@ -258,28 +258,29 @@
replace_sbox_with_tarfile(sbox, 'basic_upgrade.tar.bz2')
# Attempt to use the working copy, this should give an error
- expected_stderr = wc_is_too_old_regex
- svntest.actions.run_and_verify_svn(None, None, expected_stderr,
+ svntest.actions.run_and_verify_svn(None, None, wc_is_too_old_regex,
'info', sbox.wc_dir)
-
- # Upgrade on something not a versioned dir gives a 'not directory' error.
- not_dir = ".*E155019.*%s'.*directory"
+ # Upgrade on something anywhere within a versioned subdir gives a
+ # 'not a working copy root' error. Upgrade on something without any
+ # versioned parent gives a 'not a working copy' error.
+ # Both cases use the same error code.
+ not_wc = ".*(E155007|E155019).*%s'.*not a working copy.*"
os.mkdir(sbox.ospath('X'))
- svntest.actions.run_and_verify_svn(None, None, not_dir % 'X',
+ svntest.actions.run_and_verify_svn(None, None, not_wc % 'X',
'upgrade', sbox.ospath('X'))
- svntest.actions.run_and_verify_svn(None, None, not_dir % 'Y',
+ # Upgrade on a non-existent subdir within an old WC gives a
+ # 'not a working copy' error.
+ svntest.actions.run_and_verify_svn(None, None, not_wc % 'Y',
'upgrade', sbox.ospath('Y'))
-
- svntest.actions.run_and_verify_svn(None, None, not_dir %
- re.escape(sbox.ospath('A/mu')),
+ # Upgrade on a versioned file within an old WC gives a
+ # 'not a working copy' error.
+ svntest.actions.run_and_verify_svn(None, None, not_wc % 'mu',
'upgrade', sbox.ospath('A/mu'))
-
- # Upgrade on a versioned subdir gives a 'not root' error.
- not_root = ".*E155019.*%s'.*root.*%s'"
- svntest.actions.run_and_verify_svn(None, None, not_root %
- ('A', re.escape(sbox.wc_dir)),
+ # Upgrade on a versioned dir within an old WC gives a
+ # 'not a working copy' error.
+ svntest.actions.run_and_verify_svn(None, None, not_wc % 'A',
'upgrade', sbox.ospath('A'))
# Now upgrade the working copy
@@ -794,10 +795,9 @@
no_actual_node(sbox, 'A/D/G/tau')
# While the upgrade from f20 to f21 will work the upgrade from f22
- # to f23 will not, since working nodes are present, so the
- # auto-upgrade will fail. If this happens we cannot use the
- # Subversion libraries to query the working copy.
- exit_code, output, errput = svntest.main.run_svn('format 22', 'st', wc_dir)
+ # to f23 will not, since working nodes are present.
+ exit_code, output, errput = svntest.main.run_svn('format 22', 'upgrade',
+ wc_dir)
if not exit_code:
run_and_verify_status_no_server(wc_dir, expected_status)
@@ -984,8 +984,8 @@
assert os.path.exists(old_pristine_path)
assert not os.path.exists(new_pristine_path)
- # Touch the WC to auto-upgrade it
- svntest.actions.run_and_verify_svn(None, None, [], 'info', sbox.wc_dir)
+ # Upgrade the WC
+ svntest.actions.run_and_verify_svn(None, None, [], 'upgrade', sbox.wc_dir)
assert not os.path.exists(old_pristine_path)
assert os.path.exists(new_pristine_path)
diff --git a/subversion/tests/libsvn_fs/fs-test.c b/subversion/tests/libsvn_fs/fs-test.c
index 88a4fc5..2043ca8 100644
--- a/subversion/tests/libsvn_fs/fs-test.c
+++ b/subversion/tests/libsvn_fs/fs-test.c
@@ -4176,12 +4176,12 @@
int related = 0;
/* Get the ID for the first path/revision combination. */
- SVN_ERR(svn_fs_revision_root(&rev_root, fs, pr1.rev, pool));
- SVN_ERR(svn_fs_node_id(&id1, rev_root, pr1.path, pool));
+ SVN_ERR(svn_fs_revision_root(&rev_root, fs, pr1.rev, subpool));
+ SVN_ERR(svn_fs_node_id(&id1, rev_root, pr1.path, subpool));
/* Get the ID for the second path/revision combination. */
- SVN_ERR(svn_fs_revision_root(&rev_root, fs, pr2.rev, pool));
- SVN_ERR(svn_fs_node_id(&id2, rev_root, pr2.path, pool));
+ SVN_ERR(svn_fs_revision_root(&rev_root, fs, pr2.rev, subpool));
+ SVN_ERR(svn_fs_node_id(&id2, rev_root, pr2.path, subpool));
/* <exciting> Now, run the relationship check! </exciting> */
related = svn_fs_check_related(id1, id2) ? 1 : 0;
diff --git a/subversion/tests/libsvn_repos/repos-test.c b/subversion/tests/libsvn_repos/repos-test.c
index 1f4d71c..f9d2c82 100644
--- a/subversion/tests/libsvn_repos/repos-test.c
+++ b/subversion/tests/libsvn_repos/repos-test.c
@@ -1077,9 +1077,10 @@
SVN_ERR(create_rmlocks_editor(&editor, &edit_baton, &removed, subpool));
/* Report what we have. */
- SVN_ERR(svn_repos_begin_report2(&report_baton, 1, repos, "/", "", NULL,
+ SVN_ERR(svn_repos_begin_report3(&report_baton, 1, repos, "/", "", NULL,
FALSE, svn_depth_infinity, FALSE, FALSE,
- editor, edit_baton, NULL, NULL, subpool));
+ editor, edit_baton, NULL, NULL, 1024,
+ subpool));
SVN_ERR(svn_repos_set_path3(report_baton, "", 1,
svn_depth_infinity,
FALSE, NULL, subpool));
@@ -2065,9 +2066,10 @@
SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs,
txn_root, "", subpool));
- SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL,
+ SVN_ERR(svn_repos_begin_report3(&report_baton, 2, repos, "/", "", NULL,
TRUE, svn_depth_infinity, FALSE, FALSE,
- editor, edit_baton, NULL, NULL, subpool));
+ editor, edit_baton, NULL, NULL, 16,
+ subpool));
SVN_ERR(svn_repos_set_path3(report_baton, "", 1,
svn_depth_infinity,
FALSE, NULL, subpool));
@@ -2122,9 +2124,10 @@
SVN_ERR(dir_delta_get_editor(&editor, &edit_baton, fs,
txn_root, "", subpool));
- SVN_ERR(svn_repos_begin_report2(&report_baton, 2, repos, "/", "", NULL,
+ SVN_ERR(svn_repos_begin_report3(&report_baton, 2, repos, "/", "", NULL,
TRUE, svn_depth_infinity, FALSE, FALSE,
- editor, edit_baton, NULL, NULL, subpool));
+ editor, edit_baton, NULL, NULL, 20,
+ subpool));
SVN_ERR(svn_repos_set_path3(report_baton, "", 1,
svn_depth_infinity,
FALSE, NULL, subpool));
diff --git a/subversion/tests/libsvn_subr/auth-test.c b/subversion/tests/libsvn_subr/auth-test.c
index 09c6356..ac8e6d2 100644
--- a/subversion/tests/libsvn_subr/auth-test.c
+++ b/subversion/tests/libsvn_subr/auth-test.c
@@ -143,8 +143,8 @@
/* Test GNOME Keyring auth providers */
#ifdef SVN_HAVE_GNOME_KEYRING
- svn_auth_get_platform_specific_provider(&provider, "gnome_keyring",
- "simple", pool);
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider, "gnome_keyring",
+ "simple", pool));
if (!provider)
return svn_error_createf
@@ -152,8 +152,8 @@
"svn_auth_get_platform_specific_provider('gnome_keyring', 'simple') "
"should not return NULL");
- svn_auth_get_platform_specific_provider(&provider, "gnome_keyring",
- "ssl_client_cert_pw", pool);
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider, "gnome_keyring",
+ "ssl_client_cert_pw", pool));
if (!provider)
return svn_error_createf
@@ -162,8 +162,8 @@
"'ssl_client_cert_pw') should not return NULL");
/* Make sure you do not get a Windows auth provider */
- svn_auth_get_platform_specific_provider(&provider, "windows",
- "simple", pool);
+ SVN_ERR(svn_auth_get_platform_specific_provider(&provider, "windows",
+ "simple", pool));
if (provider)
return svn_error_createf
diff --git a/subversion/tests/libsvn_subr/cache-test.c b/subversion/tests/libsvn_subr/cache-test.c
index 9ed5916..567d6a7 100644
--- a/subversion/tests/libsvn_subr/cache-test.c
+++ b/subversion/tests/libsvn_subr/cache-test.c
@@ -183,8 +183,8 @@
svn_cache__t *cache;
svn_membuffer_t *membuffer;
- SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1,
- TRUE, pool));
+ SVN_ERR(svn_cache__membuffer_cache_create(&membuffer, 10*1024, 1, 0,
+ TRUE, TRUE, pool));
/* Create a cache with just one entry. */
SVN_ERR(svn_cache__create_membuffer_cache(&cache,
diff --git a/subversion/tests/libsvn_subr/checksum-test.c b/subversion/tests/libsvn_subr/checksum-test.c
index c98fa60..011d496 100644
--- a/subversion/tests/libsvn_subr/checksum-test.c
+++ b/subversion/tests/libsvn_subr/checksum-test.c
@@ -24,6 +24,7 @@
#include <apr_pools.h>
#include "svn_error.h"
+#include "private/svn_pseudo_md5.h"
#include "../svn_test.h"
@@ -80,6 +81,38 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+test_pseudo_md5(apr_pool_t *pool)
+{
+ apr_uint32_t input[16] = { 0 };
+ apr_uint32_t digest_15[4] = { 0 };
+ apr_uint32_t digest_31[4] = { 0 };
+ apr_uint32_t digest_63[4] = { 0 };
+ svn_checksum_t *checksum;
+
+ /* input is all 0s but the hash shall be different
+ (due to different input sizes)*/
+ svn__pseudo_md5_15(digest_15, input);
+ svn__pseudo_md5_31(digest_31, input);
+ svn__pseudo_md5_63(digest_63, input);
+
+ SVN_TEST_ASSERT(memcmp(digest_15, digest_31, sizeof(digest_15)));
+ SVN_TEST_ASSERT(memcmp(digest_15, digest_63, sizeof(digest_15)));
+ SVN_TEST_ASSERT(memcmp(digest_31, digest_63, sizeof(digest_15)));
+
+ /* the checksums shall also be different from "proper" MD5 */
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, input, 15, pool));
+ SVN_TEST_ASSERT(memcmp(digest_15, checksum->digest, sizeof(digest_15)));
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, input, 31, pool));
+ SVN_TEST_ASSERT(memcmp(digest_31, checksum->digest, sizeof(digest_15)));
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5, input, 63, pool));
+ SVN_TEST_ASSERT(memcmp(digest_63, checksum->digest, sizeof(digest_15)));
+
+ return SVN_NO_ERROR;
+}
+
/* An array of all test functions */
struct svn_test_descriptor_t test_funcs[] =
{
@@ -88,5 +121,7 @@
"checksum parse"),
SVN_TEST_PASS2(test_checksum_empty,
"checksum emptiness"),
+ SVN_TEST_PASS2(test_pseudo_md5,
+ "pseudo-md5 compatibility"),
SVN_TEST_NULL
};
diff --git a/subversion/tests/libsvn_subr/compat-test.c b/subversion/tests/libsvn_subr/compat-test.c
index 5ce3f08..a18e84d 100644
--- a/subversion/tests/libsvn_subr/compat-test.c
+++ b/subversion/tests/libsvn_subr/compat-test.c
@@ -24,10 +24,12 @@
#include <apr_pools.h>
#include "svn_error.h"
+#include "svn_pools.h"
#include "svn_version.h"
#include "../svn_test.h"
#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
#ifndef SVN_DISABLE_FULL_VERSION_MATCH
#define FALSE_IF_FULL FALSE
@@ -100,11 +102,121 @@
return SVN_NO_ERROR;
}
+
+static svn_error_t *
+test_version_parsing(apr_pool_t *pool)
+{
+ unsigned int i;
+ apr_pool_t *iterpool;
+
+ struct version_pair {
+ const char *str;
+ svn_boolean_t malformed;
+ svn_version_t version;
+ } versions[] = {
+ /* str malformed version */
+ { "1.8", FALSE, { 1, 8, 0, ""} },
+ { "1.8-dev", TRUE, { 0, 0, 0, ""} },
+ { "1.1.0", FALSE, { 1, 1, 0, ""} },
+ { "1.1.3", FALSE, { 1, 1, 3, ""} },
+ { "2.10.0", FALSE, { 2, 10, 0, ""} },
+ { "1.8.0-dev", FALSE, { 1, 8, 0, "dev"} },
+ { "1.7.0-beta1", FALSE, { 1, 7, 0, "beta1"} },
+ { "1a.8.0", TRUE, { 0, 0, 0, ""} },
+ { "1a.8.0", TRUE, { 0, 0, 0, ""} },
+ { "1.a8.0", TRUE, { 0, 0, 0, ""} },
+ { "1.8.0a", TRUE, { 0, 0, 0, ""} },
+ { "1.8.0.1", TRUE, { 0, 0, 0, ""} },
+ };
+
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < sizeof(versions)/sizeof(versions[0]); ++i)
+ {
+ svn_version_t *version;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ err = svn_version__parse_version_string(&version, versions[i].str,
+ iterpool);
+ if (err && (err->apr_err != SVN_ERR_MALFORMED_VERSION_STRING))
+ return svn_error_create(SVN_ERR_TEST_FAILED, err,
+ "Unexpected error code");
+ if (err)
+ {
+ if (! versions[i].malformed)
+ return svn_error_create(SVN_ERR_TEST_FAILED, err,
+ "Unexpected parsing error returned");
+ else
+ svn_error_clear(err);
+ }
+ else
+ {
+ if (versions[i].malformed)
+ return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+ "Parsing error expected; none returned");
+ if (! svn_ver_equal(version, &(versions[i].version)))
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
+ "Parsed version of '%s' doesn't match "
+ "expected", versions[i].str);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_version_at_least(apr_pool_t *pool)
+{
+ unsigned int i;
+
+ struct version_pair {
+ svn_version_t version;
+ int major;
+ int minor;
+ int patch;
+ svn_boolean_t at_least;
+ } versions[] = {
+ /* maj min pat version at_least */
+ { { 1, 3, 3, ""}, 1, 3, 3, TRUE },
+ { { 1, 3, 3, ""}, 1, 3, 4, FALSE },
+ { { 1, 3, 3, ""}, 1, 4, 3, FALSE },
+ { { 1, 3, 3, ""}, 0, 4, 3, TRUE },
+ { { 1, 3, 3, ""}, 2, 0, 0, FALSE },
+ { { 1, 3, 3, ""}, 1, 3, 2, TRUE },
+ { { 1, 3, 3, ""}, 1, 2, 4, TRUE },
+ { { 1, 3, 3, "dev"}, 1, 3, 2, TRUE },
+ { { 1, 3, 3, "dev"}, 1, 3, 3, FALSE },
+ { { 1, 3, 3, ""}, 0, 4, 3, TRUE },
+ };
+
+ for (i = 0; i < sizeof(versions)/sizeof(versions[0]); ++i)
+ {
+ svn_boolean_t at_least = svn_version__at_least(&(versions[i].version),
+ versions[i].major,
+ versions[i].minor,
+ versions[i].patch);
+ if (at_least && (! versions[i].at_least))
+ return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+ "Expected at-least to be FALSE; got TRUE");
+ if ((! at_least) && versions[i].at_least)
+ return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+ "Expected at-least to be TRUE; got FALSE");
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* An array of all test functions */
struct svn_test_descriptor_t test_funcs[] =
{
SVN_TEST_NULL,
SVN_TEST_PASS2(test_version_compatibility,
"svn_ver_compatible"),
+ SVN_TEST_PASS2(test_version_parsing,
+ "svn_version__parse_version_string"),
+ SVN_TEST_PASS2(test_version_at_least,
+ "svn_version__at_least"),
SVN_TEST_NULL
};
diff --git a/subversion/tests/libsvn_subr/dirent_uri-test.c b/subversion/tests/libsvn_subr/dirent_uri-test.c
index e6b75f1..2ec1ca1 100644
--- a/subversion/tests/libsvn_subr/dirent_uri-test.c
+++ b/subversion/tests/libsvn_subr/dirent_uri-test.c
@@ -1364,6 +1364,9 @@
{ "foo.", "foo./.bar", ".bar" },
{ "X:foo", "X:bar", NULL },
{ "../foo", "..", NULL },
+ { "/foo/bar/zig", "/foo", NULL },
+ { "/foo/bar/zig", "/foo/ba", NULL },
+ { "/foo/bar/zig", "/foo/bar/zi", NULL },
#ifdef SVN_USE_DOS_PATHS
{ "", "C:", NULL },
{ "", "C:foo", NULL },
@@ -1384,6 +1387,9 @@
{ "X:/foo", "X:/", NULL },
{ "A:/foo", "A:/foo/bar", "bar" },
{ "A:/foo", "A:/foot", NULL },
+ { "A:/foo/bar/zig", "A:/foo", NULL },
+ { "A:/foo/bar/zig", "A:/foo/ba", NULL },
+ { "A:/foo/bar/zig", "A:/foo/bar/zi", NULL },
{ "//srv", "//srv/share", NULL },
{ "//srv", "//srv/shr/fld", NULL },
{ "//srv/shr", "//srv", NULL },
@@ -1393,6 +1399,7 @@
{ "//srv/s r", "//srv/s r/fld", "fld" },
{ "//srv/shr/fld", "//srv/shr", NULL },
{ "//srv/shr/fld", "//srv2/shr/fld", NULL },
+ { "//srv/shr/fld", "//srv/shr/f", NULL },
{ "/", "//srv/share", NULL },
#else /* !SVN_USE_DOS_PATHS */
{ "", "C:", "C:" },
@@ -1458,6 +1465,8 @@
{ "http://", "http://test", NULL },
{ "http://server", "http://server/q", "q" },
{ "svn://server", "http://server/q", NULL },
+ { "http://foo/bar", "http://foo", NULL },
+ { "http://foo/bar", "http://foo/ba", NULL },
};
static svn_error_t *
diff --git a/subversion/tests/libsvn_subr/named_atomic-test-proc.c b/subversion/tests/libsvn_subr/named_atomic-test-proc.c
index 768f92f..534247c 100644
--- a/subversion/tests/libsvn_subr/named_atomic-test-proc.c
+++ b/subversion/tests/libsvn_subr/named_atomic-test-proc.c
@@ -84,4 +84,3 @@
return got_error;
}
-
diff --git a/subversion/tests/libsvn_subr/named_atomic-test.c b/subversion/tests/libsvn_subr/named_atomic-test.c
index 0cd2c9a..b83ec2a 100644
--- a/subversion/tests/libsvn_subr/named_atomic-test.c
+++ b/subversion/tests/libsvn_subr/named_atomic-test.c
@@ -112,6 +112,20 @@
return result == svn_tristate_true;
}
+/* Remove temporary files from disk.
+ */
+static apr_status_t
+cleanup_test_shm(void *arg)
+{
+ apr_pool_t *pool = arg;
+
+ svn_error_clear(svn_atomic_namespace__cleanup(name_namespace, pool));
+ svn_error_clear(svn_atomic_namespace__cleanup(name_namespace1, pool));
+ svn_error_clear(svn_atomic_namespace__cleanup(name_namespace2, pool));
+
+ return 0;
+}
+
/* Bring shared memory to a defined state. This is very useful in case of
* lingering problems from previous tests or test runs.
*/
@@ -150,6 +164,11 @@
return svn_error_wrap_apr(SVN_ERR_TEST_SKIPPED,
"user has insufficient privileges");
+ /* destroy temp files after usage */
+
+ apr_pool_cleanup_register(pool, pool,
+ cleanup_test_shm, apr_pool_cleanup_null);
+
/* get the two I/O atomics for this thread */
SVN_ERR(svn_atomic_namespace__create(&ns, name_namespace, scratch));
SVN_ERR(svn_named_atomic__get(&atomic, ns, ATOMIC_NAME, TRUE));
diff --git a/subversion/tests/libsvn_subr/string-test.c b/subversion/tests/libsvn_subr/string-test.c
index 7aa760b..35a0cff 100644
--- a/subversion/tests/libsvn_subr/string-test.c
+++ b/subversion/tests/libsvn_subr/string-test.c
@@ -540,6 +540,78 @@
return test_stringbuf_unequal("abc", "abb", pool);
}
+static svn_error_t *
+expect_stringbuf_equal(const svn_stringbuf_t* str1,
+ const char* str2,
+ apr_pool_t *pool)
+{
+ if (svn_stringbuf_compare(str1, svn_stringbuf_create(str2, pool)))
+ return SVN_NO_ERROR;
+ else
+ return fail(pool, "test failed");
+}
+
+static svn_error_t *
+test_stringbuf_insert(apr_pool_t *pool)
+{
+ a = svn_stringbuf_create("st , ", pool);
+
+ svn_stringbuf_insert(a, 0, "teflon", 2);
+ SVN_TEST_STRING_ASSERT(a->data, "test , ");
+
+ svn_stringbuf_insert(a, 5, "hllo", 4);
+ SVN_TEST_STRING_ASSERT(a->data, "test hllo, ");
+
+ svn_stringbuf_insert(a, 6, a->data + 1, 1);
+ SVN_TEST_STRING_ASSERT(a->data, "test hello, ");
+
+ svn_stringbuf_insert(a, 12, "world class", 5);
+ SVN_TEST_STRING_ASSERT(a->data, "test hello, world");
+
+ svn_stringbuf_insert(a, 1200, "!", 1);
+ return expect_stringbuf_equal(a, "test hello, world!", pool);
+}
+
+static svn_error_t *
+test_stringbuf_remove(apr_pool_t *pool)
+{
+ a = svn_stringbuf_create("test hello, world!", pool);
+
+ svn_stringbuf_remove(a, 0, 2);
+ SVN_TEST_STRING_ASSERT(a->data, "st hello, world!");
+
+ svn_stringbuf_remove(a, 2, 2);
+ SVN_TEST_STRING_ASSERT(a->data, "stello, world!");
+
+ svn_stringbuf_remove(a, 5, 200);
+ SVN_TEST_STRING_ASSERT(a->data, "stell");
+
+ svn_stringbuf_remove(a, 1200, 393);
+ return expect_stringbuf_equal(a, "stell", pool);
+}
+
+static svn_error_t *
+test_stringbuf_replace(apr_pool_t *pool)
+{
+ a = svn_stringbuf_create("odd with some world?", pool);
+
+ svn_stringbuf_replace(a, 0, 3, "tester", 4);
+ SVN_TEST_STRING_ASSERT(a->data, "test with some world?");
+
+ svn_stringbuf_replace(a, 5, 10, "hllo, coder", 6);
+ SVN_TEST_STRING_ASSERT(a->data, "test hllo, world?");
+
+ svn_stringbuf_replace(a, 6, 0, a->data + 1, 1);
+ SVN_TEST_STRING_ASSERT(a->data, "test hello, world?");
+
+ svn_stringbuf_replace(a, 17, 10, "!", 1);
+ SVN_TEST_STRING_ASSERT(a->data, "test hello, world!");
+
+ svn_stringbuf_replace(a, 1200, 199, "!!", 2);
+
+ return expect_stringbuf_equal(a, "test hello, world!!!", pool);
+}
+
/*
====================================================================
If you add a new test to this file, update this array.
@@ -599,5 +671,11 @@
"compare stringbufs; same length, different content"),
SVN_TEST_PASS2(test24,
"verify i64toa"),
+ SVN_TEST_PASS2(test_stringbuf_insert,
+ "check inserting into svn_stringbuf_t"),
+ SVN_TEST_PASS2(test_stringbuf_remove,
+ "check deletion from svn_stringbuf_t"),
+ SVN_TEST_PASS2(test_stringbuf_replace,
+ "check replacement in svn_stringbuf_t"),
SVN_TEST_NULL
};
diff --git a/subversion/tests/libsvn_wc/conflict-data-test.c b/subversion/tests/libsvn_wc/conflict-data-test.c
index abf245a..26258e2 100644
--- a/subversion/tests/libsvn_wc/conflict-data-test.c
+++ b/subversion/tests/libsvn_wc/conflict-data-test.c
@@ -86,8 +86,10 @@
SVN_TEST_STRING_ASSERT(expected->my_abspath, actual->my_abspath);
SVN_TEST_STRING_ASSERT(expected->merged_file, actual->merged_file);
SVN_TEST_ASSERT(expected->operation == actual->operation);
- compare_version(expected->src_left_version, actual->src_left_version);
- compare_version(expected->src_right_version, actual->src_right_version);
+ SVN_ERR(compare_version(expected->src_left_version,
+ actual->src_left_version));
+ SVN_ERR(compare_version(expected->src_right_version,
+ actual->src_right_version));
return SVN_NO_ERROR;
}
@@ -111,10 +113,11 @@
svn_wc_conflict_version_t *left, *right;
svn_wc_conflict_description2_t *conflict;
- left = svn_wc_conflict_version_create(left_repo, left_path, left_revnum,
- left_kind, result_pool);
- right = svn_wc_conflict_version_create(right_repo, right_path, right_revnum,
- right_kind, result_pool);
+ left = svn_wc_conflict_version_create2(left_repo, NULL, left_path,
+ left_revnum, left_kind, result_pool);
+ right = svn_wc_conflict_version_create2(right_repo, NULL, right_path,
+ right_revnum, right_kind,
+ result_pool);
conflict = svn_wc_conflict_description_create_tree2(
local_abspath, node_kind, operation,
left, right, result_pool);
diff --git a/subversion/tests/libsvn_wc/db-test.c b/subversion/tests/libsvn_wc/db-test.c
index 6f57ce5..56bdef5 100644
--- a/subversion/tests/libsvn_wc/db-test.c
+++ b/subversion/tests/libsvn_wc/db-test.c
@@ -97,83 +97,83 @@
"insert into nodes values ("
" 1, '', 0, null, 1, '', 1, 'normal',"
" null, null, 'dir', '()', 'infinity', null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'A', 0, '', 1, 'A', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 10, null, null, null);"
+ " 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'B', 0, '', 1, 'B', null, 'excluded',"
" null, null, 'symlink', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
- " 1, 'C', 0, '', 1, 'C', null, 'absent',"
+ " 1, 'C', 0, '', 1, 'C', null, 'server-excluded',"
" null, null, 'unknown', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'D', 0, '', 1, 'D', null, 'not-present',"
" null, null, 'unknown', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'E', 0, '', 1, 'E', null, 'incomplete',"
" null, null, 'unknown', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'F', 0, '', 1, 'F', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'G', 0, '', 2, 'G-alt', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'H', 0, '', 1, 'H', 1, 'normal',"
" null, null, 'symlink', '()', null, null, 'H-target', 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'I', 0, '', 1, 'I', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J', 0, '', 1, 'J', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e', 0, 'J', 1, 'J/J-e', 1, 'normal',"
" null, 'other/place', 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-a', 0, 'J/J-e', 1, 'J/J-e/J-e-a', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b', 0, 'J/J-e', 1, 'J/J-e/J-e-b', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b/Jeba', 0, 'J/J-e/J-e-b', 1, 'J/J-e/J-e-b/Jeba', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f', 0, 'J', 1, 'J/J-f', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f/J-f-a', 0, 'J/J-f', 1, 'J/J-f/J-f-a', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K', 0, '', 1, 'K', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-a', 0, 'K', 1, 'K/K-a', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-b', 0, 'K', 1, 'K/K-b', 1, 'normal',"
" null, 'moved/away', 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
""
/* Load data into NODES table;
### op_depths have not been calculated by me yet;
@@ -181,7 +181,7 @@
"insert into nodes values ("
" 1, 'I', 1, '', 2, 'some/dir', 2, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
/* I'm not sure what the working J is supposed to represent. It
replaces the base J, but is it a copy or not? It has no
@@ -190,135 +190,135 @@
"insert into nodes values ("
" 1, 'J', 1, '', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-a', 1, 'J', null, null, null, 'normal',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-b', 2, 'J', 2, 'some/dir', 2, 'normal',"
" 0, null, 'dir', '()', 'infinity', null, null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-b/J-b-a', 3, 'J/J-b', 2, 'another/dir', 2, 'normal',"
" 0, null, 'dir', '()', 'infinity', null, null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-b/J-b-b', 2, 'J/J-b', null, null, 2, 'normal',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-c', 1, 'J', null, null, null, 'normal',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-c/J-c-a', 1, 'J/J-c', null, null, null, 'normal',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-c', 2, 'J', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-c/J-c-a', 2, 'J/J-c', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-d', 2, 'J', 2, 'moved/file', 2, 'normal',"
" 1, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " 10, null, null, null);"
+ " 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'moved/file', 0, 'moved', 2, 'moved/file', 2, 'normal',"
" 0, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " 10, null, null, null);"
+ " 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'moved/file', 2, 'moved', 2, 'moved/file', 2, 'base-deleted',"
" 0, 'J/J-d', 'file', '()', null, null, null, null, null, null,"
- " 10, null, null, null);"
+ " 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e', 1, 'J', null, null, null, 'normal',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-a', 1, 'J/J-e', null, null, null, 'normal',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b', 1, 'J/J-e', null, null, null, 'normal',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e', 2, 'J', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-a', 2, 'J/J-e', null, null, null, 'base-deleted',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b', 2, 'J/J-e', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b/Jeba', 1, 'J/J-e/J-e-b', null, null, null, 'base-deleted',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f', 1, 'J', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f/J-f-a', 1, 'J/J-f', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K', 1, '', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-a', 1, 'K', null, null, null, 'base-deleted',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-b', 1, 'K', null, null, null, 'base-deleted',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L', 1, '', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L/L-a', 1, 'L', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L/L-a/L-a-a', 1, 'L/L-a', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L/L-a', 2, 'L', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L/L-a/L-a-a', 2, 'L/L-a', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'other/place', 2, 'other', null, null, null, 'normal',"
" 1, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'other/place/J-e-a', 2, 'other/place', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'other/place/J-e-b', 2, 'other/place', null, null, null, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'other/place/J-e-b/Jeba', 0, 'other/place/J-e-b', null, null, null, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into actual_node values ("
" 1, 'I', '', null, null, null, null, null, 'changelist', null, "
" null, null, null, null, null);"
@@ -340,7 +340,7 @@
SVN_ERR(svn_dirent_get_absolute(local_abspath,
svn_dirent_join("fake-wc", subdir, pool),
pool));
- SVN_ERR(svn_wc__db_open(db, NULL, TRUE, TRUE, pool, pool));
+ SVN_ERR(svn_wc__db_open(db, NULL, FALSE, TRUE, pool, pool));
SVN_ERR(svn_test__create_fake_wc(*local_abspath, TESTING_DATA, pool, pool));
return SVN_NO_ERROR;
@@ -459,7 +459,7 @@
SVN_TEST_ASSERT(target == NULL);
SVN_TEST_ASSERT(lock == NULL);
- /* Test: unknown kind, absent presence. */
+ /* Test: unknown kind, server-excluded presence. */
SVN_ERR(svn_wc__db_base_get_info(
&status, &kind, NULL,
NULL, NULL, NULL,
@@ -652,7 +652,7 @@
props,
1, TIME_1a, AUTHOR_1,
children, svn_depth_infinity,
- NULL, NULL, FALSE, NULL, NULL,
+ NULL, NULL, FALSE, NULL, NULL, NULL,
pool));
/* Replace an incomplete node with a file node. */
@@ -681,7 +681,7 @@
NULL, NULL,
pool));
- /* Replace an incomplete node with an absent file node. */
+ /* Replace an incomplete node with an server-excluded file node. */
SVN_ERR(svn_wc__db_base_add_excluded_node(
db, svn_dirent_join(local_abspath, "N/N-b", pool),
"N/N-b", ROOT_ONE, UUID_ONE, 3,
@@ -705,7 +705,7 @@
NULL, NULL,
pool));
- /* Create a new absent unknown-kind node. */
+ /* Create a new server-excluded unknown-kind node. */
SVN_ERR(svn_wc__db_base_add_excluded_node(
db, svn_dirent_join(local_abspath, "R", pool),
"R", ROOT_ONE, UUID_ONE, 3,
@@ -1406,6 +1406,7 @@
"not-a-uuid",
12,
props,
+ NULL,
10,
987654,
"somebody",
diff --git a/subversion/tests/libsvn_wc/entries-compat.c b/subversion/tests/libsvn_wc/entries-compat.c
index eed6bca..f2d2db0 100644
--- a/subversion/tests/libsvn_wc/entries-compat.c
+++ b/subversion/tests/libsvn_wc/entries-compat.c
@@ -96,83 +96,83 @@
"insert into nodes values ("
" 1, '', 0, null, 1, '', 1, 'normal',"
" null, null, 'dir', '()', 'infinity', null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'A', 0, '', 1, 'A', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 10, null, null, null);"
+ " 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'B', 0, '', 1, 'B', null, 'excluded',"
" null, null, 'symlink', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
- " 1, 'C', 0, '', 1, 'C', null, 'absent',"
+ " 1, 'C', 0, '', 1, 'C', null, 'server-excluded',"
" null, null, 'unknown', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'D', 0, '', 1, 'D', null, 'not-present',"
" null, null, 'unknown', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'E', 0, '', 1, 'E', null, 'incomplete',"
" null, null, 'unknown', null, null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'F', 0, '', 1, 'F', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'G', 0, '', 2, 'G-alt', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'H', 0, '', 1, 'H', 1, 'normal',"
" null, null, 'symlink', '()', null, null, 'H-target', 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'I', 0, '', 1, 'I', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J', 0, '', 1, 'J', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e', 0, 'J', 1, 'J/J-e', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-a', 0, 'J/J-e', 1, 'J/J-e/J-e-a', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b', 0, 'J/J-e', 1, 'J/J-e/J-e-b', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b/Jeba', 0, 'J/J-e/J-e-b', 1, 'J/J-e/J-e-b/Jeba', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f', 0, 'J', 1, 'J/J-f', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f/J-f-a', 0, 'J/J-f', 1, 'J/J-f/J-f-a', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K', 0, '', 1, 'K', 1, 'normal',"
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-a', 0, 'K', 1, 'K/K-a', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-b', 0, 'K', 1, 'K/K-b', 1, 'normal',"
" null, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " 15, null, null, null);"
+ " 15, null, null, null, null);"
""
/* Load data into NODES table;
### op_depths have not been calculated by me yet;
@@ -180,87 +180,87 @@
"insert into nodes values ("
" 1, 'I', 1, '', 2, 'some/dir', 2, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J', 1, '', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-a', 1, 'J', null, null, null, 'normal',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-b', 1, 'J', 2, 'some/dir', 2, 'normal',"
" 0, null, 'dir', '()', 'infinity', null, null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-b/J-b-a', 1, 'J/J-b', 2, 'another/dir', 2, 'normal',"
" 0, null, 'dir', '()', 'infinity', null, null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-b/J-b-b', 1, 'J/J-b', null, null, null, 'normal',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-c', 1, 'J', null, null, null, 'not-present',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-c/J-c-a', 1, 'J/J-c', null, null, null, 'not-present',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-d', 1, 'J', 2, 'moved/file', 2, 'normal',"
" 1, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
- " 10, null, null, null);"
+ " 10, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e', 1, 'J', null, null, null, 'not-present',"
" 0, 'other/place', 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-a', 1, 'J/J-e', null, null, null, 'not-present',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b', 1, 'J/J-e', null, null, null, 'not-present',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-e/J-e-b/Jeba', 1, 'J/J-e/J-e-b', null, null, null, 'base-deleted',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f', 1, 'J', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'J/J-f/J-f-a', 1, 'J/J-f', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K', 1, '', null, null, null, 'base-deleted',"
" 0, null, 'dir', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-a', 1, 'K', null, null, null, 'base-deleted',"
" 0, null, 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'K/K-b', 1, 'K', null, null, null, 'base-deleted',"
" 0, 'moved/away', 'file', '()', null, null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L', 1, '', null, null, null, 'normal',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L/L-a', 1, 'L', null, null, null, 'not-present',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'L/L-a/L-a-a', 1, 'L/L-a', null, null, null, 'not-present',"
" 0, null, 'dir', '()', 'immediates', null, null, null, null, null,"
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into actual_node values ("
" 1, 'I', '', null, null, null, null, null, 'changelist', null, "
" null, null, null, null, null);"
@@ -274,11 +274,11 @@
"insert into nodes values ("
" 1, 'M', 0, '', 1, 'M', 1, 'normal', "
" null, null, 'dir', '()', null, null, null, 1, " TIME_1s ", '" AUTHOR_1 "', "
- " null, null, null, null);"
+ " null, null, null, null, null);"
"insert into nodes values ("
" 1, 'M/M-a', 0, 'M', 1, 'M/M-a', 1, 'not-present', "
" null, null, 'file', '()', null, null, null, 1, null, null, "
- " null, null, null, null);"
+ " null, null, null, null, null);"
);
@@ -295,7 +295,7 @@
"insert into nodes values ("
" 1, '', 0, null, 1, 'M', 1, 'normal',"
" null, null, 'dir', '()', 'infinity', null, null, 1, " TIME_1s ", '" AUTHOR_1 "',"
- " null, null, null, null);"
+ " null, null, null, null, null);"
);
@@ -332,7 +332,7 @@
pool));
SVN_ERR(svn_wc__db_open(db,
NULL /* config */,
- TRUE /* auto_upgrade */,
+ FALSE /* auto_upgrade */,
TRUE /* enforce_empty_wq */,
pool, pool));
diff --git a/subversion/tests/libsvn_wc/op-depth-test.c b/subversion/tests/libsvn_wc/op-depth-test.c
index 9c5ad41..9e55d8b 100644
--- a/subversion/tests/libsvn_wc/op-depth-test.c
+++ b/subversion/tests/libsvn_wc/op-depth-test.c
@@ -78,7 +78,8 @@
apr_pool_t *scratch_pool)
{
SVN_ERR(svn_wc__db_util_open_db(sdb, wc_root_abspath, "wc.db",
- svn_sqlite__mode_readwrite, my_statements,
+ svn_sqlite__mode_readwrite,
+ FALSE /* exclusive */, my_statements,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
@@ -261,6 +262,17 @@
}
static svn_error_t *
+wc_resolve(svn_test__sandbox_t *b, const char *path)
+{
+ svn_client_ctx_t *ctx;
+
+ SVN_ERR(svn_client_create_context(&ctx, b->pool));
+ return svn_client_resolve(wc_path(b, path), svn_depth_infinity,
+ svn_wc_conflict_choose_mine_conflict,
+ ctx, b->pool);
+}
+
+static svn_error_t *
wc_move(svn_test__sandbox_t *b, const char *src, const char *dst)
{
svn_client_ctx_t *ctx;
@@ -1284,7 +1296,7 @@
"not-even-a-uuid", revision,
apr_hash_make(b->pool), revision,
0, NULL, NULL, svn_depth_infinity,
- NULL, NULL, FALSE, NULL, NULL,
+ NULL, NULL, FALSE, NULL, NULL, NULL,
b->pool));
after = apr_palloc(b->pool, sizeof(*after) * (apr_size_t)(num_before + num_added + 1));
@@ -3383,7 +3395,7 @@
SVN_ERR(svn_test__sandbox_create(&b, "copy_of_deleted", opts, pool));
SVN_ERR(add_and_commit_greek_tree(&b));
- /* Recreate the test scenario from copy_tests.py copy_wc_url_with_absent */
+ /* Recreate the test scenario from copy_tests.py copy_wc_url_with_server_excluded */
/* Delete A/B */
SVN_ERR(wc_delete(&b, "A/B"));
@@ -3673,21 +3685,21 @@
}
static svn_error_t *
-copy_wc_wc_absent(const svn_test_opts_t *opts, apr_pool_t *pool)
+copy_wc_wc_server_excluded(const svn_test_opts_t *opts, apr_pool_t *pool)
{
svn_test__sandbox_t b;
nodes_row_t before[] = {
{0, "", "normal", 1, ""},
{0, "A", "normal", 1, "A"},
{0, "A/B", "normal", 1, "A/B"},
- {0, "A/B/E", "absent", 1, "A/B/E"},
+ {0, "A/B/E", "server-excluded", 1, "A/B/E"},
{0}
};
nodes_row_t after[] = {
{0, "", "normal", 1, ""},
{0, "A", "normal", 1, "A"},
{0, "A/B", "normal", 1, "A/B"},
- {0, "A/B/E", "absent", 1, "A/B/E"},
+ {0, "A/B/E", "server-excluded", 1, "A/B/E"},
{1, "X", "normal", 1, "A"},
{1, "X/B", "normal", 1, "A/B"},
{1, "X/B/E", "incomplete", 1, "A/B/E"},
@@ -3695,7 +3707,7 @@
};
svn_error_t *err;
- SVN_ERR(svn_test__sandbox_create(&b, "copy_wc_wc_absent", opts, pool));
+ SVN_ERR(svn_test__sandbox_create(&b, "copy_wc_wc_server_excluded", opts, pool));
SVN_ERR(insert_dirs(&b, before));
SVN_ERR(check_db_rows(&b, "", before));
SVN_ERR(disk_mkdir(&b, "A"));
@@ -4527,8 +4539,13 @@
SVN_ERR(wc_mkdir(&b, "A"));
SVN_ERR(wc_mkdir(&b, "A/B"));
+ file_write(&b, "A/B/f", "r1 content\n");
+ SVN_ERR(wc_add(&b, "A/B/f"));
SVN_ERR(wc_commit(&b, ""));
- SVN_ERR(wc_mkdir(&b, "A/B/C"));
+ file_write(&b, "A/B/f", "r1 content\nr2 content\n");
+ SVN_ERR(wc_commit(&b, ""));
+ file_write(&b, "A/B/g", "r3 content\n");
+ SVN_ERR(wc_add(&b, "A/B/g"));
SVN_ERR(wc_commit(&b, ""));
SVN_ERR(wc_update(&b, "", 1));
@@ -4539,49 +4556,94 @@
{0, "", "normal", 1, ""},
{0, "A", "normal", 1, "A"},
{0, "A/B", "normal", 1, "A/B"},
+ {0, "A/B/f", "normal", 1, "A/B/f"},
{1, "A", "base-deleted", NO_COPY_FROM, "A2"},
{1, "A/B", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/f", "base-deleted", NO_COPY_FROM},
{1, "A2", "normal", 1, "A", MOVED_HERE},
{1, "A2/B", "normal", 1, "A/B", MOVED_HERE},
+ {1, "A2/B/f", "normal", 1, "A/B/f", MOVED_HERE},
{0}
};
SVN_ERR(check_db_rows(&b, "", nodes));
}
- /* Update A/B makes A2 a mixed-revision copy */
- SVN_ERR(wc_update(&b, "A/B", 2));
+ /* Update causes a tree-conflict on A due to incoming text-change. */
+ SVN_ERR(wc_update(&b, "", 2));
{
nodes_row_t nodes[] = {
- {0, "", "normal", 1, ""},
- {0, "A", "normal", 1, "A"},
- {0, "A/B", "normal", 2, "A/B"},
- {0, "A/B/C", "normal", 2, "A/B/C"},
- {1, "A", "base-deleted", NO_COPY_FROM, "A2"},
- {1, "A/B", "base-deleted", NO_COPY_FROM},
- {1, "A/B/C", "base-deleted", NO_COPY_FROM},
- {1, "A2", "normal", 1, "A", MOVED_HERE},
- {1, "A2/B", "not-present", 2, "A/B"}, /* XFAIL */
- {2, "A2/B", "normal", 2, "A/B", MOVED_HERE},
- {2, "A2/B/C", "normal", 2, "A/B/C", MOVED_HERE},
- {0}
- };
- SVN_ERR(check_db_rows(&b, "", nodes));
- }
-
- /* Update A makes A2 back into a single-revision copy */
- SVN_ERR(wc_update(&b, "A", 2));
- {
- nodes_row_t nodes[] = {
- {0, "", "normal", 1, ""},
+ {0, "", "normal", 2, ""},
{0, "A", "normal", 2, "A"},
{0, "A/B", "normal", 2, "A/B"},
- {0, "A/B/C", "normal", 2, "A/B/C"},
+ {0, "A/B/f", "normal", 2, "A/B/f"},
{1, "A", "base-deleted", NO_COPY_FROM, "A2"},
{1, "A/B", "base-deleted", NO_COPY_FROM},
- {1, "A/B/C", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/f", "base-deleted", NO_COPY_FROM},
+ {1, "A2", "normal", 1, "A", MOVED_HERE},
+ {1, "A2/B", "normal", 1, "A/B", MOVED_HERE},
+ {1, "A2/B/f", "normal", 1, "A/B/f", MOVED_HERE},
+ {0}
+ };
+ SVN_ERR(check_db_rows(&b, "", nodes));
+ }
+
+ /* Resolve should update the move. */
+ SVN_ERR(wc_resolve(&b, "A"));
+ {
+ nodes_row_t nodes[] = {
+ {0, "", "normal", 2, ""},
+ {0, "A", "normal", 2, "A"},
+ {0, "A/B", "normal", 2, "A/B"},
+ {0, "A/B/f", "normal", 2, "A/B/f"},
+ {1, "A", "base-deleted", NO_COPY_FROM, "A2"},
+ {1, "A/B", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/f", "base-deleted", NO_COPY_FROM},
{1, "A2", "normal", 2, "A", MOVED_HERE},
{1, "A2/B", "normal", 2, "A/B", MOVED_HERE},
- {1, "A2/B/C", "normal", 2, "A/B/C", MOVED_HERE},
+ {1, "A2/B/f", "normal", 2, "A/B/f", MOVED_HERE},
+ {0}
+ };
+ SVN_ERR(check_db_rows(&b, "", nodes));
+ }
+
+ /* Update causes a tree-conflict on due to incoming add. */
+ SVN_ERR(wc_update(&b, "", 3));
+ {
+ nodes_row_t nodes[] = {
+ {0, "", "normal", 3, ""},
+ {0, "A", "normal", 3, "A"},
+ {0, "A/B", "normal", 3, "A/B"},
+ {0, "A/B/f", "normal", 3, "A/B/f"},
+ {0, "A/B/g", "normal", 3, "A/B/g"},
+ {1, "A", "base-deleted", NO_COPY_FROM, "A2"},
+ {1, "A/B", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/f", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/g", "base-deleted", NO_COPY_FROM},
+ {1, "A2", "normal", 2, "A", MOVED_HERE},
+ {1, "A2/B", "normal", 2, "A/B", MOVED_HERE},
+ {1, "A2/B/f", "normal", 2, "A/B/f", MOVED_HERE},
+ {0}
+ };
+ SVN_ERR(check_db_rows(&b, "", nodes));
+ }
+
+ /* Are we going to handle this case? */
+ SVN_ERR(wc_resolve(&b, "A"));
+ {
+ nodes_row_t nodes[] = {
+ {0, "", "normal", 3, ""},
+ {0, "A", "normal", 3, "A"},
+ {0, "A/B", "normal", 3, "A/B"},
+ {0, "A/B/f", "normal", 3, "A/B/f"},
+ {0, "A/B/g", "normal", 3, "A/B/g"},
+ {1, "A", "base-deleted", NO_COPY_FROM, "A2"},
+ {1, "A/B", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/f", "base-deleted", NO_COPY_FROM},
+ {1, "A/B/g", "base-deleted", NO_COPY_FROM},
+ {1, "A2", "normal", 3, "A", MOVED_HERE},
+ {1, "A2/B", "normal", 3, "A/B", MOVED_HERE},
+ {1, "A2/B/f", "normal", 3, "A/B/f", MOVED_HERE},
+ {1, "A2/B/g", "normal", 3, "A/B/g", MOVED_HERE},
{0}
};
SVN_ERR(check_db_rows(&b, "", nodes));
@@ -4926,9 +4988,9 @@
{1, "A/B/C", "base-deleted", NO_COPY_FROM},
{1, "X", "normal", 1, "A", MOVED_HERE},
{1, "X/B", "not-present", 2, "A/B"},
- {2, "X/B", "normal", 2, "A/B", MOVED_HERE},
+ {2, "X/B", "normal", 2, "A/B"},
{2, "X/B/C", "not-present", 3, "A/B/C"},
- {3, "X/B/C", "normal", 3, "A/B/C", MOVED_HERE},
+ {3, "X/B/C", "normal", 3, "A/B/C"},
{0}
};
SVN_ERR(check_db_rows(&b, "", nodes));
@@ -5054,8 +5116,8 @@
"revert_file_externals"),
SVN_TEST_OPTS_PASS(copy_file_externals,
"copy_file_externals"),
- SVN_TEST_OPTS_PASS(copy_wc_wc_absent,
- "test_wc_wc_copy_absent"),
+ SVN_TEST_OPTS_PASS(copy_wc_wc_server_excluded,
+ "test_wc_wc_copy_server_excluded"),
SVN_TEST_OPTS_PASS(incomplete_switch,
"incomplete_switch (issue 4040)"),
SVN_TEST_OPTS_PASS(nested_moves_child_first,
diff --git a/subversion/tests/libsvn_wc/utils.c b/subversion/tests/libsvn_wc/utils.c
index 6d6c356..94cd8c4 100644
--- a/subversion/tests/libsvn_wc/utils.c
+++ b/subversion/tests/libsvn_wc/utils.c
@@ -85,7 +85,7 @@
svn_client_ctx_t *ctx;
svn_opt_revision_t head_rev = { svn_opt_revision_head, {0} };
- SVN_ERR(svn_client_create_context(&ctx, subpool));
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, subpool));
SVN_ERR(svn_dirent_get_absolute(wc_abspath, wc_path, pool));
SVN_ERR(svn_client_checkout3(NULL, *repos_url, *wc_abspath,
&head_rev, &head_rev, svn_depth_infinity,
@@ -133,7 +133,8 @@
svn_error_clear(svn_io_remove_file2(db_abspath, FALSE, scratch_pool));
SVN_ERR(svn_wc__db_util_open_db(&sdb, wc_abspath, "wc.db",
- svn_sqlite__mode_rwcreate, my_statements,
+ svn_sqlite__mode_rwcreate,
+ FALSE /* exclusive */, my_statements,
result_pool, scratch_pool));
for (i = 0; my_statements[i] != NULL; i++)
SVN_ERR(svn_sqlite__exec_statements(sdb, /* my_statements[] */ i));
diff --git a/tools/buildbot/slaves/win32-SharpSvn/svntest-bindings.cmd b/tools/buildbot/slaves/win32-SharpSvn/svntest-bindings.cmd
index 275252a..48d002b 100644
--- a/tools/buildbot/slaves/win32-SharpSvn/svntest-bindings.cmd
+++ b/tools/buildbot/slaves/win32-SharpSvn/svntest-bindings.cmd
@@ -32,7 +32,7 @@
PATH %PATH%;%TESTDIR%\bin
SET result=0
-python win-tests.py -r -f fsfs --javahl "%TESTDIR%\tests"
+python win-tests.py -d -f fsfs --javahl "%TESTDIR%\tests"
IF ERRORLEVEL 1 (
echo [python reported error %ERRORLEVEL%]
SET result=1
diff --git a/tools/buildbot/slaves/win32-SharpSvn/svntest-build-bindings.cmd b/tools/buildbot/slaves/win32-SharpSvn/svntest-build-bindings.cmd
index a7f185f..78658a1 100644
--- a/tools/buildbot/slaves/win32-SharpSvn/svntest-build-bindings.cmd
+++ b/tools/buildbot/slaves/win32-SharpSvn/svntest-build-bindings.cmd
@@ -29,5 +29,8 @@
EXIT /B 0
)
-msbuild subversion_vcnet.sln /p:Configuration=Release /p:Platform=win32 /t:__JAVAHL__ /t:__SWIG_PYTHON__ /t:__SWIG_PERL__ /t:__JAVAHL_TESTS__
+msbuild subversion_vcnet.sln /p:Configuration=Debug /p:Platform=win32 /t:__JAVAHL__ /t:__JAVAHL_TESTS__
+IF ERRORLEVEL 1 EXIT /B 1
+
+msbuild subversion_vcnet.sln /p:Configuration=Release /p:Platform=win32 /t:__SWIG_PYTHON__ /t:__SWIG_PERL__
IF ERRORLEVEL 1 EXIT /B 1
diff --git a/tools/buildbot/slaves/win32-SharpSvn/svntest-build.cmd b/tools/buildbot/slaves/win32-SharpSvn/svntest-build.cmd
index 23c7cb9..f9441e1 100644
--- a/tools/buildbot/slaves/win32-SharpSvn/svntest-build.cmd
+++ b/tools/buildbot/slaves/win32-SharpSvn/svntest-build.cmd
@@ -25,7 +25,7 @@
PUSHD ..\deps
-nant gen-dev -D:wc=..\build -D:impBase=../deps/build/win32 %NANTARGS%
+nant gen-dev -D:wc=..\build -D:impBase=../deps/build/win32 -D:botBuild=true %NANTARGS%
IF ERRORLEVEL 1 EXIT /B 1
POPD
diff --git a/tools/client-side/svn-bench/cl.h b/tools/client-side/svn-bench/cl.h
new file mode 100644
index 0000000..51dbdee
--- /dev/null
+++ b/tools/client-side/svn-bench/cl.h
@@ -0,0 +1,213 @@
+/*
+ * cl.h: shared stuff in the command line program
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CL_H
+#define SVN_CL_H
+
+/*** Includes. ***/
+
+#include <apr_tables.h>
+
+#include "svn_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Command dispatch. ***/
+
+/* Hold results of option processing that are shared by multiple
+ commands. */
+typedef struct svn_cl__opt_state_t
+{
+ /* An array of svn_opt_revision_range_t *'s representing revisions
+ ranges indicated on the command-line via the -r and -c options.
+ For each range in the list, if only one revision was provided
+ (-rN), its 'end' member remains 'svn_opt_revision_unspecified'.
+ This array always has at least one element, even if that is a
+ null range in which both ends are 'svn_opt_revision_unspecified'. */
+ apr_array_header_t *revision_ranges;
+
+ /* These are simply a copy of the range start and end values present
+ in the first item of the revision_ranges list. */
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+
+ /* Flag which is only set if the '-c' option was used. */
+ svn_boolean_t used_change_arg;
+
+ /* Flag which is only set if the '-r' option was used. */
+ svn_boolean_t used_revision_arg;
+
+ /* Max number of log messages to get back from svn_client_log2. */
+ int limit;
+
+ /* After option processing is done, reflects the switch actually
+ given on the command line, or svn_depth_unknown if none. */
+ svn_depth_t depth;
+
+ svn_boolean_t quiet; /* sssh...avoid unnecessary output */
+ svn_boolean_t non_interactive; /* do no interactive prompting */
+ svn_boolean_t version; /* print version information */
+ svn_boolean_t verbose; /* be verbose */
+ svn_boolean_t strict; /* do strictly what was requested */
+ const char *encoding; /* the locale/encoding of the data*/
+ svn_boolean_t help; /* print usage message */
+ const char *auth_username; /* auth username */ /* UTF-8! */
+ const char *auth_password; /* auth password */ /* UTF-8! */
+ const char *extensions; /* subprocess extension args */ /* UTF-8! */
+ apr_array_header_t *targets; /* target list from file */ /* UTF-8! */
+ svn_boolean_t no_auth_cache; /* do not cache authentication information */
+ svn_boolean_t stop_on_copy; /* don't cross copies during processing */
+ const char *config_dir; /* over-riding configuration directory */
+ apr_array_header_t *config_options; /* over-riding configuration options */
+ svn_boolean_t all_revprops; /* retrieve all revprops */
+ svn_boolean_t no_revprops; /* retrieve no revprops */
+ apr_hash_t *revprop_table; /* table of revision properties to get/set */
+ svn_boolean_t use_merge_history; /* use/display extra merge information */
+ svn_boolean_t trust_server_cert; /* trust server SSL certs that would
+ otherwise be rejected as "untrusted" */
+} svn_cl__opt_state_t;
+
+
+typedef struct svn_cl__cmd_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_client_ctx_t *ctx;
+} svn_cl__cmd_baton_t;
+
+
+/* Declare all the command procedures */
+svn_opt_subcommand_t
+ svn_cl__help,
+ svn_cl__null_export,
+ svn_cl__null_list,
+ svn_cl__null_log;
+
+
+/* See definition in main.c for documentation. */
+extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[];
+
+/* See definition in main.c for documentation. */
+extern const int svn_cl__global_options[];
+
+/* See definition in main.c for documentation. */
+extern const apr_getopt_option_t svn_cl__options[];
+
+
+/* A helper for the many subcommands that wish to merely warn when
+ * invoked on an unversioned, nonexistent, or otherwise innocuously
+ * errorful resource. Meant to be wrapped with SVN_ERR().
+ *
+ * If ERR is null, return SVN_NO_ERROR.
+ *
+ * Else if ERR->apr_err is one of the error codes supplied in varargs,
+ * then handle ERR as a warning (unless QUIET is true), clear ERR, and
+ * return SVN_NO_ERROR, and push the value of ERR->apr_err into the
+ * ERRORS_SEEN array, if ERRORS_SEEN is not NULL.
+ *
+ * Else return ERR.
+ *
+ * Typically, error codes like SVN_ERR_UNVERSIONED_RESOURCE,
+ * SVN_ERR_ENTRY_NOT_FOUND, etc, are supplied in varargs. Don't
+ * forget to terminate the argument list with SVN_NO_ERROR.
+ */
+svn_error_t *
+svn_cl__try(svn_error_t *err,
+ apr_array_header_t *errors_seen,
+ svn_boolean_t quiet,
+ ...);
+
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton);
+
+
+/* Print to stdout a hash that maps property names (char *) to property
+ values (svn_string_t *). The names are assumed to be in UTF-8 format;
+ the values are either in UTF-8 (the special Subversion props) or
+ plain binary values.
+
+ If OUT is not NULL, then write to it rather than stdout.
+
+ If NAMES_ONLY is true, print just names, else print names and
+ values. */
+svn_error_t *
+svn_cl__print_prop_hash(svn_stream_t *out,
+ apr_hash_t *prop_hash,
+ svn_boolean_t names_only,
+ apr_pool_t *pool);
+
+
+/*** Notification functions to display results on the terminal. */
+
+/* Set *NOTIFY_FUNC_P and *NOTIFY_BATON_P to a notifier/baton for all
+ * operations, allocated in POOL.
+ */
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ apr_pool_t *pool);
+
+/* Make the notifier for use with BATON print the appropriate summary
+ * line at the end of the output.
+ */
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton);
+
+/* Like svn_client_args_to_target_array() but, if the only error is that some
+ * arguments are reserved file names, then print warning messages for those
+ * targets, store the rest of the targets in TARGETS_P and return success. */
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_dest_origpath_on_truepath_collision,
+ apr_pool_t *pool);
+
+/* Return an error if TARGET is a URL; otherwise return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target);
+
+/* Return a copy of PATH, converted to the local path style, skipping
+ * PARENT_PATH if it is non-null and is a parent of or equal to PATH.
+ *
+ * This function assumes PARENT_PATH and PATH are both absolute "dirents"
+ * or both relative "dirents". */
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CL_H */
diff --git a/tools/client-side/svn-bench/client_errors.h b/tools/client-side/svn-bench/client_errors.h
new file mode 100644
index 0000000..1e9808d
--- /dev/null
+++ b/tools/client-side/svn-bench/client_errors.h
@@ -0,0 +1,95 @@
+/*
+ * client_errors.h: error codes this command line client features
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CLIENT_ERRORS_H
+#define SVN_CLIENT_ERRORS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * This error defining system is copied from and explained in
+ * ../../include/svn_error_codes.h
+ */
+
+/* Process this file if we're building an error array, or if we have
+ not defined the enumerated constants yet. */
+#if defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#if defined(SVN_ERROR_BUILD_ARRAY)
+
+#define SVN_ERROR_START \
+ static const err_defn error_table[] = { \
+ { SVN_ERR_CDMLINE__WARNING, "Warning" },
+#define SVN_ERRDEF(n, s) { n, s },
+#define SVN_ERROR_END { 0, NULL } };
+
+#elif !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#define SVN_ERROR_START \
+ typedef enum svn_client_errno_t { \
+ SVN_ERR_CDMLINE__WARNING = SVN_ERR_LAST + 1,
+#define SVN_ERRDEF(n, s) n,
+#define SVN_ERROR_END SVN_ERR_CMDLINE__ERR_LAST } svn_client_errno_t;
+
+#define SVN_CMDLINE_ERROR_ENUM_DEFINED
+
+#endif
+
+/* Define custom command line client error numbers */
+
+SVN_ERROR_START
+
+ /* BEGIN Client errors */
+
+SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_WRITE,
+ "Failed writing to temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_STAT,
+ "Failed getting info about temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_OPEN,
+ "Failed opening temporary file.")
+
+ /* END Client errors */
+
+
+SVN_ERROR_END
+
+#undef SVN_ERROR_START
+#undef SVN_ERRDEF
+#undef SVN_ERROR_END
+
+#endif /* SVN_ERROR_BUILD_ARRAY || !SVN_CMDLINE_ERROR_ENUM_DEFINED */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CLIENT_ERRORS_H */
diff --git a/tools/client-side/svn-bench/help-cmd.c b/tools/client-side/svn-bench/help-cmd.c
new file mode 100644
index 0000000..b84a098
--- /dev/null
+++ b/tools/client-side/svn-bench/help-cmd.c
@@ -0,0 +1,94 @@
+/*
+ * help-cmd.c -- Provide help
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_version.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__help(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state;
+
+ /* xgettext: the %s is for SVN_VER_NUMBER. */
+ char help_header_template[] =
+ N_("usage: svn-bench <subcommand> [options] [args]\n"
+ "Subversion command-line client, version %s.\n"
+ "Type 'svn-bench help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svn-bench --version' to see the program version and RA modules\n"
+ " or 'svn-bench --version --quiet' to see just the version number.\n"
+ "\n"
+ "Most subcommands take file and/or directory arguments, recursing\n"
+ "on the directories. If no arguments are supplied to such a\n"
+ "command, it recurses on the current directory (inclusive) by default.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ char help_footer[] =
+ N_("Subversion is a tool for version control.\n"
+ "For additional information, see http://subversion.apache.org/\n");
+
+ char *help_header =
+ apr_psprintf(pool, _(help_header_template), SVN_VER_NUMBER);
+
+ const char *ra_desc_start
+ = _("The following repository access (RA) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ if (baton)
+ opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ else
+ opt_state = NULL;
+
+ version_footer = svn_stringbuf_create(ra_desc_start, pool);
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+ return svn_opt_print_help4(os,
+ "svn", /* ### erm, derive somehow? */
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ opt_state ? opt_state->verbose : FALSE,
+ version_footer->data,
+ help_header, /* already gettext()'d */
+ svn_cl__cmd_table,
+ svn_cl__options,
+ svn_cl__global_options,
+ _(help_footer),
+ pool);
+}
diff --git a/tools/client-side/svn-bench/main.c b/tools/client-side/svn-bench/main.c
new file mode 100644
index 0000000..4e370ab
--- /dev/null
+++ b/tools/client-side/svn-bench/main.c
@@ -0,0 +1,954 @@
+/*
+ * main.c: Subversion command line client.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <assert.h>
+
+#include <apr_signal.h>
+
+#include "svn_cmdline.h"
+#include "svn_dirent_uri.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+#include "svn_version.h"
+
+#include "cl.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Option Processing ***/
+
+/* Add an identifier here for long options that don't have a short
+ option. Options that have both long and short options should just
+ use the short option letter as identifier. */
+typedef enum svn_cl__longopt_t {
+ opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_auth_username,
+ opt_config_dir,
+ opt_config_options,
+ opt_depth,
+ opt_no_auth_cache,
+ opt_non_interactive,
+ opt_stop_on_copy,
+ opt_strict,
+ opt_targets,
+ opt_version,
+ opt_with_revprop,
+ opt_with_all_revprops,
+ opt_with_no_revprops,
+ opt_trust_server_cert,
+} svn_cl__longopt_t;
+
+
+/* Option codes and descriptions for the command line client.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+const apr_getopt_option_t svn_cl__options[] =
+{
+ {"help", 'h', 0, N_("show help on a subcommand")},
+ {NULL, '?', 0, N_("show help on a subcommand")},
+ {"quiet", 'q', 0, N_("print nothing, or only summary information")},
+ {"recursive", 'R', 0, N_("descend recursively, same as --depth=infinity")},
+ {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")},
+ {"change", 'c', 1,
+ N_("the change made by revision ARG (like -r ARG-1:ARG)\n"
+ " "
+ "If ARG is negative this is like -r ARG:ARG-1\n"
+ " "
+ "If ARG is of the form ARG1-ARG2 then this is like\n"
+ " "
+ "ARG1:ARG2, where ARG1 is inclusive")},
+ {"revision", 'r', 1,
+ N_("ARG (some commands also take ARG1:ARG2 range)\n"
+ " "
+ "A revision argument can be one of:\n"
+ " "
+ " NUMBER revision number\n"
+ " "
+ " '{' DATE '}' revision at start of the date\n"
+ " "
+ " 'HEAD' latest in repository\n"
+ " "
+ " 'BASE' base rev of item's working copy\n"
+ " "
+ " 'COMMITTED' last commit at or before BASE\n"
+ " "
+ " 'PREV' revision just before COMMITTED")},
+ {"version", opt_version, 0, N_("show program version information")},
+ {"verbose", 'v', 0, N_("print extra information")},
+ {"username", opt_auth_username, 1, N_("specify a username ARG")},
+ {"password", opt_auth_password, 1, N_("specify a password ARG")},
+ {"targets", opt_targets, 1,
+ N_("pass contents of file ARG as additional args")},
+ {"depth", opt_depth, 1,
+ N_("limit operation by depth ARG ('empty', 'files',\n"
+ " "
+ "'immediates', or 'infinity')")},
+ {"strict", opt_strict, 0, N_("use strict semantics")},
+ {"stop-on-copy", opt_stop_on_copy, 0,
+ N_("do not cross copies while traversing history")},
+ {"no-auth-cache", opt_no_auth_cache, 0,
+ N_("do not cache authentication tokens")},
+ {"trust-server-cert", opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {"non-interactive", opt_non_interactive, 0,
+ N_("do no interactive prompting")},
+ {"config-dir", opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"config-option", opt_config_options, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"limit", 'l', 1, N_("maximum number of log entries")},
+ {"with-all-revprops", opt_with_all_revprops, 0,
+ N_("retrieve all revision properties")},
+ {"with-no-revprops", opt_with_no_revprops, 0,
+ N_("retrieve no revision properties")},
+ {"with-revprop", opt_with_revprop, 1,
+ N_("set revision property ARG in new revision\n"
+ " "
+ "using the name[=value] format")},
+ {"use-merge-history", 'g', 0,
+ N_("use/display additional information from merge\n"
+ " "
+ "history")},
+
+ /* Long-opt Aliases
+ *
+ * These have NULL desriptions, but an option code that matches some
+ * other option (whose description should probably mention its aliases).
+ */
+
+ {0, 0, 0, 0},
+};
+
+
+
+/*** Command dispatch. ***/
+
+/* Our array of available subcommands.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ *
+ * In most of the help text "PATH" is used where a working copy path is
+ * required, "URL" where a repository URL is required and "TARGET" when
+ * either a path or a url can be used. Hmm, should this be part of the
+ * help text?
+ */
+
+/* Options that apply to all commands. (While not every command may
+ currently require authentication or be interactive, allowing every
+ command to take these arguments allows scripts to just pass them
+ willy-nilly to every invocation of 'svn') . */
+const int svn_cl__global_options[] =
+{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive,
+ opt_trust_server_cert, opt_config_dir, opt_config_options, 0
+};
+
+const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
+{
+ { "help", svn_cl__help, {"?", "h"}, N_
+ ("Describe the usage of this program or its subcommands.\n"
+ "usage: help [SUBCOMMAND...]\n"),
+ {0} },
+ /* This command is also invoked if we see option "--help", "-h" or "-?". */
+
+ { "null-export", svn_cl__null_export, {0}, N_
+ ("Create an unversioned copy of a tree.\n"
+ "usage: null-export [-r REV] URL[@PEGREV]\n"
+ "\n"
+ " Exports a clean directory tree from the repository specified by\n"
+ " URL, at revision REV if it is given, otherwise at HEAD.\n"
+ "\n"
+ " If specified, PEGREV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r', 'q', 'N', opt_depth} },
+
+ { "null-list", svn_cl__null_list, {"ls"}, N_
+ ("List directory entries in the repository.\n"
+ "usage: list [TARGET[@REV]...]\n"
+ "\n"
+ " List each TARGET file and the contents of each TARGET directory as\n"
+ " they exist in the repository. If TARGET is a working copy path, the\n"
+ " corresponding repository URL will be used. If specified, REV determines\n"
+ " in which revision the target is first looked up.\n"
+ "\n"
+ " The default TARGET is '.', meaning the repository URL of the current\n"
+ " working directory.\n"
+ "\n"
+ " With --verbose, the following fields will be fetched for each item:\n"
+ "\n"
+ " Revision number of the last commit\n"
+ " Author of the last commit\n"
+ " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n"
+ " Size (in bytes)\n"
+ " Date and time of the last commit\n"),
+ {'r', 'v', 'R', opt_depth} },
+
+ { "null-log", svn_cl__null_log, {0}, N_
+ ("Fetch the log messages for a set of revision(s) and/or path(s).\n"
+ "usage: 1. null-log [PATH][@REV]\n"
+ " 2. null-log URL[@REV] [PATH...]\n"
+ "\n"
+ " 1. Fetch the log messages for the URL corresponding to PATH\n"
+ " (default: '.'). If specified, REV is the revision in which the\n"
+ " URL is first looked up, and the default revision range is REV:1.\n"
+ " If REV is not specified, the default revision range is BASE:1,\n"
+ " since the URL might not exist in the HEAD revision.\n"
+ "\n"
+ " 2. Fetch the log messages for the PATHs (default: '.') under URL.\n"
+ " If specified, REV is the revision in which the URL is first\n"
+ " looked up, and the default revision range is REV:1; otherwise,\n"
+ " the URL is looked up in HEAD, and the default revision range is\n"
+ " HEAD:1.\n"
+ "\n"
+ " Multiple '-c' or '-r' options may be specified (but not a\n"
+ " combination of '-c' and '-r' options), and mixing of forward and\n"
+ " reverse ranges is allowed.\n"
+ "\n"
+ " With -v, also print all affected paths with each log message.\n"
+ " With -q, don't print the log message body itself (note that this is\n"
+ " compatible with -v).\n"
+ "\n"
+ " Each log message is printed just once, even if more than one of the\n"
+ " affected paths for that revision were explicitly requested. Logs\n"
+ " follow copy history by default. Use --stop-on-copy to disable this\n"
+ " behavior, which can be useful for determining branchpoints.\n"),
+ {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy,
+ 'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,
+ 'x',},
+ {{opt_with_revprop, N_("retrieve revision property ARG")},
+ {'c', N_("the change made in revision ARG")}} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_client", svn_client_version },
+ { "svn_wc", svn_wc_version },
+ { "svn_ra", svn_ra_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/*** Main. ***/
+
+/* Report and clear the error ERR, and return EXIT_FAILURE. */
+#define EXIT_ERROR(err) \
+ svn_cmdline_handle_exit_error(err, NULL, "svn: ")
+
+/* A redefinition of the public SVN_INT_ERR macro, that suppresses the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
+#undef SVN_INT_ERR
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return EXIT_ERROR(svn_err__temp); \
+ } while (0)
+
+static int
+sub_main(int argc, const char *argv[], apr_pool_t *pool)
+{
+ svn_error_t *err;
+ int opt_id;
+ apr_getopt_t *os;
+ svn_cl__opt_state_t opt_state = { 0, { 0 } };
+ svn_client_ctx_t *ctx;
+ apr_array_header_t *received_opts;
+ int i;
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ svn_cl__cmd_baton_t command_baton;
+ svn_auth_baton_t *ab;
+ svn_config_t *cfg_config;
+ svn_boolean_t descend = TRUE;
+ svn_boolean_t use_notifier = TRUE;
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ SVN_INT_ERR(check_lib_versions());
+
+#if defined(WIN32) || defined(__CYGWIN__)
+ /* Set the working copy administrative directory name. */
+ if (getenv("SVN_ASP_DOT_NET_HACK"))
+ {
+ SVN_INT_ERR(svn_wc_set_adm_dir("_svn", pool));
+ }
+#endif
+
+ /* Initialize the RA library. */
+ SVN_INT_ERR(svn_ra_initialize(pool));
+
+ /* Begin processing arguments. */
+ opt_state.start_revision.kind = svn_opt_revision_unspecified;
+ opt_state.end_revision.kind = svn_opt_revision_unspecified;
+ opt_state.revision_ranges =
+ apr_array_make(pool, 0, sizeof(svn_opt_revision_range_t *));
+ opt_state.depth = svn_depth_unknown;
+
+ /* No args? Show usage. */
+ if (argc <= 1)
+ {
+ svn_cl__help(NULL, NULL, pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Else, parse options. */
+ SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = 1;
+ while (1)
+ {
+ const char *opt_arg;
+ const char *utf8_opt_arg;
+
+ /* Parse the next option. */
+ apr_status_t apr_err = apr_getopt_long(os, svn_cl__options, &opt_id,
+ &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ svn_cl__help(NULL, NULL, pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id) {
+ case 'l':
+ {
+ err = svn_cstring_atoi(&opt_state.limit, opt_arg);
+ if (err)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Non-numeric limit argument given"));
+ return EXIT_ERROR(err);
+ }
+ if (opt_state.limit <= 0)
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Argument to --limit must be positive"));
+ return EXIT_ERROR(err);
+ }
+ }
+ break;
+ case 'c':
+ {
+ apr_array_header_t *change_revs =
+ svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, pool);
+
+ for (i = 0; i < change_revs->nelts; i++)
+ {
+ char *end;
+ svn_revnum_t changeno, changeno_end;
+ const char *change_str =
+ APR_ARRAY_IDX(change_revs, i, const char *);
+ const char *s = change_str;
+ svn_boolean_t is_negative;
+
+ /* Check for a leading minus to allow "-c -r42".
+ * The is_negative flag is used to handle "-c -42" and "-c -r42".
+ * The "-c r-42" case is handled by strtol() returning a
+ * negative number. */
+ is_negative = (*s == '-');
+ if (is_negative)
+ s++;
+
+ /* Allow any number of 'r's to prefix a revision number. */
+ while (*s == 'r')
+ s++;
+ changeno = changeno_end = strtol(s, &end, 10);
+ if (end != s && *end == '-')
+ {
+ if (changeno < 0 || is_negative)
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
+ NULL,
+ _("Negative number in range (%s)"
+ " not supported with -c"),
+ change_str);
+ return EXIT_ERROR(err);
+ }
+ s = end + 1;
+ while (*s == 'r')
+ s++;
+ changeno_end = strtol(s, &end, 10);
+ }
+ if (end == change_str || *end != '\0')
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric change argument (%s) "
+ "given to -c"), change_str);
+ return EXIT_ERROR(err);
+ }
+
+ if (changeno == 0)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("There is no change 0"));
+ return EXIT_ERROR(err);
+ }
+
+ if (is_negative)
+ changeno = -changeno;
+
+ /* Figure out the range:
+ -c N -> -r N-1:N
+ -c -N -> -r N:N-1
+ -c M-N -> -r M-1:N for M < N
+ -c M-N -> -r M:N-1 for M > N
+ -c -M-N -> error (too confusing/no valid use case)
+ */
+ if (changeno > 0)
+ {
+ if (changeno <= changeno_end)
+ changeno--;
+ else
+ changeno_end--;
+ }
+ else
+ {
+ changeno = -changeno;
+ changeno_end = changeno - 1;
+ }
+
+ opt_state.used_change_arg = TRUE;
+ APR_ARRAY_PUSH(opt_state.revision_ranges,
+ svn_opt_revision_range_t *)
+ = svn_opt__revision_range_from_revnums(changeno, changeno_end,
+ pool);
+ }
+ }
+ break;
+ case 'r':
+ opt_state.used_revision_arg = TRUE;
+ if (svn_opt_parse_revision_to_range(opt_state.revision_ranges,
+ opt_arg, pool) != 0)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ err = svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision argument '%s'"),
+ utf8_opt_arg);
+ return EXIT_ERROR(err);
+ }
+ break;
+ case 'v':
+ opt_state.verbose = TRUE;
+ break;
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+ case opt_targets:
+ {
+ svn_stringbuf_t *buffer, *buffer_utf8;
+
+ /* We need to convert to UTF-8 now, even before we divide
+ the targets into an array, because otherwise we wouldn't
+ know what delimiter to use for svn_cstring_split(). */
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
+ SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
+ opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
+ TRUE, pool);
+ }
+ break;
+ case 'N':
+ descend = FALSE;
+ break;
+ case opt_depth:
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (err)
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
+ _("Error converting depth "
+ "from locale to UTF-8")));
+ opt_state.depth = svn_depth_from_word(utf8_opt_arg);
+ if (opt_state.depth == svn_depth_unknown
+ || opt_state.depth == svn_depth_exclude)
+ {
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a valid depth; try "
+ "'empty', 'files', 'immediates', "
+ "or 'infinity'"),
+ utf8_opt_arg));
+ }
+ break;
+ case opt_version:
+ opt_state.version = TRUE;
+ break;
+ case opt_auth_username:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
+ opt_arg, pool));
+ break;
+ case opt_auth_password:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
+ opt_arg, pool));
+ break;
+ case opt_stop_on_copy:
+ opt_state.stop_on_copy = TRUE;
+ break;
+ case opt_strict:
+ opt_state.strict = TRUE;
+ break;
+ case opt_no_auth_cache:
+ opt_state.no_auth_cache = TRUE;
+ break;
+ case opt_non_interactive:
+ opt_state.non_interactive = TRUE;
+ break;
+ case opt_trust_server_cert:
+ opt_state.trust_server_cert = TRUE;
+ break;
+ case 'x':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.extensions,
+ opt_arg, pool));
+ break;
+ case opt_config_dir:
+ {
+ const char *path_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool));
+ opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool);
+ }
+ break;
+ case opt_config_options:
+ if (!opt_state.config_options)
+ opt_state.config_options =
+ apr_array_make(pool, 1,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
+ opt_arg, pool));
+ break;
+ case opt_with_all_revprops:
+ /* If --with-all-revprops is specified along with one or more
+ * --with-revprops options, --with-all-revprops takes precedence. */
+ opt_state.all_revprops = TRUE;
+ break;
+ case opt_with_no_revprops:
+ opt_state.no_revprops = TRUE;
+ break;
+ case opt_with_revprop:
+ SVN_INT_ERR(svn_opt_parse_revprop(&opt_state.revprop_table,
+ opt_arg, pool));
+ break;
+ case 'g':
+ opt_state.use_merge_history = TRUE;
+ break;
+ default:
+ /* Hmmm. Perhaps this would be a good place to squirrel away
+ opts that commands like svn diff might need. Hmmm indeed. */
+ break;
+ }
+ }
+
+ /* ### This really belongs in libsvn_client. The trouble is,
+ there's no one place there to run it from, no
+ svn_client_init(). We'd have to add it to all the public
+ functions that a client might call. It's unmaintainable to do
+ initialization from within libsvn_client itself, but it seems
+ burdensome to demand that all clients call svn_client_init()
+ before calling any other libsvn_client function... On the other
+ hand, the alternative is effectively to demand that they call
+ svn_config_ensure() instead, so maybe we should have a generic
+ init function anyway. Thoughts? */
+ SVN_INT_ERR(svn_config_ensure(opt_state.config_dir, pool));
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is svn_cl__help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", svn_cl__help, {0}, "",
+ {opt_version, /* must accept its own option */
+ 'q', /* brief output */
+ 'v', /* verbose output */
+ opt_config_dir /* all commands accept this */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Subcommand argument required\n")));
+ svn_cl__help(NULL, NULL, pool);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table,
+ first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
+ first_arg, pool));
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Unknown command: '%s'\n"),
+ first_arg_utf8));
+ svn_cl__help(NULL, NULL, pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id,
+ svn_cl__global_options))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svn_cl__options,
+ subcommand, pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ svn_cl__help(NULL, NULL, pool);
+ else
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svn-bench help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Only merge and log support multiple revisions/revision ranges. */
+ if (subcommand->cmd_func != svn_cl__null_log)
+ {
+ if (opt_state.revision_ranges->nelts > 1)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments "
+ "encountered; can't specify -c twice, "
+ "or both -c and -r"));
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* Disallow simultaneous use of both --with-all-revprops and
+ --with-no-revprops. */
+ if (opt_state.all_revprops && opt_state.no_revprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--with-all-revprops and --with-no-revprops "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Disallow simultaneous use of both --with-revprop and
+ --with-no-revprops. */
+ if (opt_state.revprop_table && opt_state.no_revprops)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--with-revprop and --with-no-revprops "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* --trust-server-cert can only be used with --non-interactive */
+ if (opt_state.trust_server_cert && !opt_state.non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return EXIT_ERROR(err);
+ }
+
+ /* Ensure that 'revision_ranges' has at least one item, and make
+ 'start_revision' and 'end_revision' match that item. */
+ if (opt_state.revision_ranges->nelts == 0)
+ {
+ svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
+ range->start.kind = svn_opt_revision_unspecified;
+ range->end.kind = svn_opt_revision_unspecified;
+ APR_ARRAY_PUSH(opt_state.revision_ranges,
+ svn_opt_revision_range_t *) = range;
+ }
+ opt_state.start_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
+ svn_opt_revision_range_t *)->start;
+ opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
+ svn_opt_revision_range_t *)->end;
+
+ /* Create a client context object. */
+ command_baton.opt_state = &opt_state;
+ SVN_INT_ERR(svn_client_create_context2(&ctx, NULL, pool));
+ command_baton.ctx = ctx;
+
+ /* Only a few commands can accept a revision range; the rest can take at
+ most one revision number. */
+ if (subcommand->cmd_func != svn_cl__null_log)
+ {
+ if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* -N has a different meaning depending on the command */
+ if (descend == FALSE)
+ opt_state.depth = svn_depth_files;
+
+ err = svn_config_get_config(&(ctx->config),
+ opt_state.config_dir, pool);
+ if (err)
+ {
+ /* Fallback to default config if the config directory isn't readable
+ or is not a directory. */
+ if (APR_STATUS_IS_EACCES(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ }
+ else
+ return EXIT_ERROR(err);
+ }
+
+ cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+
+ /* Update the options in the config */
+ if (opt_state.config_options)
+ {
+ svn_error_clear(
+ svn_cmdline__apply_config_options(ctx->config,
+ opt_state.config_options,
+ "svn: ", "--config-option"));
+ }
+
+ /* Set up the notifier.
+
+ In general, we use it any time we aren't in --quiet mode. 'svn
+ status' is unique, though, in that we don't want it in --quiet mode
+ unless we're also in --verbose mode. When in --xml mode,
+ though, we never want it. */
+ if (opt_state.quiet)
+ use_notifier = FALSE;
+ if (use_notifier)
+ {
+ SVN_INT_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
+ pool));
+ }
+
+ /* Set up our cancellation support. */
+ ctx->cancel_func = svn_cl__check_cancel;
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Set up Authentication stuff. */
+ SVN_INT_ERR(svn_cmdline_create_auth_baton(&ab,
+ opt_state.non_interactive,
+ opt_state.auth_username,
+ opt_state.auth_password,
+ opt_state.config_dir,
+ opt_state.no_auth_cache,
+ opt_state.trust_server_cert,
+ cfg_config,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ pool));
+
+ ctx->auth_baton = ab;
+
+ /* The new svn behavior is to postpone everything until after the operation
+ completed */
+ ctx->conflict_func = NULL;
+ ctx->conflict_baton = NULL;
+ ctx->conflict_func2 = NULL;
+ ctx->conflict_baton2 = NULL;
+
+ /* And now we finally run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &command_baton, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(
+ err, apr_psprintf(pool,
+ _("Try 'svn-bench help %s' for more information"),
+ subcommand->name));
+ }
+ if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Please see the 'svn upgrade' command"));
+ }
+
+ /* Tell the user about 'svn cleanup' if any error on the stack
+ was about locked working copies. */
+ if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
+ {
+ err = svn_error_quick_wrap(
+ err, _("Run 'svn cleanup' to remove locks "
+ "(type 'svn help cleanup' for details)"));
+ }
+
+ return EXIT_ERROR(err);
+ }
+ else
+ {
+ /* Ensure that stdout is flushed, so the user will see any write errors.
+ This makes sure that output is not silently lost. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+
+ return EXIT_SUCCESS;
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ int exit_code;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ exit_code = sub_main(argc, argv, pool);
+
+ svn_pool_destroy(pool);
+ return exit_code;
+}
diff --git a/tools/client-side/svn-bench/notify.c b/tools/client-side/svn-bench/notify.c
new file mode 100644
index 0000000..198760b
--- /dev/null
+++ b/tools/client-side/svn-bench/notify.c
@@ -0,0 +1,1045 @@
+/*
+ * notify.c: feedback handlers for cmdline client.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/* Baton for notify and friends. */
+struct notify_baton
+{
+ svn_boolean_t received_some_change;
+ svn_boolean_t is_checkout;
+ svn_boolean_t is_export;
+ svn_boolean_t is_wc_to_repos_copy;
+ svn_boolean_t sent_first_txdelta;
+ svn_boolean_t in_external;
+ svn_boolean_t had_print_error; /* Used to not keep printing error messages
+ when we've already had one print error. */
+
+ /* Conflict stats for update and merge. */
+ unsigned int text_conflicts;
+ unsigned int prop_conflicts;
+ unsigned int tree_conflicts;
+ unsigned int skipped_paths;
+ apr_hash_t *conflicted_paths;
+
+ /* The cwd, for use in decomposing absolute paths. */
+ const char *path_prefix;
+};
+
+
+/* Add a conflicted path to the list of conflicted paths stored
+ * in the notify baton. */
+static void
+add_conflicted_path(struct notify_baton *nb, const char *path)
+{
+ apr_hash_set(nb->conflicted_paths,
+ apr_pstrdup(apr_hash_pool_get(nb->conflicted_paths), path),
+ APR_HASH_KEY_STRING, "");
+}
+
+/* This implements `svn_wc_notify_func2_t'.
+ * NOTE: This function can't fail, so we just ignore any print errors. */
+static void
+notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
+{
+ struct notify_baton *nb = baton;
+ char statchar_buf[5] = " ";
+ const char *path_local;
+ svn_error_t *err;
+
+ if (n->url)
+ path_local = n->url;
+ else
+ {
+ if (n->path_prefix)
+ path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path,
+ pool);
+ else /* skip nb->path_prefix, if it's non-null */
+ path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path,
+ pool);
+ }
+
+ switch (n->action)
+ {
+ case svn_wc_notify_skip:
+ nb->skipped_paths++;
+ if (n->content_state == svn_wc_notify_state_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped missing target: '%s'\n"),
+ path_local)))
+ goto print_error;
+ }
+ else if (n->content_state == svn_wc_notify_state_source_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped target: '%s' -- copy-source is missing\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped '%s'\n"), path_local)))
+ goto print_error;
+ }
+ break;
+ case svn_wc_notify_update_skip_obstruction:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- An obstructing working copy was found\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_working_only:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Has no versioned parent\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_access_denied:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Access denied\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_skip_conflicted:
+ nb->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Node remains in conflict\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_delete:
+ case svn_wc_notify_exclude:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n", path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_broken_lock:
+ if ((err = svn_cmdline_printf(pool, "B %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_external_removed:
+ nb->received_some_change = TRUE;
+ if (n->err && n->err->message)
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s': %s\n",
+ path_local, n->err->message)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s'\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_left_local_modifications:
+ if ((err = svn_cmdline_printf(pool, "Left local modifications as '%s'\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_replace:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "R %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_add:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ if ((err = svn_cmdline_printf(pool, "C %s\n", path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n", path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_exists:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[0] = 'C';
+ }
+ else
+ statchar_buf[0] = 'E';
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ nb->prop_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+
+ if ((err = svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_restore:
+ if ((err = svn_cmdline_printf(pool, _("Restored '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revert:
+ if ((err = svn_cmdline_printf(pool, _("Reverted '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_revert:
+ if (( err = svn_cmdline_printf(pool, _("Failed to revert '%s' -- "
+ "try updating instead.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_resolved:
+ if ((err = svn_cmdline_printf(pool,
+ _("Resolved conflicted state of '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_add:
+ /* We *should* only get the MIME_TYPE if PATH is a file. If we
+ do get it, and the mime-type is not textual, note that this
+ is a binary addition. */
+ if (n->mime_type && (svn_mime_type_is_binary(n->mime_type)))
+ {
+ if ((err = svn_cmdline_printf(pool, "A (bin) %s\n",
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch:
+ {
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ nb->prop_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_patch_applied_hunk:
+ nb->received_some_change = TRUE;
+ if (n->hunk_original_start != n->hunk_matched_line)
+ {
+ apr_uint64_t off;
+ const char *s;
+ const char *minus;
+
+ if (n->hunk_matched_line > n->hunk_original_start)
+ {
+ off = n->hunk_matched_line - n->hunk_original_start;
+ minus = "";
+ }
+ else
+ {
+ off = n->hunk_original_start - n->hunk_matched_line;
+ minus = "-";
+ }
+
+ /* ### We're creating the localized strings without
+ * ### APR_INT64_T_FMT since it isn't translator-friendly */
+ if (n->hunk_fuzz)
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz,
+ n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ else
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT" (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT"\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ }
+ else if (n->hunk_fuzz)
+ {
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with fuzz %lu (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with fuzz %lu\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz);
+ if (err)
+ goto print_error;
+
+ }
+ break;
+
+ case svn_wc_notify_patch_rejected_hunk:
+ nb->received_some_change = TRUE;
+
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "## -%lu,%lu +%lu,%lu ## (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "@@ -%lu,%lu +%lu,%lu @@\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch_hunk_already_applied:
+ nb->received_some_change = TRUE;
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "## -%lu,%lu +%lu,%lu ## "
+ "already applied (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "@@ -%lu,%lu +%lu,%lu @@ "
+ "already applied\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_update:
+ case svn_wc_notify_merge_record_info:
+ {
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ nb->text_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ nb->prop_conflicts++;
+ add_conflicted_path(nb, n->path);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (n->lock_state == svn_wc_notify_lock_state_unlocked)
+ statchar_buf[2] = 'B';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ nb->received_some_change = TRUE;
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' '
+ || statchar_buf[2] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_update_external:
+ /* Remember that we're now "inside" an externals definition. */
+ nb->in_external = TRUE;
+
+ /* Currently this is used for checkouts and switches too. If we
+ want different output, we'll have to add new actions. */
+ if ((err = svn_cmdline_printf(pool,
+ _("\nFetching external item into '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_external:
+ /* If we are currently inside the handling of an externals
+ definition, then we can simply present n->err as a warning
+ and feel confident that after this, we aren't handling that
+ externals definition any longer. */
+ if (nb->in_external)
+ {
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ /* Otherwise, we'll just print two warnings. Why? Because
+ svn_handle_warning2() only shows the single "best message",
+ but we have two pretty important ones: that the external at
+ '/some/path' didn't pan out, and then the more specific
+ reason why (from n->err). */
+ else
+ {
+ svn_error_t *warn_err =
+ svn_error_createf(SVN_ERR_BASE, NULL,
+ _("Error handling externals definition for '%s':"),
+ path_local);
+ svn_handle_warning2(stderr, warn_err, "svn: ");
+ svn_error_clear(warn_err);
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ }
+ break;
+
+ case svn_wc_notify_update_started:
+ if (! (nb->in_external ||
+ nb->is_checkout ||
+ nb->is_export))
+ {
+ if ((err = svn_cmdline_printf(pool, _("Updating '%s':\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_update_completed:
+ {
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Exported external at revision %ld.\n")
+ : _("Exported revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Checked out external at revision %ld.\n")
+ : _("Checked out revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if (nb->received_some_change)
+ {
+ nb->received_some_change = FALSE;
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Updated external to revision %ld.\n")
+ : _("Updated to revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External at revision %ld.\n")
+ : _("At revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ }
+ }
+ else /* no revision */
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External export complete.\n")
+ : _("Export complete.\n"))))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External checkout complete.\n")
+ : _("Checkout complete.\n"))))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External update complete.\n")
+ : _("Update complete.\n"))))
+ goto print_error;
+ }
+ }
+ }
+
+ if (nb->in_external)
+ {
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_status_external:
+ if ((err = svn_cmdline_printf
+ (pool, _("\nPerforming status on external item at '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_status_completed:
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ if ((err = svn_cmdline_printf(pool,
+ _("Status against revision: %6ld\n"),
+ n->revision)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_modified:
+ /* xgettext: Align the %s's on this and the following 4 messages */
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Sending copy of %s\n")
+ : _("Sending %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_added:
+ case svn_wc_notify_commit_copied:
+ if (n->mime_type && svn_mime_type_is_binary(n->mime_type))
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of (bin) %s\n")
+ : _("Adding (bin) %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of %s\n")
+ : _("Adding %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_commit_deleted:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Deleting copy of %s\n")
+ : _("Deleting %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_replaced:
+ case svn_wc_notify_commit_copied_replaced:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Replacing copy of %s\n")
+ : _("Replacing %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_postfix_txdelta:
+ if (! nb->sent_first_txdelta)
+ {
+ nb->sent_first_txdelta = TRUE;
+ if ((err = svn_cmdline_printf(pool,
+ _("Transmitting file data "))))
+ goto print_error;
+ }
+
+ if ((err = svn_cmdline_printf(pool, ".")))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_locked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
+ path_local, n->lock->owner)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_unlocked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' unlocked.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_lock:
+ case svn_wc_notify_failed_unlock:
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ break;
+
+ case svn_wc_notify_changelist_set:
+ if ((err = svn_cmdline_printf(pool, "A [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_changelist_clear:
+ case svn_wc_notify_changelist_moved:
+ if ((err = svn_cmdline_printf(pool,
+ "D [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool, _("--- Merging r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld through r%ld "
+ "into '%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_record_info_begin:
+ if (!n->merge_range)
+ {
+ err = svn_cmdline_printf(pool,
+ _("--- Recording mergeinfo for merge "
+ "between repository URLs into '%s':\n"),
+ path_local);
+ }
+ else
+ {
+ if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1, n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start, n->merge_range->end + 1, path_local);
+ }
+
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_elide_info:
+ if ((err = svn_cmdline_printf(pool,
+ _("--- Eliding mergeinfo from '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_foreign_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "foreign repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_tree_conflict:
+ nb->tree_conflicts++;
+ add_conflicted_path(nb, n->path);
+ if ((err = svn_cmdline_printf(pool, " C %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_add:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " A %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_update:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " U %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " D %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_modified:
+ case svn_wc_notify_property_added:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on '%s'\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from '%s'.\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted_nonexistent:
+ err = svn_cmdline_printf(pool,
+ _("Attempting to delete nonexistent "
+ "property '%s' on '%s'\n"), n->prop_name,
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_set:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_upgraded_path:
+ err = svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_url_redirect:
+ err = svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"),
+ n->url);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_path_nonexistent:
+ err = svn_cmdline_printf(pool, _("'%s' is not under version control"),
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_conflict_resolver_starting:
+ /* Once all operations invoke the interactive conflict resolution after
+ * they've completed, we can run svn_cl__print_conflict_stats() here. */
+ break;
+
+ case svn_wc_notify_conflict_resolver_done:
+ break;
+
+ default:
+ break;
+ }
+
+ if ((err = svn_cmdline_fflush(stdout)))
+ goto print_error;
+
+ return;
+
+ print_error:
+ /* If we had no errors before, print this error to stderr. Else, don't print
+ anything. The user already knows there were some output errors,
+ so there is no point in flooding her with an error per notification. */
+ if (!nb->had_print_error)
+ {
+ nb->had_print_error = TRUE;
+ /* Issue #3014:
+ * Don't print anything on broken pipes. The pipe was likely
+ * closed by the process at the other end. We expect that
+ * process to perform error reporting as necessary.
+ *
+ * ### This assumes that there is only one error in a chain for
+ * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+ if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ }
+ svn_error_clear(err);
+}
+
+
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ apr_pool_t *pool)
+{
+ struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+
+ nb->received_some_change = FALSE;
+ nb->sent_first_txdelta = FALSE;
+ nb->is_checkout = FALSE;
+ nb->is_export = FALSE;
+ nb->is_wc_to_repos_copy = FALSE;
+ nb->in_external = FALSE;
+ nb->had_print_error = FALSE;
+ nb->text_conflicts = 0;
+ nb->prop_conflicts = 0;
+ nb->tree_conflicts = 0;
+ nb->skipped_paths = 0;
+ nb->conflicted_paths = apr_hash_make(pool);
+ SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
+
+ *notify_func_p = notify;
+ *notify_baton_p = nb;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_export = TRUE;
+ return SVN_NO_ERROR;
+}
diff --git a/tools/client-side/svn-bench/null-export-cmd.c b/tools/client-side/svn-bench/null-export-cmd.c
new file mode 100644
index 0000000..73992ba
--- /dev/null
+++ b/tools/client-side/svn-bench/null-export-cmd.c
@@ -0,0 +1,344 @@
+/*
+ * export-cmd.c -- Subversion export command
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+#include "private/svn_client_private.h"
+
+/*** The export editor code. ***/
+
+/* ---------------------------------------------------------------------- */
+
+/*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
+
+typedef struct edit_baton_t
+{
+ apr_int64_t file_count;
+ apr_int64_t dir_count;
+ apr_int64_t byte_count;
+ apr_int64_t prop_count;
+ apr_int64_t prop_byte_count;
+} edit_baton_t;
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+/* Just ensure that the main export directory exists. */
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ *root_baton = edit_baton;
+ return SVN_NO_ERROR;
+}
+
+
+/* Ensure the directory exists, and send feedback. */
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ edit_baton_t *eb = parent_baton;
+ eb->dir_count++;
+
+ *baton = parent_baton;
+ return SVN_NO_ERROR;
+}
+
+
+/* Build a file baton. */
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *pool,
+ void **baton)
+{
+ edit_baton_t *eb = parent_baton;
+ eb->file_count++;
+
+ *baton = parent_baton;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ edit_baton_t *eb = baton;
+ if (window != NULL)
+ eb->byte_count += window->tview_len;
+
+ return SVN_NO_ERROR;
+}
+
+/* Write incoming data into the tmpfile stream */
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ *handler_baton = file_baton;
+ *handler = window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = file_baton;
+ eb->prop_count++;
+ eb->prop_byte_count += value->len;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = dir_baton;
+ eb->prop_count++;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public Interfaces ***/
+
+static svn_error_t *
+bench_null_export(svn_revnum_t *result_rev,
+ const char *from_path_or_url,
+ svn_opt_revision_t *peg_revision,
+ svn_opt_revision_t *revision,
+ svn_depth_t depth,
+ void *baton,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
+ svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
+
+ SVN_ERR_ASSERT(peg_revision != NULL);
+ SVN_ERR_ASSERT(revision != NULL);
+
+ if (peg_revision->kind == svn_opt_revision_unspecified)
+ peg_revision->kind = svn_path_is_url(from_path_or_url)
+ ? svn_opt_revision_head
+ : svn_opt_revision_working;
+
+ if (revision->kind == svn_opt_revision_unspecified)
+ revision = peg_revision;
+
+ if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
+ {
+ svn_client__pathrev_t *loc;
+ svn_ra_session_t *ra_session;
+ svn_node_kind_t kind;
+
+ /* Get the RA connection. */
+ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
+ from_path_or_url, NULL,
+ peg_revision,
+ revision, ctx, pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
+
+ if (kind == svn_node_file)
+ {
+ apr_hash_t *props;
+
+ /* Since you cannot actually root an editor at a file, we
+ * manually drive a few functions of our editor. */
+
+ /* Step outside the editor-likeness for a moment, to actually talk
+ * to the repository. */
+ /* ### note: the stream will not be closed */
+ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
+ svn_stream_empty(pool),
+ NULL, &props, pool));
+ }
+ else if (kind == svn_node_dir)
+ {
+ void *edit_baton = NULL;
+ const svn_delta_editor_t *export_editor = NULL;
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+
+ svn_delta_editor_t *editor = svn_delta_default_editor(pool);
+
+ editor->set_target_revision = set_target_revision;
+ editor->open_root = open_root;
+ editor->add_directory = add_directory;
+ editor->add_file = add_file;
+ editor->apply_textdelta = apply_textdelta;
+ editor->close_file = close_file;
+ editor->change_file_prop = change_file_prop;
+ editor->change_dir_prop = change_dir_prop;
+
+ /* for ra_svn, we don't need an editior in quiet mode */
+ if (!quiet || strncmp(loc->repos_root_url, "svn:", 4))
+ SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
+ ctx->cancel_baton,
+ editor,
+ baton,
+ &export_editor,
+ &edit_baton,
+ pool));
+
+ /* Manufacture a basic 'report' to the update reporter. */
+ SVN_ERR(svn_ra_do_update2(ra_session,
+ &reporter, &report_baton,
+ loc->rev,
+ "", /* no sub-target */
+ depth,
+ FALSE, /* don't want copyfrom-args */
+ export_editor, edit_baton, pool));
+
+ SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
+ /* Depth is irrelevant, as we're
+ passing start_empty=TRUE anyway. */
+ svn_depth_infinity,
+ TRUE, /* "help, my dir is empty!" */
+ NULL, pool));
+
+ SVN_ERR(reporter->finish_report(report_baton, pool));
+ }
+ else if (kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' doesn't exist"),
+ from_path_or_url);
+ }
+ /* kind == svn_node_unknown not handled */
+ }
+
+
+ if (result_rev)
+ *result_rev = edit_revision;
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__null_export(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *from;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+ svn_opt_revision_t peg_revision;
+ const char *truefrom;
+ edit_baton_t eb = { 0 };
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We want exactly 1 or 2 targets for this subcommand. */
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ /* The first target is the `from' path. */
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+
+ /* Get the peg revision if present. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool));
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ /* Do the export. */
+ err = bench_null_export(NULL, truefrom, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ &eb,
+ ctx, opt_state->quiet, pool);
+
+ if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s directories\n"
+ "%15s files\n"
+ "%15s bytes in files\n"
+ "%15s properties\n"
+ "%15s bytes in properties\n"),
+ svn__ui64toa_sep(eb.dir_count, ',', pool),
+ svn__ui64toa_sep(eb.file_count, ',', pool),
+ svn__ui64toa_sep(eb.byte_count, ',', pool),
+ svn__ui64toa_sep(eb.prop_count, ',', pool),
+ svn__ui64toa_sep(eb.prop_byte_count, ',', pool)));
+
+ return svn_error_trace(err);
+}
diff --git a/tools/client-side/svn-bench/null-list-cmd.c b/tools/client-side/svn-bench/null-list-cmd.c
new file mode 100644
index 0000000..c6f750a
--- /dev/null
+++ b/tools/client-side/svn-bench/null-list-cmd.c
@@ -0,0 +1,166 @@
+/*
+ * list-cmd.c -- list a URL
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_opt.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
+
+
+/* Baton used when printing directory entries. */
+struct print_baton {
+ svn_boolean_t verbose;
+ apr_int64_t directories;
+ apr_int64_t files;
+ apr_int64_t locks;
+ svn_client_ctx_t *ctx;
+};
+
+/* This implements the svn_client_list_func_t API, printing a single
+ directory entry in text format. */
+static svn_error_t *
+print_dirent(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ apr_pool_t *pool)
+{
+ struct print_baton *pb = baton;
+
+ if (pb->ctx->cancel_func)
+ SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
+
+ if (dirent->kind == svn_node_dir)
+ pb->directories++;
+ if (dirent->kind == svn_node_file)
+ pb->files++;
+ if (lock)
+ pb->locks++;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__null_list(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_uint32_t dirent_fields;
+ struct print_baton pb = { FALSE };
+ svn_boolean_t seen_nonexistent_target = FALSE;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ if (opt_state->verbose)
+ dirent_fields = SVN_DIRENT_ALL;
+ else
+ dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */
+
+ pb.ctx = ctx;
+ pb.verbose = opt_state->verbose;
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_immediates;
+
+ /* For each target, try to list it. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ err = svn_client_list2(truepath, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ dirent_fields,
+ opt_state->verbose,
+ print_dirent,
+ &pb, ctx, subpool);
+
+ if (err)
+ {
+ /* If one of the targets is a non-existent URL or wc-entry,
+ don't bail out. Just warn and move on to the next target. */
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ svn_handle_warning2(stderr, err, "svn-bench: ");
+ else
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+ else if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s directories\n"
+ "%15s files\n"
+ "%15s locks\n"),
+ svn__ui64toa_sep(pb.directories, ',', pool),
+ svn__ui64toa_sep(pb.files, ',', pool),
+ svn__ui64toa_sep(pb.locks, ',', pool)));
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (seen_nonexistent_target)
+ return svn_error_create(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not list all targets because some targets don't exist"));
+ else
+ return SVN_NO_ERROR;
+}
diff --git a/tools/client-side/svn-bench/null-log-cmd.c b/tools/client-side/svn-bench/null-log-cmd.c
new file mode 100644
index 0000000..0243d3d
--- /dev/null
+++ b/tools/client-side/svn-bench/null-log-cmd.c
@@ -0,0 +1,242 @@
+/*
+ * log-cmd.c -- Display log messages
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_STDIO
+#include <apr_want.h>
+
+#include "svn_cmdline.h"
+#include "svn_compat.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
+
+/*** Code. ***/
+
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
+struct log_receiver_baton
+{
+ /* Client context. */
+ svn_client_ctx_t *ctx;
+
+ /* Level of merge revision nesting */
+ apr_size_t merge_depth;
+
+ /* collect counters? */
+ svn_boolean_t quiet;
+
+ /* total revision counters */
+ apr_int64_t revisions;
+ apr_int64_t changes;
+ apr_int64_t message_lines;
+
+ /* part that came from merges */
+ apr_int64_t merges;
+ apr_int64_t merged_revs;
+ apr_int64_t merged_changes;
+ apr_int64_t merged_message_lines;
+};
+
+
+/* Implement `svn_log_entry_receiver_t', printing the logs in
+ * a human-readable and machine-parseable format.
+ *
+ * BATON is of type `struct log_receiver_baton'.
+ */
+static svn_error_t *
+log_entry_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lb = baton;
+ const char *author;
+ const char *date;
+ const char *message;
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ lb->merge_depth--;
+ return SVN_NO_ERROR;
+ }
+
+ /* if we don't want counters, we are done */
+ if (lb->quiet)
+ return SVN_NO_ERROR;
+
+ /* extract the message and do all the other counting */
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+ if (log_entry->revision == 0 && message == NULL)
+ return SVN_NO_ERROR;
+
+ lb->revisions++;
+ if (lb->merge_depth)
+ lb->merged_revs++;
+
+ if (message != NULL)
+ {
+ int count = svn_cstring_count_newlines(message) + 1;
+ lb->message_lines += count;
+ if (lb->merge_depth)
+ lb->merged_message_lines += count;
+ }
+
+ if (log_entry->changed_paths2)
+ {
+ unsigned count = apr_hash_count(log_entry->changed_paths2);
+ lb->changes += count;
+ if (lb->merge_depth)
+ lb->merged_changes += count;
+ }
+
+ if (log_entry->has_children)
+ {
+ lb->merge_depth++;
+ lb->merges++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__null_log(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ struct log_receiver_baton lb = { ctx };
+ const char *target;
+ int i;
+ apr_array_header_t *revprops;
+ svn_opt_revision_t target_peg_revision;
+ const char *target_path_or_url;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ /* Determine if they really want a two-revision range. */
+ if (opt_state->used_change_arg)
+ {
+ if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
+ {
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("-c and -r are mutually exclusive"));
+ }
+ for (i = 0; i < opt_state->revision_ranges->nelts; i++)
+ {
+ svn_opt_revision_range_t *range;
+ range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
+ svn_opt_revision_range_t *);
+ if (range->start.value.number < range->end.value.number)
+ range->start.value.number++;
+ else
+ range->end.value.number++;
+ }
+ }
+
+ /* Parse the first target into path-or-url and peg revision. */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(svn_opt_parse_path(&target_peg_revision, &target_path_or_url,
+ target, pool));
+ if (target_peg_revision.kind == svn_opt_revision_unspecified)
+ target_peg_revision.kind = (svn_path_is_url(target)
+ ? svn_opt_revision_head
+ : svn_opt_revision_working);
+ APR_ARRAY_IDX(targets, 0, const char *) = target_path_or_url;
+
+ if (svn_path_is_url(target))
+ {
+ for (i = 1; i < targets->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(targets, i, const char *);
+
+ if (svn_path_is_url(target) || target[0] == '/')
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only relative paths can be specified"
+ " after a URL for 'svn-bench log', "
+ "but '%s' is not a relative path"),
+ target);
+ }
+ }
+
+ lb.quiet = opt_state->quiet;
+
+ revprops = apr_array_make(pool, 3, sizeof(char *));
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ if (!opt_state->quiet)
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+ SVN_ERR(svn_client_log5(targets,
+ &target_peg_revision,
+ opt_state->revision_ranges,
+ opt_state->limit,
+ opt_state->verbose,
+ opt_state->stop_on_copy,
+ opt_state->use_merge_history,
+ revprops,
+ log_entry_receiver,
+ &lb,
+ ctx,
+ pool));
+
+ if (!opt_state->quiet)
+ {
+ if (opt_state->use_merge_history)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s revisions, %15s merged in %s merges\n"
+ "%15s msg lines, %15s in merged revisions\n"
+ "%15s changes, %15s in merged revisions\n"),
+ svn__ui64toa_sep(lb.revisions, ',', pool),
+ svn__ui64toa_sep(lb.merged_revs, ',', pool),
+ svn__ui64toa_sep(lb.merges, ',', pool),
+ svn__ui64toa_sep(lb.message_lines, ',', pool),
+ svn__ui64toa_sep(lb.merged_message_lines, ',', pool),
+ svn__ui64toa_sep(lb.changes, ',', pool),
+ svn__ui64toa_sep(lb.merged_changes, ',', pool)));
+ else
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("%15s revisions\n"
+ "%15s msg lines\n"
+ "%15s changes\n"),
+ svn__ui64toa_sep(lb.revisions, ',', pool),
+ svn__ui64toa_sep(lb.message_lines, ',', pool),
+ svn__ui64toa_sep(lb.changes, ',', pool)));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/tools/client-side/svn-bench/util.c b/tools/client-side/svn-bench/util.c
new file mode 100644
index 0000000..d7fc4c4
--- /dev/null
+++ b/tools/client-side/svn-bench/util.c
@@ -0,0 +1,92 @@
+/*
+ * util.c: Subversion command line client utility functions. Any
+ * functions that need to be shared across subcommands should be put
+ * in here.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "svn_private_config.h"
+#include "svn_error.h"
+#include "svn_path.h"
+
+#include "cl.h"
+
+
+
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_last_origpath_on_truepath_collision,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_client_args_to_target_array2(targets,
+ os,
+ known_targets,
+ ctx,
+ keep_last_origpath_on_truepath_collision,
+ pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
+ svn_error_clear(err);
+ }
+ else
+ return svn_error_trace(err);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target)
+{
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a local path"), target);
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *relpath = NULL;
+
+ if (parent_path)
+ relpath = svn_dirent_skip_ancestor(parent_path, path);
+
+ return svn_dirent_local_style(relpath ? relpath : path, pool);
+}
+
diff --git a/tools/dev/benchmarks/suite1/benchmark.py b/tools/dev/benchmarks/suite1/benchmark.py
index ea543d2..06e657e 100755
--- a/tools/dev/benchmarks/suite1/benchmark.py
+++ b/tools/dev/benchmarks/suite1/benchmark.py
@@ -17,25 +17,52 @@
# specific language governing permissions and limitations
# under the License.
-"""Usage: benchmark.py run|list|compare|show|chart ...
+"""Usage: benchmark.py run|list|compare|show|chart <selection> ...
+
+SELECTING TIMINGS -- B@R,LxS
+
+In the subcommands below, a timings selection consists of a string with up to
+four elements:
+ <branch>@<revision>,<levels>x<spread>
+abbreviated as:
+ B@R,LxS
+
+<branch> is a label of an svn branch, e.g. "1.7.x".
+<revision> is the last-changed-revision of above branch.
+<levels> is the number of directory levels created in the benchmark.
+<spread> is the number of child trees spreading off each dir level.
+
+<branch_name> and <revision> are simply used for labeling. Upon the actual
+test runs, you should enter labels matching the selected --svn-bin-dir.
+Later, you can select runs individually by using these labels.
+
+For <revision>, you can provide special keywords:
+- 'each' has the same effect as entering each available revision number that
+ is on record in the db in a separate timings selection.
+- 'last' is the same as 'each', but shows only the last 10 revisions. 'last'
+ can be combined with a number, e.g. 'last12'.
+
+For all subcommands except 'run', you can omit some or all of the elements of
+a timings selection to combine all available timings sets. Try that out with
+the 'list' subcommand.
+
+Examples:
+ benchmark.py run 1.7.x@12345,5x5
+ benchmark.py show trunk@12345
+ benchmark.py compare 1.7.0,1x100 trunk@each,1x100
+ benchmark.py chart compare 1.7.0,5x5 trunk@last12,5x5
+
RUN BENCHMARKS
- benchmark.py run <branch>@<revision>,<levels>x<spread> [N] [options]
+ benchmark.py run B@R,LxS [N] [options]
Test data is added to an sqlite database created automatically, by default
'benchmark.db' in the current working directory. To specify a different path,
use option -f <path_to_db>.
-<branch_name> is a label of the svn branch you're testing, e.g. "1.7.x".
-<revision> is the last-changed-revision of above branch.
-<levels> is the number of directory levels to create
-<spread> is the number of child trees spreading off each dir level
If <N> is provided, the run is repeated N times.
-<branch_name> and <revision> are simply used for later reference. You
-should enter labels matching the selected --svn-bin-dir.
-
<levels> and <spread> control the way the tested working copy is structured:
<levels>: number of directory levels to create.
<spread>: number of files and subdirectories created in each dir.
@@ -43,22 +70,21 @@
LIST WHAT IS ON RECORD
- benchmark.py list [ <branch>@<rev>,<levels>x<spread> ]
+ benchmark.py list [B@R,LxS]
Find entries in the database for the given constraints. Any arguments can
be omitted. (To select only a rev, start with a '@', like '@123'; to select
only spread, start with an 'x', like "x100".)
-Omit all args to get a listing of all available distinct entries.
+Call without arguments to get a listing of all available constraints.
COMPARE TIMINGS
- benchmark.py compare B@R,LxS B@R,LxS
+ benchmark.py compare B@R,LxS B@R,LxS [B@R,LxS [...]]
-Compare two kinds of timings (in text mode). Each B@R,LxS selects
-timings from branch, revision, WC-levels and -spread by the same labels as
-previously given for a 'run' call. Any elements can be omitted. For example:
+Compare any number of timings sets to the first provided set (in text mode).
+For example:
benchmark.py compare 1.7.0 trunk@1349903
Compare the total timings of all combined '1.7.0' branch runs to
all combined runs of 'trunk'-at-revision-1349903.
@@ -66,38 +92,38 @@
Same as above, but only compare the working copy types with 5 levels
and a spread of 5.
+Use the -c option to limit comparison to specific command names.
+
SHOW TIMINGS
- benchmark.py show <branch>@<rev>,<levels>x<spread>
+ benchmark.py show B@R,LxS [B@R,LxS [...]]
Print out a summary of the timings selected from the given constraints.
-Any arguments can be omitted (like for the 'list' command).
GENERATE CHARTS
benchmark.py chart compare B@R,LxS B@R,LxS [ B@R,LxS ... ]
-Produce a bar chart that compares any number of sets of timings. Timing sets
-are supplied by B@R,LxS arguments (i.e. <branch>@<rev>,<levels>x<spread> as
-provided for a 'run' call), where any number of elements may be omitted. The
-less constraints you supply, the more timings are included (try it out with
-the 'list' command). The first set is taken as a reference point for 100% and
-+0 seconds. Each following dataset produces a set of labeled bar charts.
-So, at least two constraint arguments must be provided.
+Produce a bar chart that compares any number of sets of timings. Like with
+the plain 'compare' command, the first set is taken as a reference point for
+100% and +-0 seconds. Each following dataset produces a set of labeled bar
+charts, grouped by svn command names. At least two timings sets must be
+provided.
-Use the -c option to limit charts to specific command names.
+Use the -c option to limit comparison to specific command names.
EXAMPLES
-# Run 3 benchmarks on svn 1.7.0. Timings are saved in benchmark.db.
+# Run 3 benchmarks on svn 1.7.0 with 5 dir levels and 5 files and subdirs for
+# each level (spread). Timings are saved in ./benchmark.db.
# Provide label '1.7.0' and its Last-Changed-Rev for later reference.
-# (You may also set your $PATH instead of using --svn-bin-dir.)
./benchmark.py run --svn-bin-dir ~/svn-prefix/1.7.0/bin 1.7.0@1181106,5x5 3
# Record 3 benchmark runs on trunk, again naming its Last-Changed-Rev.
+# (You may also set your $PATH instead of using --svn-bin-dir.)
./benchmark.py run --svn-bin-dir ~/svn-prefix/trunk/bin trunk@1352725,5x5 3
# Work with the results of above two runs
@@ -129,12 +155,18 @@
import shutil
import stat
import string
+from copy import copy
IGNORE_COMMANDS = ('--version', )
TOTAL_RUN = 'TOTAL RUN'
j = os.path.join
+def bail(msg=None):
+ if msg:
+ print msg
+ exit(1)
+
def time_str():
return time.strftime('%Y-%m-%d %H:%M:%S');
@@ -227,6 +259,7 @@
if self.levels: self.levels = int(self.levels)
if self.spread: self.spread = int(self.spread)
+ def label(self):
label_parts = []
if self.branch:
label_parts.append(self.branch)
@@ -240,12 +273,52 @@
if self.spread:
label_parts.append(RUN_KIND_SEPARATORS[2])
label_parts.append(str(self.spread))
- self.label = ''.join(label_parts)
+ return ''.join(label_parts)
def args(self):
return (self.branch, self.revision, self.levels, self.spread)
+def parse_timings_selections(db, *args):
+ run_kinds = []
+
+ for arg in args:
+ run_kind = RunKind(arg)
+
+ if run_kind.revision == 'each':
+ run_kind.revision = None
+ query = TimingQuery(db, run_kind)
+ for revision in query.get_sorted_revisions():
+ revision_run_kind = copy(run_kind)
+ revision_run_kind.revision = revision
+ run_kinds.append(revision_run_kind)
+ elif run_kind.revision and run_kind.revision.startswith('last'):
+ Nstr = run_kind.revision[4:]
+ if not Nstr:
+ N = 10
+ else:
+ N = int(Nstr)
+ run_kind.revision = None
+ query = TimingQuery(db, run_kind)
+ for revision in query.get_sorted_revisions()[-N:]:
+ revision_run_kind = copy(run_kind)
+ revision_run_kind.revision = revision
+ run_kinds.append(revision_run_kind)
+ else:
+ run_kinds.append(run_kind)
+
+ return run_kinds
+
+def parse_one_timing_selection(db, *args):
+ run_kinds = parse_timings_selections(db, *args)
+ if len(run_kinds) != 1:
+ bail("I need exactly one timings identifier, not '%s'"
+ % (' '.join(*args)))
+ return run_kinds[0]
+
+
+
+
PATHNAME_VALID_CHARS = "-_.,@%s%s" % (string.ascii_letters, string.digits)
def filesystem_safe_string(s):
return ''.join(c for c in s if c in PATHNAME_VALID_CHARS)
@@ -436,15 +509,19 @@
AND b.batch_id = r.batch_id
AND r.aborted = 0
"""
- self.append_constraint('k', 'branch', run_kind.branch)
- self.append_constraint('k', 'revision', run_kind.revision)
- self.append_constraint('k', 'wc_levels', run_kind.levels)
- self.append_constraint('k', 'wc_spread', run_kind.spread)
- self.label = run_kind.label
+ self.append_constraint('k.branch', run_kind.branch)
+ self.each_revision = False
+ if run_kind.revision == 'each':
+ self.each_revision = True
+ else:
+ self.append_constraint('k.revision', run_kind.revision)
+ self.append_constraint('k.wc_levels', run_kind.levels)
+ self.append_constraint('k.wc_spread', run_kind.spread)
+ self.label = run_kind.label()
- def append_constraint(self, table, name, val):
+ def append_constraint(self, column_name, val):
if val:
- self.constraints.append('AND %s.%s = ?' % (table, name))
+ self.constraints.append('AND %s = ?' % column_name)
self.values.append(val)
def remove_last_constraint(self):
@@ -458,7 +535,6 @@
query.append('ORDER BY %s' % x)
c = db.conn.cursor()
try:
- #print ' '.join(query)
c.execute(' '.join(query), self.values)
if n == 1:
return [tpl[0] for tpl in c.fetchall()]
@@ -500,7 +576,7 @@
max(t.timing),
avg(t.timing)""",
self.FROM_WHERE ]
- self.append_constraint('t', 'command', command)
+ self.append_constraint('t.command', command)
try:
query.extend(self.constraints)
c = db.conn.cursor()
@@ -816,11 +892,12 @@
def cmdline_run(db, options, run_kind_str, N=1):
- run_kind = RunKind(run_kind_str)
+ run_kind = parse_one_timing_selection(db, run_kind_str)
+
N = int(N)
print 'Hi, going to run a Subversion benchmark series of %d runs...' % N
- print 'Label is %s' % run_kind.label
+ print 'Label is %s' % run_kind.label()
# can we run the svn binaries?
svn_bin = j(options.svn_bin_dir, 'svn')
@@ -829,8 +906,7 @@
for b in (svn_bin, svnadmin_bin):
so,se = run_cmd([b, '--version'])
if not so:
- print "Can't run", b
- exit(1)
+ bail("Can't run %s" % b)
print ', '.join([s.strip() for s in so.split('\n')[:2]])
@@ -844,54 +920,55 @@
batch.done()
-def cmdline_list(db, options, run_kind_str=None):
- run_kind = RunKind(run_kind_str)
+def cmdline_list(db, options, *args):
+ run_kinds = parse_timings_selections(db, *args)
- constraints = []
- def add_if_not_none(name, val):
- if val:
- constraints.append(' %s = %s' % (name, val))
- add_if_not_none('branch', run_kind.branch)
- add_if_not_none('revision', run_kind.revision)
- add_if_not_none('levels', run_kind.levels)
- add_if_not_none('spread', run_kind.spread)
- if constraints:
- print 'For\n', '\n'.join(constraints)
- print 'I found:'
+ for run_kind in run_kinds:
- d = TimingQuery(db, run_kind)
-
- cmd_names = d.get_sorted_command_names()
- if cmd_names:
- print '\n%d command names:\n ' % len(cmd_names), '\n '.join(cmd_names)
+ constraints = []
+ def add_if_not_none(name, val):
+ if val:
+ constraints.append(' %s = %s' % (name, val))
+ add_if_not_none('branch', run_kind.branch)
+ add_if_not_none('revision', run_kind.revision)
+ add_if_not_none('levels', run_kind.levels)
+ add_if_not_none('spread', run_kind.spread)
+ if constraints:
+ print 'For\n', '\n'.join(constraints)
+ print 'I found:'
- branches = d.get_sorted_branches()
- if branches and (len(branches) > 1 or branches[0] != run_kind.branch):
- print '\n%d branches:\n ' % len(branches), '\n '.join(branches)
+ d = TimingQuery(db, run_kind)
+
+ cmd_names = d.get_sorted_command_names()
+ if cmd_names:
+ print '\n%d command names:\n ' % len(cmd_names), '\n '.join(cmd_names)
- revisions = d.get_sorted_revisions()
- if revisions and (len(revisions) > 1 or revisions[0] != run_kind.revision):
- print '\n%d revisions:\n ' % len(revisions), '\n '.join(revisions)
+ branches = d.get_sorted_branches()
+ if branches and (len(branches) > 1 or branches[0] != run_kind.branch):
+ print '\n%d branches:\n ' % len(branches), '\n '.join(branches)
- levels_spread = d.get_sorted_levels_spread()
- if levels_spread and (
- len(levels_spread) > 1
- or levels_spread[0] != (run_kind.levels, run_kind.spread)):
- print '\n%d kinds of levels x spread:\n ' % len(levels_spread), '\n '.join(
- [ ('%dx%d' % (l, s)) for l,s in levels_spread ])
+ revisions = d.get_sorted_revisions()
+ if revisions and (len(revisions) > 1 or revisions[0] != run_kind.revision):
+ print '\n%d revisions:\n ' % len(revisions), '\n '.join(revisions)
- print "\n%d runs in %d batches.\n" % (d.count_runs_batches())
+ levels_spread = d.get_sorted_levels_spread()
+ if levels_spread and (
+ len(levels_spread) > 1
+ or levels_spread[0] != (run_kind.levels, run_kind.spread)):
+ print '\n%d kinds of levels x spread:\n ' % len(levels_spread), '\n '.join(
+ [ ('%dx%d' % (l, s)) for l,s in levels_spread ])
+
+ print "\n%d runs in %d batches.\n" % (d.count_runs_batches())
def cmdline_show(db, options, *run_kind_strings):
- for run_kind_str in run_kind_strings:
- run_kind = RunKind(run_kind_str)
-
+ run_kinds = parse_timings_selections(db, *run_kind_strings)
+ for run_kind in run_kinds:
q = TimingQuery(db, run_kind)
timings = q.get_timings()
s = []
- s.append('Timings for %s' % run_kind.label)
+ s.append('Timings for %s' % run_kind.label())
s.append(' N min max avg operation (unit is seconds)')
for command_name in q.get_sorted_command_names():
@@ -909,96 +986,108 @@
print '\n'.join(s)
-def cmdline_compare(db, options, left_str, right_str):
- left_kind = RunKind(left_str)
- right_kind = RunKind(right_str)
+def cmdline_compare(db, options, *args):
+ run_kinds = parse_timings_selections(db, *args)
+ if len(run_kinds) < 2:
+ bail("Need at least two sets of timings to compare.")
+
+ left_kind = run_kinds[0]
leftq = TimingQuery(db, left_kind)
left = leftq.get_timings()
if not left:
- print "No timings for", left_kind.label
- exit(1)
+ bail("No timings for %s" % left_kind.label())
- rightq = TimingQuery(db, right_kind)
- right = rightq.get_timings()
- if not right:
- print "No timings for", right_kind.label
- exit(1)
+ for run_kind_idx in range(1, len(run_kinds)):
+ right_kind = run_kinds[run_kind_idx]
- label = 'Compare %s to %s' % (left_kind.label, right_kind.label)
+ rightq = TimingQuery(db, right_kind)
+ right = rightq.get_timings()
+ if not right:
+ print "No timings for %s" % right_kind.label()
+ continue
- s = [label]
+ label = 'Compare %s to %s' % (right_kind.label(), left_kind.label())
- verbose = options.verbose
- if not verbose:
- s.append(' N avg operation')
- else:
- s.append(' N min max avg operation')
+ s = [label]
- command_names = [name for name in leftq.get_sorted_command_names()
- if name in right]
- if options.command_names:
- command_names = [name for name in command_names
- if name in options.command_names]
-
- for command_name in command_names:
- left_N, left_min, left_max, left_avg = left[command_name]
- right_N, right_min, right_max, right_avg = right[command_name]
-
- N_str = '%s/%s' % (n_label(left_N), n_label(right_N))
- avg_str = '%7.2f|%+7.3f' % (do_div(left_avg, right_avg),
- do_diff(left_avg, right_avg))
-
+ verbose = options.verbose
if not verbose:
- s.append('%9s %-16s %s' % (N_str, avg_str, command_name))
+ s.append(' N avg operation')
else:
- min_str = '%7.2f|%+7.3f' % (do_div(left_min, right_min),
- do_diff(left_min, right_min))
- max_str = '%7.2f|%+7.3f' % (do_div(left_max, right_max),
- do_diff(left_max, right_max))
+ s.append(' N min max avg operation')
- s.append('%9s %-16s %-16s %-16s %s' % (N_str, min_str, max_str, avg_str,
- command_name))
+ command_names = [name for name in leftq.get_sorted_command_names()
+ if name in right]
+ if options.command_names:
+ command_names = [name for name in command_names
+ if name in options.command_names]
- s.extend([
- '(legend: "1.23|+0.45" means: slower by factor 1.23 and by 0.45 seconds;',
- ' factor < 1 and seconds < 0 means \'%s\' is faster.'
- % right_kind.label,
- ' "2/3" means: \'%s\' has 2 timings on record, the other has 3.)'
- % left_kind.label
- ])
+ for command_name in command_names:
+ left_N, left_min, left_max, left_avg = left[command_name]
+ right_N, right_min, right_max, right_avg = right[command_name]
+
+ N_str = '%s/%s' % (n_label(left_N), n_label(right_N))
+ avg_str = '%7.2f|%+7.3f' % (do_div(left_avg, right_avg),
+ do_diff(left_avg, right_avg))
+
+ if not verbose:
+ s.append('%9s %-16s %s' % (N_str, avg_str, command_name))
+ else:
+ min_str = '%7.2f|%+7.3f' % (do_div(left_min, right_min),
+ do_diff(left_min, right_min))
+ max_str = '%7.2f|%+7.3f' % (do_div(left_max, right_max),
+ do_diff(left_max, right_max))
+
+ s.append('%9s %-16s %-16s %-16s %s' % (N_str, min_str, max_str, avg_str,
+ command_name))
+
+ s.extend([
+ '(legend: "1.23|+0.45" means: slower by factor 1.23 and by 0.45 seconds;',
+ ' factor < 1 and seconds < 0 means \'%s\' is faster.'
+ % right_kind.label(),
+ ' "2/3" means: \'%s\' has 2 timings on record, the other has 3.)'
+ % left_kind.label()
+ ])
- print '\n'.join(s)
+ print '\n'.join(s)
# ------------------------------------------------------- charts
def cmdline_chart_compare(db, options, *args):
+ import matplotlib
+ matplotlib.use('Agg')
import numpy as np
- import matplotlib.pyplot as plt
+ import matplotlib.pylab as plt
labels = []
timing_sets = []
command_names = None
- for arg in args:
- run_kind = RunKind(arg)
+ run_kinds = parse_timings_selections(db, *args)
+
+ # iterate the timings selections and accumulate data
+ for run_kind in run_kinds:
query = TimingQuery(db, run_kind)
timings = query.get_timings()
if not timings:
- print "No timings for", run_kind.label
- exit(1)
- labels.append(run_kind.label)
+ print "No timings for %s" % run_kind.label()
+ continue
+ labels.append(run_kind.label())
timing_sets.append(timings)
- if command_names:
- for i in range(len(command_names)):
- if not command_names[i] in timings:
- del command_names[i]
- else:
+ # it only makes sense to compare those commands that have timings
+ # in the first selection, because that is the one everything else
+ # is compared to. Remember the first selection's command names.
+ if not command_names:
command_names = query.get_sorted_command_names()
+
+ if len(timing_sets) < 2:
+ bail("Not enough timings")
+
if options.command_names:
command_names = [name for name in command_names
if name in options.command_names]
@@ -1009,99 +1098,125 @@
[ filesystem_safe_string(l) for l in labels ]
) + '.svg'
- print '\nwriting chart file:', chart_path
-
N = len(command_names)
M = len(timing_sets) - 1
+ if M < 2:
+ M = 2
- ind = np.arange(N) # the x locations for the groups
- width = 1. / (1.2 + M) # the width of the bars
- dist = 0.15
+ group_positions = np.arange(N) # the y locations for the groups
+ dist = 1. / (1. + M)
+ height = (1. - dist) / M # the height of the bars
- fig = plt.figure(figsize=(0.33*N*M,12))
- plot1 = fig.add_subplot(211)
- plot2 = fig.add_subplot(212)
+ fig = plt.figure(figsize=(12, 5 + 0.2*N*M))
+ plot1 = fig.add_subplot(121)
+ plot2 = fig.add_subplot(122)
- # invisible lines that make sure the scale doesn't get minuscule
- plot1.axhline(y=101, color='white', linewidth=0.01)
- plot1.axhline(y=95.0, color='white', linewidth=0.01)
- plot2.axhline(y=0.1, color='white', linewidth=0.01)
- plot2.axhline(y=-0.5, color='white', linewidth=0.01)
+ left = timing_sets[0]
- reference = timing_sets[0]
+ # Iterate timing sets. Each loop produces one bar for each command name
+ # group.
+ for label_i,label in enumerate(labels[1:],1):
+ right = timing_sets[label_i]
+ if not right:
+ continue
- ofs = 0
+ for cmd_i, command_name in enumerate(command_names):
+ if command_name not in right:
+ #skip
+ continue
- for label_i in range(1, len(labels)):
- timings = timing_sets[label_i]
- divs = []
- diffs = []
- divs_color = []
- deviations = []
- for command_name in command_names:
- ref_N, ref_min, ref_max, ref_avg = reference[command_name]
- this_N, this_min, this_max, this_avg = timings[command_name]
+ left_N, left_min, left_max, left_avg = left[command_name]
+ right_N, right_min, right_max, right_avg = right[command_name]
- val = 100. * (do_div(ref_avg, this_avg) - 1.0)
- if val < 0:
+ div_avg = 100. * (do_div(left_avg, right_avg) - 1.0)
+ if div_avg <= 0:
col = '#55dd55'
else:
col = '#dd5555'
- divs.append(val)
- divs_color.append(col)
- diffs.append( do_diff(ref_avg, this_avg) )
- deviations.append(this_max / this_min)
- rects = plot1.bar(ind + ofs, divs, width * (1.0 - dist),
- color=divs_color, bottom=100.0, edgecolor='none')
+ diff_val = do_diff(left_avg, right_avg)
- for i in range(len(rects)):
- x = rects[i].get_x() + width / 2.2
- div = divs[i]
- label = labels[label_i]
+ ofs = (dist + height) / 2. + height * (label_i - 1)
- plot1.text(x, 100.,
- ' %+5.1f%% %s' % (div,label),
- ha='center', va='top', size='small',
- rotation=-90, family='monospace')
+ barheight = height * (1.0 - dist)
- rects = plot2.bar(ind + ofs, diffs, width * 0.9,
- color=divs_color, bottom=0.0, edgecolor='none')
+ y = float(cmd_i) + ofs
- for i in range(len(rects)):
- x = rects[i].get_x() + width / 2.2
- diff = diffs[i]
- label = labels[label_i]
+ plot1.barh((y, ),
+ (div_avg, ),
+ barheight,
+ color=col, edgecolor='white')
+ plot1.text(0., y + height/2.,
+ '%s %+5.1f%%' % (label, div_avg),
+ ha='right', va='center', size='small',
+ rotation=0, family='monospace')
- plot2.text(x, 0.,
- ' %+5.2fs %s' % (diff,label),
- ha='center', va='top', size='small',
- rotation=-90, family='monospace')
+ plot2.barh((y, ),
+ (diff_val, ),
+ barheight,
+ color=col, edgecolor='white')
+ plot2.text(0., y + height/2.,
+ '%s %+6.2fs' % (label, diff_val),
+ ha='right', va='center', size='small',
+ rotation=0, family='monospace')
- ofs += width
- plot1.set_title('Speed change compared to %s [%%]' % labels[0])
- plot1.set_xticks(ind + (width / 2.))
- plot1.set_xticklabels(command_names, rotation=-55,
- horizontalalignment='left',
- size='x-small', weight='bold')
- plot1.axhline(y=100.0, color='#555555', linewidth=0.2)
- plot2.set_title('[seconds]')
- plot2.set_xticks(ind + (width / 2.))
- plot2.set_xticklabels(command_names, rotation=-55,
- horizontalalignment='left',
- size='medium', weight='bold')
- plot2.axhline(y=0.0, color='#555555', linewidth=0.2)
+ for p in (plot1, plot2):
+ xlim = list(p.get_xlim())
+ if xlim[1] < 10.:
+ xlim[1] = 10.
+ # make sure the zero line is far enough right so that the annotations
+ # fit inside the chart. About half the width should suffice.
+ if xlim[0] > -xlim[1]:
+ xlim[0] = -xlim[1]
+ p.set_xlim(*xlim)
+ p.set_xticks((0,))
+ p.set_yticks(group_positions + (height / 2.))
+ p.set_yticklabels(())
+ p.set_ylim((len(command_names), 0))
+ p.grid()
- margin = 1.5/(N*M)
- fig.subplots_adjust(bottom=0.1, top=0.97,
- left=margin,
- right=1.0-(margin / 2.))
+ plot1.set_xticklabels(('+-0%',), rotation=0)
+ plot1.set_title('Average runtime change from %s in %%' % labels[0],
+ size='medium')
- #plot1.legend( (rects1[0], rects2[0]), (left_label, right_label) )
+ plot2.set_xticklabels(('+-0s',), rotation=0)
+ plot2.set_title('Average runtime change from %s in seconds' % labels[0],
+ size='medium')
- #plt.show()
+ margin = 1./(2 + N*M)
+ titlemargin = 0
+ if options.title:
+ titlemargin = margin * 1.5
+
+ fig.subplots_adjust(left=0.005, right=0.995, wspace=0.3, bottom=margin,
+ top=1.0-margin-titlemargin)
+
+ ystep = (1.0 - 2.*margin - titlemargin) / len(command_names)
+
+ for idx,command_name in enumerate(command_names):
+ ylabel = '%s\nvs. %.1fs' % (
+ command_name,
+ left[command_name][3])
+
+ ypos=1.0 - margin - titlemargin - ystep/M - ystep * idx
+ plt.figtext(0.5, ypos,
+ command_name,
+ ha='center', va='top',
+ size='medium', weight='bold')
+ plt.figtext(0.5, ypos - ystep/(M+1),
+ '%s\n= %.2fs' % (
+ labels[0], left[command_name][3]),
+ ha='center', va='top',
+ size='small')
+
+ if options.title:
+ plt.figtext(0.5, 1. - titlemargin/2, options.title, ha='center',
+ va='center', weight='bold')
+
plt.savefig(chart_path)
+ print 'wrote chart file:', chart_path
+
# ------------------------------------------------------------ main
@@ -1142,6 +1257,9 @@
parser.add_option('-c', '--command-names', action='store',
dest='command_names',
help='Comma separated list of command names to limit to.')
+ parser.add_option('-t', '--title', action='store',
+ dest='title',
+ help='For charts, a title to print in the chart graphics.')
parser.set_description(__doc__)
parser.set_usage('')
@@ -1154,7 +1272,7 @@
if msg:
print
print msg
- exit(1)
+ bail()
# there should be at least one arg left: the sub-command
if not args:
diff --git a/tools/dev/benchmarks/suite1/cronjob b/tools/dev/benchmarks/suite1/cronjob
index c89994b..5b74292 100755
--- a/tools/dev/benchmarks/suite1/cronjob
+++ b/tools/dev/benchmarks/suite1/cronjob
@@ -30,13 +30,7 @@
# what it does: http://hofmeyr.de/pat/
#EMAILS=your@ema.il add@ress.es
-EMAILS=""
-
-if [ "$USER" = "neels" ]; then
- # I don't want to keep editing files after every update. ~Neels
- EMAILS=dev@subversion.apache.org
-fi
-
+EMAILS=dev@subversion.apache.org
echo
echo "--------------------------------------------------------------------"
@@ -45,12 +39,16 @@
results="$(tempfile)"
+benchdir=/home/neels/svnbench
+patbin=/home/neels/bin/pat
+patbase=/home/neels/pat
+
# first update trunk to HEAD and rebuild.
# update/build is logged to the cronjob log (via stdout)
-cd /home/neels/pat/trunk
-/home/neels/bin/pat update
+cd "$patbase/trunk"
+"$patbin" update
if [ "$?" -ne "0" ]; then
subject="Failed to update to HEAD."
@@ -58,14 +56,14 @@
echo "$subject"
else
- rev="$(/home/neels/pat/stable/prefix/bin/svn info /home/neels/pat/trunk/src | grep Revision)"
+ rev="$("$patbase"/stable/prefix/bin/svn info "$patbase"/trunk/src | grep Revision)"
if [ -z "$rev" ]; then
subject="Working copy problem."
echo "$subject" > "$results"
echo "$subject"
else
- NONMAINTAINER=1 /home/neels/bin/pat remake
+ NONMAINTAINER=1 "$patbin" remake
if [ "$?" -ne "0" ]; then
subject="Failed to build $rev."
echo "$subject" > "$results"
@@ -76,10 +74,10 @@
# updating and building succeeded!
# run the benchmark:
- compiled="$(/home/neels/pat/trunk/prefix/bin/svn --version | grep "compiled")"
+ compiled="$("$patbase"/trunk/prefix/bin/svn --version | grep "compiled")"
subject="$rev$compiled"
- cd /home/neels/svnbench/
+ cd "$benchdir"
# make more or less sure that runs don't leak into each other via
# I/O caching.
@@ -88,8 +86,8 @@
# basically, just run it. But also, I want to
# - append output to stdout, for cronjob logging.
# - send output as mail, but only this run's output less update&build
- "$(which time)" -p ./run 2>&1 | tee "$results"
-
+ time -p ./run 2>&1 | tee "$results"
+ time -p ./generate_charts 2>&1 | tee -a "$results"
fi
fi
fi
diff --git a/tools/dev/benchmarks/suite1/generate_charts b/tools/dev/benchmarks/suite1/generate_charts
new file mode 100644
index 0000000..8e16526
--- /dev/null
+++ b/tools/dev/benchmarks/suite1/generate_charts
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+SVN_A_NAME="1.7.0"
+SVN_B_NAME="trunk"
+
+# benchmark script and parameters...
+benchmark="$PWD/benchmark.py"
+
+db="$PWD/${SVNBENCH_DEBUG}benchmark.db"
+
+chartsdir="$PWD/charts"
+
+mkdir -p "$chartsdir/.busy"
+
+if [ ! -e "$chartsdir/README" ]; then
+ cat > "$chartsdir/README" <<END
+These charts are generated by svn benchmark suite1.
+http://svn.apache.org/repos/asf/subversion/trunk/tools/dev/benchmarks/suite1
+
+*DISCLAIMER* - This tests only file://-URL access on a GNU/Linux VM.
+This is intended to measure changes in performance of the local working
+copy layer, *only*. These results are *not* generally true for everyone.
+END
+fi
+
+for levelspread in "" 5x5 1x100 100x1; do
+ if [ -z "$levelspread" ]; then
+ lsarg=""
+ lstitle=""
+ else
+ lsarg=",$levelspread"
+ lstitle=", WC dir levels x spread = $levelspread"
+ fi
+ N=12
+ "$benchmark" -f "$db" chart compare \
+ ${SVN_A_NAME}$lsarg ${SVN_B_NAME}@last${N}$lsarg \
+ -o "$chartsdir/.busy/compare_${SVN_A_NAME}_${SVN_B_NAME}@last${N}$lsarg.svg" \
+ -t "svn client benchmarks, file:// access *only*$lstitle"
+done
+
+mv "$chartsdir/.busy/"*.svg "$chartsdir/"
+rmdir "$chartsdir/.busy"
+
diff --git a/tools/dev/benchmarks/suite1/run b/tools/dev/benchmarks/suite1/run
index 2e67a5a..c146ea0 100755
--- a/tools/dev/benchmarks/suite1/run
+++ b/tools/dev/benchmarks/suite1/run
@@ -88,9 +88,11 @@
echo "Started at $started"
echo "
-*Disclaimer* - This tests only file://-URL access on a GNU/Linux VM.
+*DISCLAIMER* - This tests only file://-URL access on a GNU/Linux VM.
This is intended to measure changes in performance of the local working
-copy layer, *only*. These results are *not* generally true for everyone."
+copy layer, *only*. These results are *not* generally true for everyone.
+
+Charts of this data are available at http://svn-qavm.apache.org/charts/"
if [ -z "$SVNBENCH_SUMMARY_ONLY" ]; then
batch $al $as $N
@@ -139,6 +141,5 @@
echo ""
echo "Had started at $started,"
echo " done at $(date)"
-pwd
} 2>&1 | tee results.txt
diff --git a/tools/dev/svnraisetreeconflict/main.c b/tools/dev/svnraisetreeconflict/main.c
index ccf851f..4a8ea66 100644
--- a/tools/dev/svnraisetreeconflict/main.c
+++ b/tools/dev/svnraisetreeconflict/main.c
@@ -218,10 +218,10 @@
/* Allocate and fill in the description data structures */
SVN_ERR(svn_dirent_get_absolute(&wc_abspath, wc_path, pool));
- left = svn_wc_conflict_version_create(repos_url1, path_in_repos1, peg_rev1,
- kind1, pool);
- right = svn_wc_conflict_version_create(repos_url2, path_in_repos2, peg_rev2,
- kind2, pool);
+ left = svn_wc_conflict_version_create2(repos_url1, NULL, path_in_repos1,
+ peg_rev1, kind1, pool);
+ right = svn_wc_conflict_version_create2(repos_url2, NULL, path_in_repos2,
+ peg_rev2, kind2, pool);
c = svn_wc_conflict_description_create_tree2(wc_abspath, kind,
operation, left, right, pool);
c->action = (svn_wc_conflict_action_t)action;
diff --git a/tools/dev/unix-build/Makefile.svn b/tools/dev/unix-build/Makefile.svn
index f1b0619..aeb1b0f 100644
--- a/tools/dev/unix-build/Makefile.svn
+++ b/tools/dev/unix-build/Makefile.svn
@@ -862,6 +862,8 @@
> $(CYRUS_SASL_SRCDIR)/lib/dlopen.c.patched
mv $(CYRUS_SASL_SRCDIR)/lib/dlopen.c.patched \
$(CYRUS_SASL_SRCDIR)/lib/dlopen.c
+ # Fix a weird autotools error about missing cmulocal dir
+ (cd $(CYRUS_SASL_SRCDIR)/saslauthd/ && ln -sf ../cmulocal)
touch $@
# configure cyrus-sasl
@@ -961,6 +963,12 @@
tar -C $(SRCDIR) -zxf $(DISTDIR)/$(RUBY_DIST)
touch $@
+ifeq ($(THREADING),yes)
+THREADSAFE_FLAG=--enable-pthread
+else
+THREADSAFE_FLAG=--disable-pthread
+endif
+
# configure ruby
$(RUBY_OBJDIR)/.configured: $(RUBY_OBJDIR)/.retrieved
cd $(RUBY_OBJDIR) \
@@ -968,7 +976,7 @@
$(RUBY_SRCDIR)/configure \
--prefix=$(PREFIX)/ruby \
--enable-shared \
- --disable-pthread
+ $(THREADSAFE_FLAG)
touch $@
# compile ruby
@@ -1149,6 +1157,7 @@
MOD_AUTHZ_SVN=modules/svn-$(WC)/mod_authz_svn.so
LIBMAGIC_FLAG=--with-libmagic=$(PREFIX)/libmagic
NEON_FLAG=--with-neon="$(PREFIX)/neon"
+JAVAHL_CHECK_TARGET=check-javahl
else ifeq ($(BRANCH_MAJOR),1.6)
BDB_FLAG=db.h:$(PREFIX)/bdb/include:$(PREFIX)/bdb/lib:db-$(BDB_MAJOR_VER)
SERF_FLAG=--with-serf="$(PREFIX)/serf"
@@ -1156,6 +1165,7 @@
MOD_AUTHZ_SVN=modules/svn-$(WC)/mod_authz_svn.so
W_NO_SYSTEM_HEADERS=-Wno-system-headers
NEON_FLAG=--with-neon="$(PREFIX)/neon"
+JAVAHL_CHECK_TARGET=check-javahl
else ifeq ($(BRANCH_MAJOR),1.5)
BDB_FLAG=$(PREFIX)/bdb
SERF_FLAG=--with-serf="$(PREFIX)/serf-old"
@@ -1164,12 +1174,14 @@
DISABLE_NEON_VERSION_CHECK=--disable-neon-version-check
W_NO_SYSTEM_HEADERS=-Wno-system-headers
NEON_FLAG=--with-neon="$(PREFIX)/neon"
+JAVAHL_CHECK_TARGET=check-javahl
else # 1.8
BDB_FLAG=db.h:$(PREFIX)/bdb/include:$(PREFIX)/bdb/lib:db-$(BDB_MAJOR_VER)
SERF_FLAG=--with-serf="$(PREFIX)/serf"
MOD_DAV_SVN=modules/svn-$(WC)/mod_dav_svn.so
MOD_AUTHZ_SVN=modules/svn-$(WC)/mod_authz_svn.so
LIBMAGIC_FLAG=--with-libmagic=$(PREFIX)/libmagic
+JAVAHL_CHECK_TARGET=check-all-javahl
endif
ifeq ($(ENABLE_JAVA_BINDINGS),yes)
@@ -1350,10 +1362,17 @@
libpath:
@echo export LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):$$LD_LIBRARY_PATH" \
"PYTHONPATH=$(SVN_PREFIX)/lib/svn-python"
+#
+# OpenBSD requires an LD_PRELOAD hack to dlopen() libraries linked to
+# libpthread (e.g. libsvn_auth_gnome_keyring.so) into executables that
+# aren't linked to libpthread.
+ifeq ($(UNAME),OpenBSD)
+LIB_PTHREAD_HACK=LD_PRELOAD=libpthread.so
+endif
.PHONY: start-svnserve stop-svnserve start-httpd stop-httpd
-HTTPD_CMD = env LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) \
+HTTPD_CMD = env LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) $(LIB_PTHREAD_HACK) \
$(PREFIX)/httpd/bin/apachectl -f $(HTTPD_CHECK_CONF)
HTTPD_START_CMD = $(HTTPD_CMD) -k start
HTTPD_START_CMD_DEBUG = $(HTTPD_START_CMD) -X
@@ -1400,7 +1419,7 @@
-cd $(svn_builddir) && for fs in fsfs bdb; do \
echo "Begin test: $(subst svn-check-,,$@) x $$fs"; \
test -d "$(RAMDISK)/tmp" && export TMPDIR="$(RAMDISK)/tmp"; \
- env LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) \
+ env LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) $(LIB_PTHREAD_HACK) \
make check PARALLEL=$(PARALLEL) CLEANUP=$(CLEANUP) $1 FS_TYPE=$$fs; \
for log in tests.log fails.log; do \
test -f $$log && mv -f $$log $$log.$@-$$fs; \
@@ -1456,6 +1475,7 @@
-if [ $(ENABLE_PERL_BINDINGS) = yes ]; then \
(cd $(svn_builddir) && \
env LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) \
+ $(LIB_PTHREAD_HACK) \
make check-swig-pl 2>&1) | \
tee $(svn_builddir)/tests.log.bindings.pl; \
fi
@@ -1473,6 +1493,7 @@
env RUBYLIB=$(RUBYLIB) \
LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) \
PATH=$(SVN_PREFIX)/bin:$$PATH \
+ $(LIB_PTHREAD_HACK) \
make check-swig-rb 2>&1) | \
tee $(svn_builddir)/tests.log.bindings.rb
@@ -1480,7 +1501,7 @@
-if [ $(ENABLE_JAVA_BINDINGS) = yes ]; then \
(cd $(svn_builddir) && \
env LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) \
- make check-all-javahl 2>&1) | \
+ make $(JAVAHL_CHECK_TARGET) 2>&1) | \
tee $(svn_builddir)/tests.log.bindings.javahl; \
fi
diff --git a/tools/dist/backport.pl b/tools/dist/backport.pl
index 4e8c41e..c03afb1 100755
--- a/tools/dist/backport.pl
+++ b/tools/dist/backport.pl
@@ -126,9 +126,9 @@
reinteg_rev=\`$SVN info $STATUS | sed -ne 's/Last Changed Rev: //p'\`
if $WET_RUN; then
# Sleep to avoid out-of-order commit notifications
- if [ -n "$YES" ]; then sleep 15; fi
+ if [ -n "\$YES" ]; then sleep 15; fi
$SVNq rm $BRANCHES/$entry{branch} -m "Remove the '$entry{branch}' branch, reintegrated in r\$reinteg_rev."
- if [ -n "$YES" ]; then sleep 1; fi
+ if [ -n "\$YES" ]; then sleep 1; fi
else
echo "Removing reintegrated '$entry{branch}' branch"
fi
@@ -229,6 +229,10 @@
usage, exit 0 if @ARGV;
usage, exit 1 unless -r $STATUS;
+ # Because we use the ':normal' command in Vim...
+ die "A vim with the +ex_extra feature is required"
+ if `${VIM} --version` !~ /[+]ex_extra/;
+
@ARGV = $STATUS;
# Skip most of the file
diff --git a/tools/dist/make-deps-tarball.sh b/tools/dist/make-deps-tarball.sh
index f3f23ae..318adc6 100755
--- a/tools/dist/make-deps-tarball.sh
+++ b/tools/dist/make-deps-tarball.sh
@@ -21,14 +21,12 @@
#
set -e
-# NOTE: Before bumping the default APR dependency the 1.6.x test suite
-# should be adjusted to cope with hash ordering changes from APR 1.4.6.
-APR=apr-1.4.5
+APR=apr-1.4.6
APR_UTIL=apr-util-1.4.1
NEON=neon-0.29.6
SERF=serf-0.3.1
-ZLIB=zlib-1.2.6
-SQLITE_VERSION=3071100
+ZLIB=zlib-1.2.7
+SQLITE_VERSION=3071400
SQLITE=sqlite-amalgamation-$SQLITE_VERSION
HTTPD=httpd-2.2.22
diff --git a/tools/dist/nightly.sh b/tools/dist/nightly.sh
index 22b6a86..0f2f991 100755
--- a/tools/dist/nightly.sh
+++ b/tools/dist/nightly.sh
@@ -55,10 +55,10 @@
# Get the latest versions of the rolling scripts
for i in release.py dist.sh
do
- $svn export -r $head $repo/trunk/tools/dist/$i@$head $dir/$i
+ $svn export --force -r $head $repo/trunk/tools/dist/$i@$head $dir/$i
done
# We also need ezt
-$svn export -r $head $repo/trunk/build/generator/ezt.py@$head $dir/ezt.py
+$svn export --force -r $head $repo/trunk/build/generator/ezt.py@$head $dir/ezt.py
# Create the environment
cd roll
diff --git a/tools/dist/release.py b/tools/dist/release.py
index ed2b82f..9374663 100755
--- a/tools/dist/release.py
+++ b/tools/dist/release.py
@@ -679,7 +679,7 @@
#----------------------------------------------------------------------
# Validate the signatures for a release
-key_start = '-----BEGIN PGP SIGNATURE-----\n'
+key_start = '-----BEGIN PGP SIGNATURE-----'
fp_pattern = re.compile(r'^pub\s+(\w+\/\w+)[^\n]*\n\s+Key\sfingerprint\s=((\s+[0-9A-F]{4}){10})\nuid\s+([^<\(]+)\s')
def check_sigs(args):
diff --git a/tools/hook-scripts/persist-ephemeral-txnprops.py b/tools/hook-scripts/persist-ephemeral-txnprops.py
new file mode 100644
index 0000000..330bc53
--- /dev/null
+++ b/tools/hook-scripts/persist-ephemeral-txnprops.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+
+import sys
+import os
+from svn import repos, fs, core
+
+def duplicate_ephemeral_txnprops(repos_path, txn_name):
+ fs_ptr = repos.fs(repos.open(repos_path))
+ txn_t = fs.open_txn(fs_ptr, txn_name)
+ for name, value in fs.txn_proplist(txn_t).items():
+ if name.startswith(core.SVN_PROP_TXN_PREFIX):
+ name = core.SVN_PROP_REVISION_PREFIX + \
+ name[len(core.SVN_PROP_TXN_PREFIX):]
+ fs.change_txn_prop(txn_t, name, value)
+
+def usage_and_exit(errmsg=None):
+ stream = errmsg and sys.stderr or sys.stdout
+ stream.write("""\
+Usage:
+
+ persist-ephemeral-txnprops.py REPOS_PATH TXN_NAME
+
+Duplicate ephemeral transaction properties so that the information
+they carry may persist as properties of the revision created once the
+transaction is committed. This is intended to be used as a Subversion
+pre-commit hook script.
+
+REPOS_PATH is the on-disk path of the repository whose transaction
+properties are being examined/modified. TXN_NAME is the name of the
+transaction.
+
+Ephemeral transaction properties, whose names all begin with the
+prefix "%s", will be copied to new properties which use the
+prefix "%s" instead.
+
+""" % (core.SVN_PROP_TXN_PREFIX, core.SVN_PROP_REVISION_PREFIX))
+ if errmsg:
+ stream.write("ERROR: " + errmsg + "\n")
+ sys.exit(errmsg and 1 or 0)
+
+def main():
+ argc = len(sys.argv)
+ if argc != 3:
+ usage_and_exit("Incorrect number of arguments.")
+ repos_path = sys.argv[1]
+ txn_name = sys.argv[2]
+ duplicate_ephemeral_txnprops(repos_path, txn_name)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/server-side/fsfs-reorg.c b/tools/server-side/fsfs-reorg.c
new file mode 100644
index 0000000..e9770bd
--- /dev/null
+++ b/tools/server-side/fsfs-reorg.c
@@ -0,0 +1,2514 @@
+/* diff.c -- test driver for text diffs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <assert.h>
+
+#include <apr.h>
+#include <apr_general.h>
+#include <apr_file_io.h>
+#include <apr_poll.h>
+
+#include "svn_pools.h"
+#include "svn_diff.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_dirent_uri.h"
+#include "svn_sorts.h"
+#include "svn_delta.h"
+#include "svn_hash.h"
+
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+#ifndef _
+#define _(x) x
+#endif
+
+#define ERROR_TAG "diff: "
+
+typedef struct noderev_t noderev_t;
+typedef struct revision_info_t revision_info_t;
+
+enum fragment_kind_t
+{
+ header_fragment,
+ changes_fragment,
+ noderep_fragment,
+ property_fragment,
+ dir_fragment,
+ file_fragment
+};
+
+typedef struct fragment_t
+{
+ apr_size_t position;
+ void *data;
+ enum fragment_kind_t kind;
+} fragment_t;
+
+typedef struct revision_location_t
+{
+ apr_size_t offset;
+ apr_size_t changes;
+ apr_size_t changes_len;
+ apr_size_t end;
+} revision_location_t;
+
+typedef struct location_t
+{
+ apr_size_t offset;
+ apr_size_t size;
+} location_t;
+
+typedef struct direntry_t
+{
+ const char *name;
+ apr_size_t name_len;
+ noderev_t *node;
+} direntry_t;
+
+typedef struct directory_t
+{
+ apr_array_header_t *entries;
+ unsigned char target_md5[16];
+ apr_size_t size;
+} directory_t;
+
+typedef struct representation_t
+{
+ location_t original;
+ location_t target;
+ apr_size_t header_size;
+ struct representation_t *delta_base;
+ revision_info_t *revision;
+ directory_t *dir;
+ svn_boolean_t is_plain;
+ svn_boolean_t covered;
+} representation_t;
+
+struct noderev_t
+{
+ location_t original;
+ location_t target;
+ noderev_t *predecessor;
+ representation_t *text;
+ representation_t *props;
+ revision_info_t *revision;
+ svn_boolean_t covered;
+};
+
+struct revision_info_t
+{
+ svn_revnum_t revision;
+ revision_location_t original;
+ revision_location_t target;
+ noderev_t *root_noderev;
+ apr_array_header_t *node_revs;
+ apr_array_header_t *representations;
+};
+
+typedef struct revision_pack_t
+{
+ svn_revnum_t base;
+ apr_array_header_t *info;
+ apr_array_header_t *fragments;
+ apr_size_t filesize;
+ apr_size_t target_offset;
+} revision_pack_t;
+
+typedef struct content_cache_t
+{
+ apr_pool_t *pool;
+ apr_pool_t *hash_pool;
+
+ apr_hash_t *hash;
+
+ char *data;
+ apr_size_t limit;
+
+ apr_size_t total_size;
+ apr_size_t insert_count;
+} content_cache_t;
+
+typedef struct dir_cache_entry_t
+{
+ svn_revnum_t revision;
+ apr_size_t offset;
+
+ apr_hash_t *hash;
+} dir_cache_entry_t;
+
+typedef struct dir_cache_t
+{
+ dir_cache_entry_t *entries;
+
+ apr_pool_t *pool1;
+ apr_pool_t *pool2;
+ apr_size_t entry_count;
+ apr_size_t insert_count;
+} dir_cache_t;
+
+typedef struct window_cache_entry_t
+{
+ svn_revnum_t revision;
+ apr_size_t offset;
+
+ svn_stringbuf_t *window;
+} window_cache_entry_t;
+
+typedef struct window_cache_t
+{
+ window_cache_entry_t *entries;
+
+ apr_pool_t *pool;
+ apr_size_t entry_count;
+ apr_size_t capacity;
+ apr_size_t used;
+} window_cache_t;
+
+typedef struct fs_fs_t
+{
+ const char *path;
+ svn_revnum_t start_revision;
+ int format;
+
+ svn_revnum_t max_revision;
+ svn_revnum_t min_unpacked_rev;
+ int max_files_per_dir;
+
+ apr_array_header_t *revisions;
+ apr_array_header_t *packs;
+
+ representation_t *null_base;
+ content_cache_t *cache;
+ dir_cache_t *dir_cache;
+ window_cache_t *window_cache;
+} fs_fs_t;
+
+static const char *
+get_pack_folder(fs_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%s/db/revs/%ld.pack",
+ fs->path, rev / fs->max_files_per_dir);
+}
+
+static const char *
+rev_or_pack_file_name(fs_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ return fs->min_unpacked_rev > rev
+ ? svn_dirent_join(get_pack_folder(fs, rev, pool), "pack", pool)
+ : apr_psprintf(pool, "%s/db/revs/%ld/%ld", fs->path,
+ rev / fs->max_files_per_dir, rev);
+}
+
+static svn_error_t *
+open_rev_or_pack_file(apr_file_t **file,
+ fs_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ return svn_io_file_open(file,
+ rev_or_pack_file_name(fs, rev, pool),
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT,
+ pool);
+}
+
+static svn_error_t *
+read_rev_or_pack_file(svn_stringbuf_t **content,
+ fs_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ return svn_stringbuf_from_file2(content,
+ rev_or_pack_file_name(fs, rev, pool),
+ pool);
+}
+
+static content_cache_t *
+create_content_cache(apr_pool_t *pool,
+ apr_size_t limit)
+{
+ content_cache_t *result = apr_pcalloc(pool, sizeof(*result));
+
+ result->pool = pool;
+ result->hash_pool = svn_pool_create(pool);
+ result->hash = svn_hash__make(result->hash_pool);
+ result->limit = limit;
+ result->total_size = 0;
+ result->insert_count = 0;
+ result->data = apr_palloc(pool, limit);
+
+ return result;
+}
+
+static svn_string_t *
+get_cached_content(content_cache_t *cache,
+ svn_revnum_t revision)
+{
+ return apr_hash_get(cache->hash, &revision, sizeof(revision));
+}
+
+static void
+set_cached_content(content_cache_t *cache,
+ svn_revnum_t revision,
+ svn_string_t *data)
+{
+ svn_string_t *content;
+ svn_revnum_t *key;
+
+ assert(get_cached_content(cache, revision) == NULL);
+
+ if (cache->total_size + data->len > cache->limit)
+ {
+ if (cache->insert_count > 10000)
+ {
+ svn_pool_clear(cache->hash_pool);
+ cache->hash = svn_hash__make(cache->hash_pool);
+ cache->insert_count = 0;
+ }
+ else
+ cache->hash = svn_hash__make(cache->hash_pool);
+
+ cache->total_size = 0;
+ }
+
+ content = apr_palloc(cache->hash_pool, sizeof(*content));
+ content->data = cache->data + cache->total_size;
+ content->len = data->len;
+
+ memcpy(cache->data + cache->total_size, data->data, data->len);
+ cache->total_size += data->len;
+
+ key = apr_palloc(cache->hash_pool, sizeof(*key));
+ *key = revision;
+
+ apr_hash_set(cache->hash, key, sizeof(*key), content);
+ ++cache->insert_count;
+}
+
+static svn_error_t *
+get_content(svn_string_t **data,
+ fs_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+ revision_info_t *revision_info;
+ svn_stringbuf_t *temp;
+ apr_off_t temp_offset;
+
+ svn_string_t *result = get_cached_content(fs->cache, revision);
+ if (result)
+ {
+ *data = result;
+ return SVN_NO_ERROR;
+ }
+
+ if (revision - fs->start_revision > fs->revisions->nelts)
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("Unknown revision %ld"), revision);
+ revision_info = APR_ARRAY_IDX(fs->revisions,
+ revision - fs->start_revision,
+ revision_info_t*);
+
+ temp = svn_stringbuf_create_ensure( revision_info->original.end
+ - revision_info->original.offset,
+ scratch_pool);
+ temp->len = revision_info->original.end - revision_info->original.offset;
+ SVN_ERR(open_rev_or_pack_file(&file, fs, revision, scratch_pool));
+
+ temp_offset = revision_info->original.offset;
+ SVN_ERR(svn_io_file_seek(file, APR_SET, &temp_offset,
+ scratch_pool));
+ SVN_ERR_ASSERT(temp_offset < APR_SIZE_MAX);
+ revision_info->original.offset = (apr_size_t)temp_offset;
+ SVN_ERR(svn_io_file_read(file, temp->data, &temp->len, scratch_pool));
+
+ set_cached_content(fs->cache, revision,
+ svn_stringbuf__morph_into_string(temp));
+ *data = get_cached_content(fs->cache, revision);
+
+ return SVN_NO_ERROR;
+}
+
+static dir_cache_t *
+create_dir_cache(apr_pool_t *pool,
+ apr_size_t entry_count)
+{
+ dir_cache_t *result = apr_pcalloc(pool, sizeof(*result));
+
+ result->pool1 = svn_pool_create(pool);
+ result->pool2 = svn_pool_create(pool);
+ result->entry_count = entry_count;
+ result->insert_count = 0;
+ result->entries = apr_pcalloc(pool, sizeof(*result->entries) * entry_count);
+
+ return result;
+}
+
+static apr_size_t
+get_dir_cache_index(fs_fs_t *fs,
+ svn_revnum_t revision,
+ apr_size_t offset)
+{
+ return (revision + offset * 0xd1f3da69) % fs->dir_cache->entry_count;
+}
+
+static apr_pool_t *
+get_cached_dir_pool(fs_fs_t *fs)
+{
+ return fs->dir_cache->pool1;
+}
+
+static apr_hash_t *
+get_cached_dir(fs_fs_t *fs,
+ representation_t *representation)
+{
+ svn_revnum_t revision = representation->revision->revision;
+ apr_size_t offset = representation->original.offset;
+
+ apr_size_t i = get_dir_cache_index(fs, revision, offset);
+ dir_cache_entry_t *entry = &fs->dir_cache->entries[i];
+
+ return entry->offset == offset && entry->revision == revision
+ ? entry->hash
+ : NULL;
+}
+
+static void
+set_cached_dir(fs_fs_t *fs,
+ representation_t *representation,
+ apr_hash_t *hash)
+{
+ svn_revnum_t revision = representation->revision->revision;
+ apr_size_t offset = representation->original.offset;
+
+ apr_size_t i = get_dir_cache_index(fs, revision, offset);
+ dir_cache_entry_t *entry = &fs->dir_cache->entries[i];
+
+ fs->dir_cache->insert_count += apr_hash_count(hash);
+ if (fs->dir_cache->insert_count >= fs->dir_cache->entry_count * 100)
+ {
+ apr_pool_t *pool;
+
+ svn_pool_clear(fs->dir_cache->pool2);
+ memset(fs->dir_cache->entries,
+ 0,
+ sizeof(*fs->dir_cache->entries) * fs->dir_cache->entry_count);
+ fs->dir_cache->insert_count = 0;
+
+ pool = fs->dir_cache->pool2;
+ fs->dir_cache->pool2 = fs->dir_cache->pool1;
+ fs->dir_cache->pool1 = pool;
+ }
+
+ entry->hash = hash;
+ entry->offset = offset;
+ entry->revision = revision;
+}
+
+static window_cache_t *
+create_window_cache(apr_pool_t *pool,
+ apr_size_t entry_count,
+ apr_size_t capacity)
+{
+ window_cache_t *result = apr_pcalloc(pool, sizeof(*result));
+
+ result->pool = svn_pool_create(pool);
+ result->entry_count = entry_count;
+ result->capacity = capacity;
+ result->used = 0;
+ result->entries = apr_pcalloc(pool, sizeof(*result->entries) * entry_count);
+
+ return result;
+}
+
+static apr_size_t
+get_window_cache_index(fs_fs_t *fs,
+ svn_revnum_t revision,
+ apr_size_t offset)
+{
+ return (revision + offset * 0xd1f3da69) % fs->window_cache->entry_count;
+}
+
+static svn_stringbuf_t *
+get_cached_window(fs_fs_t *fs,
+ representation_t *representation,
+ apr_pool_t *pool)
+{
+ svn_revnum_t revision = representation->revision->revision;
+ apr_size_t offset = representation->original.offset;
+
+ apr_size_t i = get_window_cache_index(fs, revision, offset);
+ window_cache_entry_t *entry = &fs->window_cache->entries[i];
+
+ return entry->offset == offset && entry->revision == revision
+ ? svn_stringbuf_dup(entry->window, pool)
+ : NULL;
+}
+
+static void
+set_cached_window(fs_fs_t *fs,
+ representation_t *representation,
+ svn_stringbuf_t *window)
+{
+ svn_revnum_t revision = representation->revision->revision;
+ apr_size_t offset = representation->original.offset;
+
+ apr_size_t i = get_window_cache_index(fs, revision, offset);
+ window_cache_entry_t *entry = &fs->window_cache->entries[i];
+
+ fs->window_cache->used += window->len;
+ if (fs->window_cache->used >= fs->window_cache->capacity)
+ {
+ svn_pool_clear(fs->window_cache->pool);
+ memset(fs->window_cache->entries,
+ 0,
+ sizeof(*fs->window_cache->entries) * fs->window_cache->entry_count);
+ fs->window_cache->used = window->len;
+ }
+
+ entry->window = svn_stringbuf_dup(window, fs->window_cache->pool);
+ entry->offset = offset;
+ entry->revision = revision;
+}
+
+/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
+ Use POOL for temporary allocations. */
+static svn_error_t *
+read_manifest(apr_array_header_t **manifest,
+ fs_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_stream_t *manifest_stream;
+ apr_pool_t *iterpool;
+
+ /* Open the manifest file. */
+ SVN_ERR(svn_stream_open_readonly(&manifest_stream,
+ svn_dirent_join(path, "manifest", pool),
+ pool, pool));
+
+ /* While we're here, let's just read the entire manifest file into an array,
+ so we can cache the entire thing. */
+ iterpool = svn_pool_create(pool);
+ *manifest = apr_array_make(pool, fs->max_files_per_dir, sizeof(apr_size_t));
+ while (1)
+ {
+ svn_stringbuf_t *sb;
+ svn_boolean_t eof;
+ apr_uint64_t val;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_stream_readline(manifest_stream, &sb, "\n", &eof, iterpool));
+ if (eof)
+ break;
+
+ err = svn_cstring_strtoui64(&val, sb->data, 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ _("Manifest offset '%s' too large"),
+ sb->data);
+ APR_ARRAY_PUSH(*manifest, apr_size_t) = (apr_size_t)val;
+ }
+ svn_pool_destroy(iterpool);
+
+ return svn_stream_close(manifest_stream);
+}
+
+static svn_error_t *
+read_revision_header(apr_size_t *changes,
+ apr_size_t *changes_len,
+ apr_size_t *root_noderev,
+ svn_stringbuf_t *file_content,
+ apr_size_t start,
+ apr_size_t end,
+ apr_pool_t *pool)
+{
+ char buf[64];
+ const char *line;
+ const char *space;
+ apr_uint64_t val;
+ apr_size_t len;
+
+ /* Read in this last block, from which we will identify the last line. */
+ len = sizeof(buf);
+ if (start + len > end)
+ len = end - start;
+
+ memcpy(buf, file_content->data + end - len, len);
+
+ /* The last byte should be a newline. */
+ if (buf[(apr_ssize_t)len - 1] != '\n')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Revision lacks trailing newline"));
+
+ /* Look for the next previous newline. */
+ buf[len - 1] = 0;
+ line = strrchr(buf, '\n');
+ if (line == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Final line in revision file longer "
+ "than 64 characters"));
+
+ space = strchr(line, ' ');
+ if (space == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Final line in revision file missing space"));
+
+ *(char *)space = 0;
+
+ SVN_ERR(svn_cstring_strtoui64(&val, line+1, 0, APR_SIZE_MAX, 10));
+ *root_noderev = (apr_size_t)val;
+ SVN_ERR(svn_cstring_strtoui64(&val, space+1, 0, APR_SIZE_MAX, 10));
+ *changes = (apr_size_t)val;
+ *changes_len = end - *changes - start - (buf + len - line) + 1;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_format(int *pformat, int *max_files_per_dir,
+ const char *path, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_file_t *file;
+ char buf[80];
+ apr_size_t len;
+
+ err = svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* Treat an absent format file as format 1. Do not try to
+ create the format file on the fly, because the repository
+ might be read-only for us, or this might be a read-only
+ operation, and the spirit of FSFS is to make no changes
+ whatseover in read-only operations. See thread starting at
+ http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
+ for more. */
+ svn_error_clear(err);
+ *pformat = 1;
+ *max_files_per_dir = 0;
+
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ len = sizeof(buf);
+ err = svn_io_read_length_line(file, buf, &len, pool);
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ /* Return a more useful error message. */
+ svn_error_clear(err);
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("Can't read first line of format file '%s'"),
+ svn_dirent_local_style(path, pool));
+ }
+ SVN_ERR(err);
+
+ /* Check that the first line contains only digits. */
+ SVN_ERR(svn_cstring_atoi(pformat, buf));
+
+ /* Set the default values for anything that can be set via an option. */
+ *max_files_per_dir = 0;
+
+ /* Read any options. */
+ while (1)
+ {
+ len = sizeof(buf);
+ err = svn_io_read_length_line(file, buf, &len, pool);
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ /* No more options; that's okay. */
+ svn_error_clear(err);
+ break;
+ }
+ SVN_ERR(err);
+
+ if (strncmp(buf, "layout ", 7) == 0)
+ {
+ if (strcmp(buf+7, "linear") == 0)
+ {
+ *max_files_per_dir = 0;
+ continue;
+ }
+
+ if (strncmp(buf+7, "sharded ", 8) == 0)
+ {
+ /* Check that the argument is numeric. */
+ SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf + 15));
+ continue;
+ }
+ }
+
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("'%s' contains invalid filesystem format option '%s'"),
+ svn_dirent_local_style(path, pool), buf);
+ }
+
+ return svn_io_file_close(file, pool);
+}
+
+static svn_error_t *
+read_number(svn_revnum_t *result, const char *path, apr_pool_t *pool)
+{
+ svn_stringbuf_t *content;
+ apr_int64_t number;
+
+ SVN_ERR(svn_stringbuf_from_file2(&content, path, pool));
+
+ content->data[content->len-1] = 0;
+ SVN_ERR(svn_cstring_atoi64(&number, content->data));
+ *result = (svn_revnum_t)number;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fs_open(fs_fs_t **fs, const char *path, apr_pool_t *pool)
+{
+ *fs = apr_pcalloc(pool, sizeof(**fs));
+ (*fs)->path = apr_pstrdup(pool, path);
+ (*fs)->max_files_per_dir = 1000;
+
+ /* Read the FS format number. */
+ SVN_ERR(read_format(&(*fs)->format,
+ &(*fs)->max_files_per_dir,
+ svn_dirent_join(path, "db/format", pool),
+ pool));
+ if (((*fs)->format != 4) && ((*fs)->format != 6))
+ return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, NULL);
+
+ SVN_ERR(read_number(&(*fs)->min_unpacked_rev,
+ svn_dirent_join(path, "db/min-unpacked-rev", pool),
+ pool));
+ return read_number(&(*fs)->max_revision,
+ svn_dirent_join(path, "db/current", pool),
+ pool);
+}
+
+static svn_boolean_t
+key_matches(svn_string_t *string, const char *key)
+{
+ return strcmp(string->data, key) == 0;
+}
+
+static int
+compare_noderev_offsets(const void *data, const void *key)
+{
+ return (*(const noderev_t **)data)->original.offset
+ - *(const apr_size_t *)key;
+}
+
+static svn_error_t *
+parse_revnode_pos(revision_info_t **revision_info,
+ apr_size_t *offset,
+ fs_fs_t *fs,
+ svn_string_t *id)
+{
+ int revision;
+ apr_uint64_t temp;
+
+ const char *revision_pos = strrchr(id->data, 'r');
+ char *offset_pos = (char *)strchr(id->data, '/');
+
+ if (revision_pos == NULL || offset_pos == NULL)
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("Invalid node id '%s'"), id->data);
+
+ *offset_pos = 0;
+ SVN_ERR(svn_cstring_atoi(&revision, revision_pos + 1));
+ SVN_ERR(svn_cstring_strtoui64(&temp, offset_pos + 1, 0, APR_SIZE_MAX, 10));
+ *offset = (apr_size_t)temp;
+ *offset_pos = '/';
+
+ if (revision - fs->start_revision > fs->revisions->nelts)
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("Unknown revision %d"), revision);
+
+ *revision_info = APR_ARRAY_IDX(fs->revisions,
+ revision - fs->start_revision,
+ revision_info_t*);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+find_noderev(noderev_t **result,
+ revision_info_t *revision_info,
+ apr_size_t offset)
+{
+ int idx = svn_sort__bsearch_lower_bound(&offset,
+ revision_info->node_revs,
+ compare_noderev_offsets);
+ if ((idx < 0) || (idx >= revision_info->node_revs->nelts))
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("No noderev found at offset %" APR_OFF_T_FMT),
+ offset);
+
+ *result = APR_ARRAY_IDX(revision_info->node_revs, idx, noderev_t *);
+ if ((*result)->original.offset != offset)
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("No noderev found at offset %" APR_OFF_T_FMT),
+ offset);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+parse_pred(noderev_t **result,
+ fs_fs_t *fs,
+ svn_string_t *id)
+{
+ apr_size_t offset;
+ revision_info_t *revision_info;
+
+ SVN_ERR(parse_revnode_pos(&revision_info, &offset, fs, id));
+ SVN_ERR(find_noderev(result, revision_info, offset));
+
+ return SVN_NO_ERROR;
+}
+
+static int
+compare_representation_offsets(const void *data, const void *key)
+{
+ return (*(const representation_t **)data)->original.offset
+ - *(const apr_size_t *)key;
+}
+
+static representation_t *
+find_representation(int *idx,
+ fs_fs_t *fs,
+ revision_info_t **revision_info,
+ int revision,
+ apr_size_t offset)
+{
+ revision_info_t *info;
+ *idx = -1;
+
+ info = revision_info ? *revision_info : NULL;
+ if (info == NULL || info->revision != revision)
+ {
+ info = APR_ARRAY_IDX(fs->revisions,
+ revision - fs->start_revision,
+ revision_info_t*);
+ if (revision_info)
+ *revision_info = info;
+ }
+
+ if (info == NULL)
+ return NULL;
+
+ *idx = svn_sort__bsearch_lower_bound(&offset,
+ info->representations,
+ compare_representation_offsets);
+ if (*idx < info->representations->nelts)
+ {
+ representation_t *result
+ = APR_ARRAY_IDX(info->representations, *idx, representation_t *);
+ if (result->original.offset == offset)
+ return result;
+ }
+
+ return NULL;
+}
+
+static svn_error_t *
+read_rep_base(representation_t **representation,
+ apr_size_t *header_size,
+ svn_boolean_t *is_plain,
+ fs_fs_t *fs,
+ svn_stringbuf_t *file_content,
+ apr_size_t offset,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ char *str, *last_str;
+ int idx, revision;
+ apr_uint64_t temp;
+
+ const char *buffer = file_content->data + offset;
+ const char *line_end = strchr(buffer, '\n');
+ *header_size = line_end - buffer + 1;
+
+ if (strncmp(buffer, "PLAIN\n", *header_size) == 0)
+ {
+ *is_plain = TRUE;
+ *representation = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ *is_plain = FALSE;
+ if (strncmp(buffer, "DELTA\n", *header_size) == 0)
+ {
+ /* This is a delta against the empty stream. */
+ *representation = fs->null_base;
+ return SVN_NO_ERROR;
+ }
+
+ str = apr_pstrndup(scratch_pool, buffer, line_end - buffer);
+ last_str = str;
+
+ /* We hopefully have a DELTA vs. a non-empty base revision. */
+ str = svn_cstring_tokenize(" ", &last_str);
+ str = svn_cstring_tokenize(" ", &last_str);
+ SVN_ERR(svn_cstring_atoi(&revision, str));
+
+ str = svn_cstring_tokenize(" ", &last_str);
+ SVN_ERR(svn_cstring_strtoui64(&temp, str, 0, APR_SIZE_MAX, 10));
+
+ *representation = find_representation(&idx, fs, NULL, revision, (apr_size_t)temp);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+parse_representation(representation_t **representation,
+ fs_fs_t *fs,
+ svn_stringbuf_t *file_content,
+ svn_string_t *value,
+ revision_info_t *revision_info,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ representation_t *result;
+ int revision;
+
+ apr_uint64_t offset;
+ apr_uint64_t size;
+ int idx;
+
+ char *c = (char *)value->data;
+ SVN_ERR(svn_cstring_atoi(&revision, svn_cstring_tokenize(" ", &c)));
+ SVN_ERR(svn_cstring_strtoui64(&offset, svn_cstring_tokenize(" ", &c), 0, APR_SIZE_MAX, 10));
+ SVN_ERR(svn_cstring_strtoui64(&size, svn_cstring_tokenize(" ", &c), 0, APR_SIZE_MAX, 10));
+
+ result = find_representation(&idx, fs, &revision_info, revision, (apr_size_t)offset);
+ if (!result)
+ {
+ result = apr_pcalloc(pool, sizeof(*result));
+ result->revision = revision_info;
+ result->original.offset = (apr_size_t)offset;
+ result->original.size = (apr_size_t)size;
+ SVN_ERR(read_rep_base(&result->delta_base, &result->header_size,
+ &result->is_plain, fs, file_content,
+ (apr_size_t)offset + revision_info->original.offset,
+ pool, scratch_pool));
+
+ svn_sort__array_insert(&result, revision_info->representations, idx);
+ }
+
+ *representation = result;
+
+ return SVN_NO_ERROR;
+}
+
+/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
+ window into *NWIN. */
+static svn_error_t *
+read_windows(apr_array_header_t **windows,
+ fs_fs_t *fs,
+ representation_t *representation,
+ apr_pool_t *pool)
+{
+ svn_string_t *content;
+ svn_string_t data;
+ svn_stream_t *stream;
+ apr_size_t offset = representation->original.offset
+ + representation->header_size;
+ char version;
+ apr_size_t len = sizeof(version);
+
+ *windows = apr_array_make(pool, 0, sizeof(svn_txdelta_window_t *));
+
+ SVN_ERR(get_content(&content, fs, representation->revision->revision, pool));
+
+ data.data = content->data + offset + 3;
+ data.len = representation->original.size - 3;
+ stream = svn_stream_from_string(&data, pool);
+ SVN_ERR(svn_stream_read(stream, &version, &len));
+
+ while (TRUE)
+ {
+ svn_txdelta_window_t *window;
+ svn_stream_mark_t *mark;
+ char dummy;
+
+ len = sizeof(dummy);
+ SVN_ERR(svn_stream_mark(stream, &mark, pool));
+ SVN_ERR(svn_stream_read(stream, &dummy, &len));
+ if (len == 0)
+ break;
+
+ SVN_ERR(svn_stream_seek(stream, mark));
+ SVN_ERR(svn_txdelta_read_svndiff_window(&window, stream, version, pool));
+ APR_ARRAY_PUSH(*windows, svn_txdelta_window_t *) = window;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_plain(svn_stringbuf_t **content,
+ fs_fs_t *fs,
+ representation_t *representation,
+ apr_pool_t *pool)
+{
+ svn_string_t *data;
+ apr_size_t offset = representation->original.offset
+ + representation->header_size;
+
+ SVN_ERR(get_content(&data, fs, representation->revision->revision, pool));
+
+ *content = svn_stringbuf_ncreate(data->data + offset,
+ representation->original.size,
+ pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Get the undeltified window that is a result of combining all deltas
+ from the current desired representation identified in *RB with its
+ base representation. Store the window in *RESULT. */
+static svn_error_t *
+get_combined_window(svn_stringbuf_t **content,
+ fs_fs_t *fs,
+ representation_t *representation,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *windows;
+ svn_stringbuf_t *base_content, *result;
+ const char *source;
+ apr_pool_t *sub_pool = svn_pool_create(pool);
+ apr_pool_t *iter_pool = svn_pool_create(pool);
+
+ if (representation->is_plain)
+ return read_plain(content, fs, representation, pool);
+
+ *content = get_cached_window(fs, representation, pool);
+ if (*content)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(read_windows(&windows, fs, representation, sub_pool));
+ if (representation->delta_base && representation->delta_base->revision)
+ SVN_ERR(get_combined_window(&base_content, fs,
+ representation->delta_base, sub_pool));
+ else
+ base_content = svn_stringbuf_create_empty(sub_pool);
+
+ result = svn_stringbuf_create_empty(pool);
+ source = base_content->data;
+
+ for (i = 0; i < windows->nelts; ++i)
+ {
+ svn_txdelta_window_t *window
+ = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
+ svn_stringbuf_t *buf
+ = svn_stringbuf_create_ensure(window->tview_len, iter_pool);
+
+ buf->len = window->tview_len;
+ svn_txdelta_apply_instructions(window, window->src_ops ? source : NULL,
+ buf->data, &buf->len);
+
+ svn_stringbuf_appendbytes(result, buf->data, buf->len);
+ source += window->sview_len;
+
+ svn_pool_clear(iter_pool);
+ }
+
+ svn_pool_destroy(iter_pool);
+ svn_pool_destroy(sub_pool);
+
+ set_cached_window(fs, representation, result);
+ *content = result;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_noderev(noderev_t **noderev,
+ fs_fs_t *fs,
+ svn_stringbuf_t *file_content,
+ apr_size_t offset,
+ revision_info_t *revision_info,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool);
+
+static svn_error_t *
+get_noderev(noderev_t **noderev,
+ fs_fs_t *fs,
+ svn_stringbuf_t *file_content,
+ apr_size_t offset,
+ revision_info_t *revision_info,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ int idx = svn_sort__bsearch_lower_bound(&offset,
+ revision_info->node_revs,
+ compare_noderev_offsets);
+ if ((idx < 0) || (idx >= revision_info->node_revs->nelts))
+ SVN_ERR(read_noderev(noderev, fs, file_content, offset, revision_info,
+ pool, scratch_pool));
+ else
+ {
+ *noderev = APR_ARRAY_IDX(revision_info->node_revs, idx, noderev_t *);
+ if ((*noderev)->original.offset != offset)
+ SVN_ERR(read_noderev(noderev, fs, file_content, offset, revision_info,
+ pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_dir(apr_hash_t **hash,
+ fs_fs_t *fs,
+ representation_t *representation,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *text;
+ apr_pool_t *text_pool;
+ svn_stream_t *stream;
+ apr_pool_t *pool;
+
+ *hash = get_cached_dir(fs, representation);
+ if (*hash)
+ return SVN_NO_ERROR;
+
+ pool = get_cached_dir_pool(fs);
+ *hash = svn_hash__make(pool);
+ if (representation != NULL)
+ {
+ text_pool = svn_pool_create(scratch_pool);
+ SVN_ERR(get_combined_window(&text, fs, representation, text_pool));
+ stream = svn_stream_from_stringbuf(text, text_pool);
+ SVN_ERR(svn_hash_read2(*hash, stream, SVN_HASH_TERMINATOR, pool));
+ svn_pool_destroy(text_pool);
+ }
+
+ set_cached_dir(fs, representation, *hash);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+parse_dir(fs_fs_t *fs,
+ svn_stringbuf_t *file_content,
+ representation_t *representation,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *hash;
+ apr_hash_index_t *hi;
+ apr_pool_t *iter_pool = svn_pool_create(scratch_pool);
+ apr_hash_t *base_dir = svn_hash__make(scratch_pool);
+
+ if (representation == NULL)
+ return SVN_NO_ERROR;
+
+ if (representation->delta_base && representation->delta_base->dir)
+ {
+ apr_array_header_t *dir = representation->delta_base->dir->entries;
+ int i;
+
+ for (i = 0; i < dir->nelts; ++i)
+ {
+ direntry_t *entry = APR_ARRAY_IDX(dir, i, direntry_t *);
+ apr_hash_set(base_dir, entry->name, entry->name_len, entry);
+ }
+ }
+
+ SVN_ERR(read_dir(&hash, fs, representation, scratch_pool));
+
+ representation->dir = apr_pcalloc(pool, sizeof(*representation->dir));
+ representation->dir->entries
+ = apr_array_make(pool, apr_hash_count(hash), sizeof(direntry_t *));
+
+ /* Translate the string dir entries into real entries. */
+ for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *str_val = svn__apr_hash_index_val(hi);
+ apr_size_t offset;
+ revision_info_t *revision_info;
+ apr_size_t name_len = strlen(name);
+ direntry_t *entry = base_dir
+ ? apr_hash_get(base_dir, name, name_len)
+ : NULL;
+
+ SVN_ERR(parse_revnode_pos(&revision_info, &offset, fs, str_val));
+
+ if ( !entry
+ || !entry->node->text
+ || entry->node->text->revision != revision_info
+ || entry->node->original.offset != offset)
+ {
+ direntry_t *new_entry = apr_pcalloc(pool, sizeof(*entry));
+ new_entry->name_len = name_len;
+ if (entry)
+ new_entry->name = entry->name;
+ else
+ new_entry->name = apr_pstrdup(pool, name);
+
+ entry = new_entry;
+ SVN_ERR(get_noderev(&entry->node, fs, file_content, offset,
+ revision_info, pool, iter_pool));
+ }
+
+ APR_ARRAY_PUSH(representation->dir->entries, direntry_t *) = entry;
+ svn_pool_clear(iter_pool);
+ }
+
+ svn_pool_destroy(iter_pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_noderev(noderev_t **noderev,
+ fs_fs_t *fs,
+ svn_stringbuf_t *file_content,
+ apr_size_t offset,
+ revision_info_t *revision_info,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ noderev_t *result = apr_pcalloc(pool, sizeof(*result));
+ svn_string_t *line;
+ svn_boolean_t is_dir = FALSE;
+
+ scratch_pool = svn_pool_create(scratch_pool);
+
+ result->original.offset = offset;
+ while (1)
+ {
+ svn_string_t key;
+ svn_string_t value;
+ char *sep;
+ const char *start = file_content->data + offset
+ + revision_info->original.offset;
+ const char *end = strchr(start, '\n');
+
+ line = svn_string_ncreate(start, end - start, scratch_pool);
+ offset += end - start + 1;
+ if (line->len == 0)
+ break;
+
+ sep = strchr(line->data, ':');
+ if (sep == NULL)
+ continue;
+
+ key.data = line->data;
+ key.len = sep - key.data;
+ *sep = 0;
+
+ if (key.len + 2 > line->len)
+ continue;
+
+ value.data = sep + 2;
+ value.len = line->len - (key.len + 2);
+
+ if (key_matches(&key, "type"))
+ is_dir = strcmp(value.data, "dir") == 0;
+ else if (key_matches(&key, "pred"))
+ SVN_ERR(parse_pred(&result->predecessor, fs, &value));
+ else if (key_matches(&key, "text"))
+ SVN_ERR(parse_representation(&result->text, fs, file_content,
+ &value, revision_info,
+ pool, scratch_pool));
+ else if (key_matches(&key, "props"))
+ SVN_ERR(parse_representation(&result->props, fs, file_content,
+ &value, revision_info,
+ pool, scratch_pool));
+ }
+
+ result->revision = revision_info;
+ result->original.size = offset - result->original.offset;
+
+ svn_sort__array_insert(&result,
+ revision_info->node_revs,
+ svn_sort__bsearch_lower_bound(&offset,
+ revision_info->node_revs,
+ compare_noderev_offsets));
+
+ if (is_dir)
+ SVN_ERR(parse_dir(fs, file_content, result->text,
+ pool, scratch_pool));
+
+ svn_pool_destroy(scratch_pool);
+ *noderev = result;
+
+ return SVN_NO_ERROR;
+}
+
+static void print_progress(svn_revnum_t revision)
+{
+ printf("%8ld", revision);
+ fflush(stdout);
+}
+
+static svn_error_t *
+read_pack_file(fs_fs_t *fs,
+ svn_revnum_t base,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *manifest = NULL;
+ apr_pool_t *local_pool = svn_pool_create(pool);
+ apr_pool_t *iter_pool = svn_pool_create(local_pool);
+ int i;
+ svn_stringbuf_t *file_content;
+ revision_pack_t *revisions;
+ const char *pack_folder = get_pack_folder(fs, base, local_pool);
+ SVN_ERR(read_rev_or_pack_file(&file_content, fs, base, local_pool));
+
+ revisions = apr_pcalloc(pool, sizeof(*revisions));
+ revisions->base = base;
+ revisions->fragments = NULL;
+ revisions->info = apr_array_make(pool,
+ fs->max_files_per_dir,
+ sizeof(revision_info_t*));
+ revisions->filesize = file_content->len;
+ APR_ARRAY_PUSH(fs->packs, revision_pack_t*) = revisions;
+
+ SVN_ERR(read_manifest(&manifest, fs, pack_folder, local_pool));
+ if (manifest->nelts != fs->max_files_per_dir)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, NULL);
+
+ for (i = 0; i < manifest->nelts; ++i)
+ {
+ apr_size_t root_node_offset;
+ svn_string_t rev_content;
+
+ revision_info_t *info = apr_pcalloc(pool, sizeof(*info));
+ info->node_revs = apr_array_make(iter_pool, 4, sizeof(noderev_t*));
+ info->representations = apr_array_make(iter_pool, 4, sizeof(representation_t*));
+
+ info->revision = base + i;
+ info->original.offset = APR_ARRAY_IDX(manifest, i, apr_size_t);
+ info->original.end = i+1 < manifest->nelts
+ ? APR_ARRAY_IDX(manifest, i+1 , apr_size_t)
+ : file_content->len;
+ SVN_ERR(read_revision_header(&info->original.changes,
+ &info->original.changes_len,
+ &root_node_offset,
+ file_content,
+ APR_ARRAY_IDX(manifest, i , apr_size_t),
+ info->original.end,
+ iter_pool));
+
+ APR_ARRAY_PUSH(revisions->info, revision_info_t*) = info;
+ APR_ARRAY_PUSH(fs->revisions, revision_info_t*) = info;
+
+ rev_content.data = file_content->data + info->original.offset;
+ rev_content.len = info->original.end - info->original.offset;
+ set_cached_content(fs->cache, info->revision, &rev_content);
+
+ SVN_ERR(read_noderev(&info->root_noderev, fs, file_content,
+ root_node_offset, info, pool, iter_pool));
+
+ info->node_revs = apr_array_copy(pool, info->node_revs);
+ info->representations = apr_array_copy(pool, info->representations);
+
+ svn_pool_clear(iter_pool);
+ }
+
+ print_progress(base);
+ apr_pool_destroy(local_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_revision_file(fs_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_size_t root_node_offset;
+ apr_pool_t *local_pool = svn_pool_create(pool);
+ svn_stringbuf_t *file_content;
+ svn_string_t rev_content;
+ revision_pack_t *revisions = apr_pcalloc(pool, sizeof(*revisions));
+ revision_info_t *info = apr_pcalloc(pool, sizeof(*info));
+
+ SVN_ERR(read_rev_or_pack_file(&file_content, fs, revision, local_pool));
+
+ info->node_revs = apr_array_make(pool, 4, sizeof(noderev_t*));
+ info->representations = apr_array_make(pool, 4, sizeof(representation_t*));
+
+ info->revision = revision;
+ info->original.offset = 0;
+ info->original.end = file_content->len;
+ SVN_ERR(read_revision_header(&info->original.changes,
+ &info->original.changes_len,
+ &root_node_offset,
+ file_content,
+ 0,
+ info->original.end,
+ local_pool));
+
+ APR_ARRAY_PUSH(fs->revisions, revision_info_t*) = info;
+
+ revisions->base = revision;
+ revisions->fragments = NULL;
+ revisions->info = apr_array_make(pool, 1, sizeof(revision_info_t*));
+ revisions->filesize = file_content->len;
+ APR_ARRAY_PUSH(revisions->info, revision_info_t*) = info;
+ APR_ARRAY_PUSH(fs->packs, revision_pack_t*) = revisions;
+
+ rev_content.data = file_content->data + info->original.offset;
+ rev_content.len = info->original.end - info->original.offset;
+ set_cached_content(fs->cache, info->revision, &rev_content);
+
+ SVN_ERR(read_noderev(&info->root_noderev, fs, file_content,
+ root_node_offset, info,
+ pool, local_pool));
+ APR_ARRAY_PUSH(info->node_revs, noderev_t*) = info->root_noderev;
+
+ if (revision % fs->max_files_per_dir == 0)
+ print_progress(revision);
+
+ apr_pool_destroy(local_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_revisions(fs_fs_t **fs,
+ const char *path,
+ svn_revnum_t start_revision,
+ apr_size_t memsize,
+ apr_pool_t *pool)
+{
+ svn_revnum_t revision;
+ apr_size_t content_cache_size;
+ apr_size_t window_cache_size;
+ apr_size_t dir_cache_size;
+
+ /* determine cache sizes */
+
+ if (memsize < 100)
+ memsize = 100;
+
+ content_cache_size = memsize * 7 / 10 > 4000 ? 4000 : memsize * 7 / 10;
+ window_cache_size = memsize * 2 / 10 * 1024 * 1024;
+ dir_cache_size = (memsize / 10) * 16000;
+
+ SVN_ERR(fs_open(fs, path, pool));
+
+ (*fs)->start_revision = start_revision
+ - (start_revision % (*fs)->max_files_per_dir);
+ (*fs)->revisions = apr_array_make(pool,
+ (*fs)->max_revision + 1 - (*fs)->start_revision,
+ sizeof(revision_info_t *));
+ (*fs)->packs = apr_array_make(pool,
+ ((*fs)->min_unpacked_rev - (*fs)->start_revision)
+ / (*fs)->max_files_per_dir,
+ sizeof(revision_pack_t *));
+ (*fs)->null_base = apr_pcalloc(pool, sizeof(*(*fs)->null_base));
+ (*fs)->cache = create_content_cache
+ (apr_allocator_owner_get
+ (svn_pool_create_allocator(FALSE)),
+ content_cache_size * 1024 * 1024);
+ (*fs)->dir_cache = create_dir_cache
+ (apr_allocator_owner_get
+ (svn_pool_create_allocator(FALSE)),
+ dir_cache_size);
+ (*fs)->window_cache = create_window_cache
+ (apr_allocator_owner_get
+ (svn_pool_create_allocator(FALSE)),
+ 10000, window_cache_size);
+
+ for ( revision = start_revision
+ ; revision < (*fs)->min_unpacked_rev
+ ; revision += (*fs)->max_files_per_dir)
+ SVN_ERR(read_pack_file(*fs, revision, pool));
+
+ for ( ; revision <= (*fs)->max_revision; ++revision)
+ SVN_ERR(read_revision_file(*fs, revision, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static apr_size_t
+get_max_offset_len(const revision_pack_t *pack)
+{
+ apr_size_t max_future_size = pack->filesize * 2 + 10000;
+ apr_size_t result = 0;
+
+ while (max_future_size > 0)
+ {
+ ++result;
+ max_future_size /= 10;
+ }
+
+ return result;
+}
+
+static svn_error_t *
+add_revisions_pack_heads(revision_pack_t *pack,
+ apr_pool_t *pool)
+{
+ int i;
+ revision_info_t *info;
+ apr_size_t offset_len = get_max_offset_len(pack);
+ fragment_t fragment;
+
+ /* allocate fragment arrays */
+
+ int fragment_count = 1;
+ for (i = 0; i < pack->info->nelts; ++i)
+ {
+ info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
+ fragment_count += info->node_revs->nelts
+ + info->representations->nelts
+ + 2;
+ }
+
+ pack->target_offset = pack->info->nelts > 1 ? 64 : 0;
+ pack->fragments = apr_array_make(pool,
+ fragment_count,
+ sizeof(fragment_t));
+
+ /* put revision headers first */
+
+ for (i = 0; i < pack->info->nelts - 1; ++i)
+ {
+ info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
+ info->target.offset = pack->target_offset;
+
+ fragment.data = info;
+ fragment.kind = header_fragment;
+ fragment.position = pack->target_offset;
+ APR_ARRAY_PUSH(pack->fragments, fragment_t) = fragment;
+
+ pack->target_offset += 2 * offset_len + 3;
+ }
+
+ info = APR_ARRAY_IDX(pack->info, pack->info->nelts - 1, revision_info_t*);
+ info->target.offset = pack->target_offset;
+
+ /* followed by the changes list */
+
+ for (i = 0; i < pack->info->nelts; ++i)
+ {
+ info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
+
+ info->target.changes = pack->target_offset - info->target.offset;
+ info->target.changes_len = info->original.changes_len;
+
+ fragment.data = info;
+ fragment.kind = changes_fragment;
+ fragment.position = pack->target_offset;
+ APR_ARRAY_PUSH(pack->fragments, fragment_t) = fragment;
+
+ pack->target_offset += info->original.changes_len;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_target_offset(apr_size_t **current_pos,
+ apr_array_header_t **fragments,
+ fs_fs_t *fs,
+ revision_info_t *info)
+{
+ int i;
+ revision_pack_t *pack;
+ svn_revnum_t revision = info->revision;
+
+ if (fs->min_unpacked_rev > revision)
+ {
+ i = (revision - fs->start_revision) / fs->max_files_per_dir;
+ }
+ else
+ {
+ i = (fs->min_unpacked_rev - fs->start_revision) / fs->max_files_per_dir;
+ i += revision - fs->min_unpacked_rev;
+ }
+
+ pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
+ *current_pos = &pack->target_offset;
+ *fragments = pack->fragments;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_noderev_recursively(fs_fs_t *fs,
+ noderev_t *node,
+ apr_pool_t *pool);
+
+static svn_error_t *
+add_representation_recursively(fs_fs_t *fs,
+ representation_t *representation,
+ enum fragment_kind_t kind,
+ apr_pool_t *pool)
+{
+ apr_size_t *current_pos;
+ apr_array_header_t *fragments;
+ fragment_t fragment;
+
+ if ( representation == NULL
+ || representation->covered
+ || (representation->dir && kind != dir_fragment)
+ || representation == fs->null_base)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_target_offset(¤t_pos, &fragments,
+ fs, representation->revision));
+ representation->target.offset = *current_pos;
+ representation->covered = TRUE;
+
+ fragment.data = representation;
+ fragment.kind = kind;
+ fragment.position = *current_pos;
+ APR_ARRAY_PUSH(fragments, fragment_t) = fragment;
+
+ if ( kind != dir_fragment
+ && representation->delta_base && representation->delta_base->dir)
+ {
+ apr_pool_t *text_pool = svn_pool_create(pool);
+ svn_stringbuf_t *content;
+
+ get_combined_window(&content, fs, representation, text_pool);
+ representation->target.size = content->len;
+ *current_pos += representation->target.size + 13;
+
+ svn_pool_destroy(text_pool);
+ }
+ else
+ if ( kind == dir_fragment
+ || (representation->delta_base && representation->delta_base->dir))
+ {
+ if (representation->original.size < 50)
+ *current_pos += 300;
+ else
+ *current_pos += representation->original.size * 3 + 150;
+ }
+ else
+ {
+ representation->target.size = representation->original.size;
+
+ if (representation->delta_base &&
+ (representation->delta_base != fs->null_base))
+ *current_pos += representation->original.size + 50;
+ else
+ *current_pos += representation->original.size + 13;
+ }
+
+ if (representation->delta_base)
+ SVN_ERR(add_representation_recursively(fs,
+ representation->delta_base,
+ kind,
+ pool));
+
+ if (representation->dir)
+ {
+ int i;
+ apr_array_header_t *entries = representation->dir->entries;
+
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ direntry_t *entry = APR_ARRAY_IDX(entries, i, direntry_t *);
+ if (entry->node)
+ SVN_ERR(add_noderev_recursively(fs, entry->node, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_noderev_recursively(fs_fs_t *fs,
+ noderev_t *node,
+ apr_pool_t *pool)
+{
+ apr_size_t *current_pos;
+ apr_array_header_t *fragments;
+ fragment_t fragment;
+
+ if (node->covered)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(get_target_offset(¤t_pos, &fragments, fs, node->revision));
+ node->covered = TRUE;
+ node->target.offset = *current_pos;
+
+ fragment.data = node;
+ fragment.kind = noderep_fragment;
+ fragment.position = *current_pos;
+ APR_ARRAY_PUSH(fragments, fragment_t) = fragment;
+
+ *current_pos += node->original.size + 40;
+
+ if (node->text && node->text->dir)
+ SVN_ERR(add_representation_recursively(fs, node->text, dir_fragment, pool));
+ else
+ SVN_ERR(add_representation_recursively(fs, node->text, file_fragment, pool));
+
+ SVN_ERR(add_representation_recursively(fs, node->props, property_fragment, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_revisions_pack_tail(revision_pack_t *pack,
+ apr_pool_t *pool)
+{
+ int i;
+ revision_info_t *info;
+ apr_size_t offset_len = get_max_offset_len(pack);
+ fragment_t fragment;
+
+ /* put final revision header last and fix up revision lengths */
+
+ info = APR_ARRAY_IDX(pack->info, pack->info->nelts-1, revision_info_t*);
+
+ fragment.data = info;
+ fragment.kind = header_fragment;
+ fragment.position = pack->target_offset;
+ APR_ARRAY_PUSH(pack->fragments, fragment_t) = fragment;
+
+ pack->target_offset += 2 * offset_len + 3;
+
+ for (i = 0; i < pack->info->nelts; ++i)
+ {
+ info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
+ info->target.end = pack->target_offset;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reorder_revisions(fs_fs_t *fs,
+ apr_pool_t *pool)
+{
+ int i, k;
+
+ /* headers and changes */
+
+ for (i = 0; i < fs->packs->nelts; ++i)
+ {
+ revision_pack_t *pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
+ SVN_ERR(add_revisions_pack_heads(pack, pool));
+ }
+
+ /* representations & nodes */
+
+ for (i = fs->revisions->nelts-1; i >= 0; --i)
+ {
+ revision_info_t *info = APR_ARRAY_IDX(fs->revisions, i, revision_info_t*);
+ for (k = info->node_revs->nelts - 1; k >= 0; --k)
+ {
+ noderev_t *node = APR_ARRAY_IDX(info->node_revs, k, noderev_t*);
+ SVN_ERR(add_noderev_recursively(fs, node, pool));
+ }
+
+ if (info->revision % fs->max_files_per_dir == 0)
+ print_progress(info->revision);
+ }
+
+ /* pack file tails */
+
+ for (i = 0; i < fs->packs->nelts; ++i)
+ {
+ revision_pack_t *pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
+ SVN_ERR(add_revisions_pack_tail(pack, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_fragment_content(svn_string_t **content,
+ fs_fs_t *fs,
+ fragment_t *fragment,
+ apr_pool_t *pool);
+
+static svn_error_t *
+update_noderevs(fs_fs_t *fs,
+ revision_pack_t *pack,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_pool_t *itempool = svn_pool_create(pool);
+
+ for (i = 0; i < pack->fragments->nelts; ++i)
+ {
+ fragment_t *fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
+ if (fragment->kind == dir_fragment)
+ {
+ svn_string_t *content;
+
+ SVN_ERR(get_fragment_content(&content, fs, fragment, itempool));
+ svn_pool_clear(itempool);
+ }
+ }
+
+ svn_pool_destroy(itempool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_content_length(apr_size_t *length,
+ fs_fs_t *fs,
+ fragment_t *fragment,
+ svn_boolean_t add_padding,
+ apr_pool_t *pool)
+{
+ svn_string_t *content;
+
+ SVN_ERR(get_fragment_content(&content, fs, fragment, pool));
+ if (add_padding)
+ switch (fragment->kind)
+ {
+ case dir_fragment:
+ *length = content->len + 16;
+ break;
+ case noderep_fragment:
+ *length = content->len + 3;
+ break;
+ default:
+ *length = content->len;
+ break;
+ }
+ else
+ *length = content->len;
+
+ return SVN_NO_ERROR;
+}
+
+static void
+move_fragment(fragment_t *fragment,
+ apr_size_t new_position)
+{
+ revision_info_t *info;
+ representation_t *representation;
+ noderev_t *node;
+
+ fragment->position = new_position;
+
+ switch (fragment->kind)
+ {
+ case header_fragment:
+ info = fragment->data;
+ info->target.offset = new_position;
+ break;
+
+ case changes_fragment:
+ info = fragment->data;
+ info->target.changes = new_position - info->target.offset;
+ break;
+
+ case property_fragment:
+ case file_fragment:
+ case dir_fragment:
+ representation = fragment->data;
+ representation->target.offset = new_position;
+ break;
+
+ case noderep_fragment:
+ node = fragment->data;
+ node->target.offset = new_position;
+ break;
+ }
+}
+
+static svn_error_t *
+pack_revisions(fs_fs_t *fs,
+ revision_pack_t *pack,
+ apr_pool_t *pool)
+{
+ int i;
+ fragment_t *fragment, *next;
+ svn_boolean_t needed_to_expand;
+ revision_info_t *info;
+ apr_size_t current_pos, len, old_len;
+
+ apr_pool_t *itempool = svn_pool_create(pool);
+
+ SVN_ERR(update_noderevs(fs, pack, pool));
+
+ current_pos = pack->info->nelts > 1 ? 64 : 0;
+ for (i = 0; i + 1 < pack->fragments->nelts; ++i)
+ {
+ fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
+ SVN_ERR(get_content_length(&len, fs, fragment, TRUE, itempool));
+ move_fragment(fragment, current_pos);
+ current_pos += len;
+
+ svn_pool_clear(itempool);
+ }
+
+ fragment = &APR_ARRAY_IDX(pack->fragments, pack->fragments->nelts-1, fragment_t);
+ fragment->position = current_pos;
+
+ do
+ {
+ needed_to_expand = FALSE;
+ current_pos = pack->info->nelts > 1 ? 64 : 0;
+
+ for (i = 0; i + 1 < pack->fragments->nelts; ++i)
+ {
+ fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
+ next = &APR_ARRAY_IDX(pack->fragments, i + 1, fragment_t);
+ old_len = next->position - fragment->position;
+
+ SVN_ERR(get_content_length(&len, fs, fragment, FALSE, itempool));
+
+ if (len > old_len)
+ {
+ len = (apr_size_t)(len * 1.1) + 10;
+ needed_to_expand = TRUE;
+ }
+ else
+ len = old_len;
+
+ if (i == pack->info->nelts - 1)
+ {
+ info = APR_ARRAY_IDX(pack->info, pack->info->nelts - 1, revision_info_t*);
+ info->target.offset = current_pos;
+ }
+
+ move_fragment(fragment, current_pos);
+ current_pos += len;
+
+ svn_pool_clear(itempool);
+ }
+
+ fragment = &APR_ARRAY_IDX(pack->fragments, pack->fragments->nelts-1, fragment_t);
+ fragment->position = current_pos;
+
+ SVN_ERR(get_content_length(&len, fs, fragment, FALSE, itempool));
+ current_pos += len;
+
+ for (i = 0; i < pack->info->nelts; ++i)
+ {
+ info = APR_ARRAY_IDX(pack->info, i, revision_info_t*);
+ info->target.end = current_pos;
+ }
+ }
+ while (needed_to_expand);
+
+ svn_pool_destroy(itempool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_revisions(fs_fs_t *fs,
+ revision_pack_t *pack,
+ apr_pool_t *pool)
+{
+ int i;
+ fragment_t *fragment = NULL;
+ svn_string_t *content;
+
+ apr_pool_t *itempool = svn_pool_create(pool);
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ apr_file_t *file;
+ apr_size_t current_pos = 0;
+ svn_stringbuf_t *null_buffer = svn_stringbuf_create_empty(iterpool);
+
+ const char *dir = apr_psprintf(iterpool, "%s/new/%ld%s",
+ fs->path, pack->base / fs->max_files_per_dir,
+ pack->info->nelts > 1 ? ".pack" : "");
+ SVN_ERR(svn_io_make_dir_recursively(dir, pool));
+ SVN_ERR(svn_io_file_open(&file,
+ pack->info->nelts > 1
+ ? apr_psprintf(iterpool, "%s/pack", dir)
+ : apr_psprintf(iterpool, "%s/%ld", dir, pack->base),
+ APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT,
+ iterpool));
+
+ for (i = 0; i < pack->fragments->nelts; ++i)
+ {
+ apr_size_t padding;
+ fragment = &APR_ARRAY_IDX(pack->fragments, i, fragment_t);
+ SVN_ERR(get_fragment_content(&content, fs, fragment, itempool));
+
+ SVN_ERR_ASSERT(fragment->position >= current_pos);
+ if ( fragment->kind == header_fragment
+ && i+1 < pack->fragments->nelts)
+ padding = APR_ARRAY_IDX(pack->fragments, i+1, fragment_t).position -
+ content->len - current_pos;
+ else
+ padding = fragment->position - current_pos;
+
+ if (padding)
+ {
+ while (null_buffer->len < padding)
+ svn_stringbuf_appendbyte(null_buffer, 0);
+
+ SVN_ERR(svn_io_file_write_full(file,
+ null_buffer->data,
+ padding,
+ NULL,
+ itempool));
+ current_pos += padding;
+ }
+
+ SVN_ERR(svn_io_file_write_full(file,
+ content->data,
+ content->len,
+ NULL,
+ itempool));
+ current_pos += content->len;
+
+ svn_pool_clear(itempool);
+ }
+
+ apr_file_close(file);
+
+ if (pack->info->nelts > 1)
+ {
+ svn_stream_t *stream;
+ SVN_ERR(svn_io_file_open(&file,
+ apr_psprintf(iterpool, "%s/manifest", dir),
+ APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_OS_DEFAULT,
+ iterpool));
+ stream = svn_stream_from_aprfile2(file, FALSE, iterpool);
+
+ for (i = 0; i < pack->info->nelts; ++i)
+ {
+ revision_info_t *info = APR_ARRAY_IDX(pack->info, i,
+ revision_info_t *);
+ SVN_ERR(svn_stream_printf(stream, itempool,
+ "%" APR_UINT64_T_FMT "\n",
+ info->target.offset));
+ svn_pool_clear(itempool);
+ }
+ }
+
+ svn_pool_destroy(itempool);
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+pack_and_write_revisions(fs_fs_t *fs,
+ apr_pool_t *pool)
+{
+ int i;
+
+ SVN_ERR(svn_io_make_dir_recursively(apr_psprintf(pool, "%s/new",
+ fs->path),
+ pool));
+
+ for (i = 0; i < fs->packs->nelts; ++i)
+ {
+ revision_pack_t *pack = APR_ARRAY_IDX(fs->packs, i, revision_pack_t*);
+ if (pack->base % fs->max_files_per_dir == 0)
+ print_progress(pack->base);
+
+ SVN_ERR(pack_revisions(fs, pack, pool));
+ SVN_ERR(write_revisions(fs, pack, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_updated_dir(svn_string_t **content,
+ fs_fs_t *fs,
+ representation_t *representation,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *hash;
+ apr_pool_t *hash_pool = svn_pool_create(scratch_pool);
+ apr_array_header_t *dir = representation->dir->entries;
+ int i;
+ svn_stream_t *stream;
+ svn_stringbuf_t *result;
+
+ SVN_ERR(read_dir(&hash, fs, representation, scratch_pool));
+ hash = apr_hash_copy(hash_pool, hash);
+ for (i = 0; i < dir->nelts; ++i)
+ {
+ char buffer[256];
+ svn_string_t *new_val;
+ apr_size_t pos;
+ direntry_t *entry = APR_ARRAY_IDX(dir, i, direntry_t *);
+ svn_string_t *str_val = apr_hash_get(hash, entry->name, entry->name_len);
+ if (str_val == NULL)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Dir entry '%s' not found"), entry->name);
+
+ SVN_ERR_ASSERT(str_val->len < sizeof(buffer));
+
+ memcpy(buffer, str_val->data, str_val->len+1);
+ pos = strchr(buffer, '/') - buffer + 1;
+ pos += svn__ui64toa(buffer + pos, entry->node->target.offset - entry->node->revision->target.offset);
+ new_val = svn_string_ncreate(buffer, pos, hash_pool);
+
+ apr_hash_set(hash, entry->name, entry->name_len, new_val);
+ }
+
+ result = svn_stringbuf_create_ensure(representation->target.size, pool);
+ stream = svn_stream_from_stringbuf(result, hash_pool);
+ SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, hash_pool));
+ svn_pool_destroy(hash_pool);
+
+ *content = svn_stringbuf__morph_into_string(result);
+
+ return SVN_NO_ERROR;
+}
+
+struct diff_write_baton_t
+{
+ svn_stream_t *stream;
+ apr_size_t size;
+};
+
+static svn_error_t *
+diff_write_handler(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct diff_write_baton_t *whb = baton;
+
+ SVN_ERR(svn_stream_write(whb->stream, data, len));
+ whb->size += *len;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+diff_stringbufs(svn_stringbuf_t *diff,
+ apr_size_t *inflated_size,
+ svn_string_t *base,
+ svn_string_t *content,
+ apr_pool_t *pool)
+{
+ svn_txdelta_window_handler_t diff_wh;
+ void *diff_whb;
+ struct diff_write_baton_t whb;
+
+ svn_stream_t *stream;
+ svn_stream_t *source = svn_stream_from_string(base, pool);
+ svn_stream_t *target = svn_stream_from_stringbuf(diff, pool);
+
+ /* Prepare to write the svndiff data. */
+ svn_txdelta_to_svndiff3(&diff_wh,
+ &diff_whb,
+ target,
+ 1,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
+ pool);
+
+ whb.stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
+ whb.size = 0;
+
+ stream = svn_stream_create(&whb, pool);
+ svn_stream_set_write(stream, diff_write_handler);
+
+ SVN_ERR(svn_stream_write(stream, content->data, &content->len));
+ SVN_ERR(svn_stream_close(whb.stream));
+ SVN_ERR(svn_stream_close(stream));
+
+ *inflated_size = whb.size;
+ return SVN_NO_ERROR;
+}
+
+static void
+update_id(svn_stringbuf_t *node_rev,
+ const char *key,
+ noderev_t *node)
+{
+ char *newline_pos = 0;
+ char *pos;
+
+ pos = strstr(node_rev->data, key);
+ if (pos)
+ pos = strchr(pos, '/');
+ if (pos)
+ newline_pos = strchr(++pos, '\n');
+
+ if (pos && newline_pos)
+ {
+ char temp[SVN_INT64_BUFFER_SIZE];
+ apr_size_t len = svn__i64toa(temp, node->target.offset - node->revision->target.offset);
+ svn_stringbuf_replace(node_rev,
+ pos - node_rev->data, newline_pos - pos,
+ temp, len);
+ }
+}
+
+static void
+update_text(svn_stringbuf_t *node_rev,
+ const char *key,
+ representation_t *representation,
+ apr_pool_t *scratch_pool)
+{
+ apr_size_t key_len = strlen(key);
+ char *pos = strstr(node_rev->data, key);
+ char *val_pos;
+
+ if (!pos)
+ return;
+
+ val_pos = pos + key_len;
+ if (representation->dir)
+ {
+ char *newline_pos = strchr(val_pos, '\n');
+ svn_checksum_t checksum = {representation->dir->target_md5,
+ svn_checksum_md5};
+ const char* temp = apr_psprintf(scratch_pool, "%ld %" APR_UINT64_T_FMT " %"
+ APR_UINT64_T_FMT" %" APR_SIZE_T_FMT " %s",
+ representation->revision->revision,
+ representation->target.offset - representation->revision->target.offset,
+ representation->target.size,
+ representation->dir->size,
+ svn_checksum_to_cstring(&checksum,
+ scratch_pool));
+
+ svn_stringbuf_replace(node_rev,
+ val_pos - node_rev->data, newline_pos - val_pos,
+ temp, strlen(temp));
+ }
+ else
+ {
+ const char* temp;
+ char *end_pos = strchr(val_pos, ' ');
+
+ val_pos = end_pos + 1;
+ end_pos = strchr(strchr(val_pos, ' ') + 1, ' ');
+ temp = apr_psprintf(scratch_pool, "%" APR_UINT64_T_FMT " %" APR_UINT64_T_FMT,
+ representation->target.offset - representation->revision->target.offset,
+ representation->target.size);
+
+ svn_stringbuf_replace(node_rev,
+ val_pos - node_rev->data, end_pos - val_pos,
+ temp, strlen(temp));
+ }
+}
+
+static svn_error_t *
+get_fragment_content(svn_string_t **content,
+ fs_fs_t *fs,
+ fragment_t *fragment,
+ apr_pool_t *pool)
+{
+ revision_info_t *info;
+ representation_t *representation;
+ noderev_t *node;
+ svn_string_t *revision_content, *base_content;
+ svn_stringbuf_t *header, *node_rev, *text;
+ apr_size_t header_size;
+ svn_checksum_t *checksum = NULL;
+
+ switch (fragment->kind)
+ {
+ case header_fragment:
+ info = fragment->data;
+ *content = svn_string_createf(pool,
+ "\n%" APR_UINT64_T_FMT " %" APR_UINT64_T_FMT "\n",
+ info->root_noderev->target.offset - info->target.offset,
+ info->target.changes);
+ return SVN_NO_ERROR;
+
+ case changes_fragment:
+ info = fragment->data;
+ SVN_ERR(get_content(&revision_content, fs, info->revision, pool));
+
+ *content = svn_string_create_empty(pool);
+ (*content)->data = revision_content->data + info->original.changes;
+ (*content)->len = info->target.changes_len;
+ return SVN_NO_ERROR;
+
+ case property_fragment:
+ case file_fragment:
+ representation = fragment->data;
+ SVN_ERR(get_content(&revision_content, fs,
+ representation->revision->revision, pool));
+
+ if (representation->delta_base)
+ if (representation->delta_base->dir)
+ {
+ SVN_ERR(get_combined_window(&text, fs, representation, pool));
+ representation->target.size = text->len;
+
+ svn_stringbuf_insert(text, 0, "PLAIN\n", 6);
+ svn_stringbuf_appendcstr(text, "ENDREP\n");
+ *content = svn_stringbuf__morph_into_string(text);
+
+ return SVN_NO_ERROR;
+ }
+ else
+ if (representation->delta_base == fs->null_base)
+ header = svn_stringbuf_create("DELTA\n", pool);
+ else
+ header = svn_stringbuf_createf(pool,
+ "DELTA %ld %" APR_UINT64_T_FMT " %" APR_UINT64_T_FMT "\n",
+ representation->delta_base->revision->revision,
+ representation->delta_base->target.offset
+ - representation->delta_base->revision->target.offset,
+ representation->delta_base->target.size);
+ else
+ header = svn_stringbuf_create("PLAIN\n", pool);
+
+ header_size = strchr(revision_content->data +
+ representation->original.offset, '\n') -
+ revision_content->data -
+ representation->original.offset + 1;
+ svn_stringbuf_appendbytes(header,
+ revision_content->data +
+ representation->original.offset +
+ header_size,
+ representation->original.size);
+ svn_stringbuf_appendcstr(header, "ENDREP\n");
+ *content = svn_stringbuf__morph_into_string(header);
+ return SVN_NO_ERROR;
+
+ case dir_fragment:
+ representation = fragment->data;
+ SVN_ERR(get_updated_dir(&revision_content, fs, representation,
+ pool, pool));
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_md5,
+ revision_content->data, revision_content->len,
+ pool));
+ memcpy(representation->dir->target_md5,
+ checksum->digest,
+ sizeof(representation->dir->target_md5));
+
+ if (representation->delta_base)
+ {
+ if (representation->delta_base->dir == NULL)
+ {
+ header = svn_stringbuf_create("DELTA\n", pool);
+ base_content = svn_string_create_empty(pool);
+ }
+ else
+ {
+ representation_t *base_rep = representation->delta_base;
+ header = svn_stringbuf_createf(pool,
+ "DELTA %ld %" APR_UINT64_T_FMT " %" APR_UINT64_T_FMT "\n",
+ base_rep->revision->revision,
+ base_rep->target.offset - base_rep->revision->target.offset,
+ base_rep->target.size);
+ SVN_ERR(get_updated_dir(&base_content, fs, base_rep,
+ pool, pool));
+ }
+
+ header_size = header->len;
+ SVN_ERR(diff_stringbufs(header, &representation->dir->size,
+ base_content,
+ revision_content, pool));
+ representation->target.size = header->len - header_size;
+ svn_stringbuf_appendcstr(header, "ENDREP\n");
+ *content = svn_stringbuf__morph_into_string(header);
+ }
+ else
+ {
+ representation->target.size = revision_content->len;
+ representation->dir->size = revision_content->len;
+ *content = svn_string_createf(pool, "PLAIN\n%sENDREP\n",
+ revision_content->data);
+ }
+
+ return SVN_NO_ERROR;
+
+ case noderep_fragment:
+ node = fragment->data;
+ SVN_ERR(get_content(&revision_content, fs,
+ node->revision->revision, pool));
+ node_rev = svn_stringbuf_ncreate(revision_content->data +
+ node->original.offset,
+ node->original.size,
+ pool);
+
+ update_id(node_rev, "id: ", node);
+ update_id(node_rev, "pred: ", node->predecessor);
+ update_text(node_rev, "text: ", node->text, pool);
+ update_text(node_rev, "props: ", node->props, pool);
+
+ *content = svn_stringbuf__morph_into_string(node_rev);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR_ASSERT(0);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+prepare_repo(const char *path, apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ const char *old_path = svn_dirent_join(path, "db/old", pool);
+ const char *new_path = svn_dirent_join(path, "new", pool);
+ const char *revs_path = svn_dirent_join(path, "db/revs", pool);
+ const char *old_rep_cache_path = svn_dirent_join(path, "db/rep-cache.db.old", pool);
+ const char *rep_cache_path = svn_dirent_join(path, "db/rep-cache.db", pool);
+
+ SVN_ERR(svn_io_check_path(old_path, &kind, pool));
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(svn_io_remove_dir2(new_path, TRUE, NULL, NULL, pool));
+ SVN_ERR(svn_io_file_move(revs_path, new_path, pool));
+ SVN_ERR(svn_io_file_move(old_path, revs_path, pool));
+ SVN_ERR(svn_io_remove_dir2(new_path, TRUE, NULL, NULL, pool));
+ }
+
+ SVN_ERR(svn_io_check_path(old_rep_cache_path, &kind, pool));
+ if (kind == svn_node_file)
+ SVN_ERR(svn_io_file_move(old_rep_cache_path, rep_cache_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+activate_new_revs(const char *path, apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ const char *old_path = svn_dirent_join(path, "db/old", pool);
+ const char *new_path = svn_dirent_join(path, "new", pool);
+ const char *revs_path = svn_dirent_join(path, "db/revs", pool);
+ const char *old_rep_cache_path = svn_dirent_join(path, "db/rep-cache.db.old", pool);
+ const char *rep_cache_path = svn_dirent_join(path, "db/rep-cache.db", pool);
+
+ SVN_ERR(svn_io_check_path(old_path, &kind, pool));
+ if (kind == svn_node_none)
+ {
+ SVN_ERR(svn_io_file_move(revs_path, old_path, pool));
+ SVN_ERR(svn_io_file_move(new_path, revs_path, pool));
+ }
+
+ SVN_ERR(svn_io_check_path(old_rep_cache_path, &kind, pool));
+ if (kind == svn_node_none)
+ SVN_ERR(svn_io_file_move(rep_cache_path, old_rep_cache_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static void
+print_usage(svn_stream_t *ostream, const char *progname,
+ apr_pool_t *pool)
+{
+ svn_error_clear(svn_stream_printf(ostream, pool,
+ "\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ "!!! This is an experimental tool. Don't use it on production data !!!\n"
+ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ "\n"
+ "Usage: %s <repo> <cachesize>\n"
+ "\n"
+ "Optimize the repository at local path <repo> staring from revision 0.\n"
+ "Use up to <cachesize> MB of memory for caching. This does not include\n"
+ "temporary representation of the repository structure, i.e. the actual\n"
+ "memory will be higher and <cachesize> be the lower limit.\n",
+ progname));
+}
+
+int main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ svn_stream_t *ostream;
+ svn_error_t *svn_err;
+ const char *repo_path = NULL;
+ svn_revnum_t start_revision = 0;
+ apr_size_t memsize = 0;
+ apr_uint64_t temp = 0;
+ fs_fs_t *fs;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ svn_err = svn_stream_for_stdout(&ostream, pool);
+ if (svn_err)
+ {
+ svn_handle_error2(svn_err, stdout, FALSE, ERROR_TAG);
+ return 2;
+ }
+
+ if (argc != 3)
+ {
+ print_usage(ostream, argv[0], pool);
+ return 2;
+ }
+
+ svn_err = svn_cstring_strtoui64(&temp, argv[2], 0, APR_SIZE_MAX, 10);
+ if (svn_err)
+ {
+ print_usage(ostream, argv[0], pool);
+ svn_error_clear(svn_err);
+ return 2;
+ }
+
+ memsize = (apr_size_t)temp;
+ repo_path = argv[1];
+ start_revision = 0;
+
+ printf("\nPreparing repository\n");
+ svn_err = prepare_repo(repo_path, pool);
+
+ if (!svn_err)
+ {
+ printf("Reading revisions\n");
+ svn_err = read_revisions(&fs, repo_path, start_revision, memsize, pool);
+ }
+
+ if (!svn_err)
+ {
+ printf("\nReordering revision content\n");
+ svn_err = reorder_revisions(fs, pool);
+ }
+
+ if (!svn_err)
+ {
+ printf("\nPacking and writing revisions\n");
+ svn_err = pack_and_write_revisions(fs, pool);
+ }
+
+ if (!svn_err)
+ {
+ printf("\nSwitch to new revs\n");
+ svn_err = activate_new_revs(repo_path, pool);
+ }
+
+ if (svn_err)
+ {
+ svn_handle_error2(svn_err, stdout, FALSE, ERROR_TAG);
+ return 2;
+ }
+
+ return 0;
+}
diff --git a/tools/server-side/svnauthz-validate.c b/tools/server-side/svnauthz-validate.c
index b5bfdae..4af0f77 100644
--- a/tools/server-side/svnauthz-validate.c
+++ b/tools/server-side/svnauthz-validate.c
@@ -28,32 +28,56 @@
*
*/
+#include "svn_cmdline.h"
+#include "svn_dirent_uri.h"
+#include "svn_opt.h"
#include "svn_pools.h"
#include "svn_repos.h"
-#include "svn_cmdline.h"
+#include "svn_utf.h"
+
+enum {
+ OPT_USERNAME = SVN_OPT_FIRST_LONGOPT_ID,
+ OPT_PATH,
+ OPT_REPOS
+};
+
+static int
+usage(const char *argv0)
+{
+ printf("Usage: %s FILE [--username USER [--path FSPATH] [--repository REPOS_NAME]] FILE\n\n", argv0);
+ printf("Loads the authz file at FILE and validates its syntax.\n"
+ "Optionally prints the access available to USER for FSPATH in\n"
+ "repository with authz name REPOS_NAME. If FSPATH is omitted, reports\n"
+ "whether USER has any access at all.\n"
+ "Returns:\n"
+ " 0 when syntax is OK.\n"
+ " 1 when syntax is invalid.\n"
+ " 2 operational error\n");
+ return 2;
+}
int
main(int argc, const char **argv)
{
apr_pool_t *pool;
svn_error_t *err;
+ apr_status_t apr_err;
svn_authz_t *authz;
- const char *authz_file;
-
- if (argc != 2 && argc != 4 && argc != 5)
+ apr_getopt_t *os;
+ const apr_getopt_option_t options[] =
{
- printf("Usage: %s FILE [USER PATH [REPOS_NAME]]\n\n", argv[0]);
- printf("Loads the authz file at FILE and validates its syntax.\n"
- "Optionally reports the access available to USER for PATH in\n"
- "repository REPOS_NAME.\n"
- "Returns:\n"
- " 0 when syntax is OK.\n"
- " 1 when syntax is invalid.\n"
- " 2 operational error\n");
- return 2;
- }
-
- authz_file = argv[1];
+ {"username", OPT_USERNAME, 1, ("the authenticated username")},
+ {"path", OPT_PATH, 1, ("path within the repository")},
+ {"repository", OPT_REPOS, 1, ("repository authz name")},
+ {0, 0, 0, 0}
+ };
+ struct {
+ const char *authz_file;
+ const char *username;
+ const char *fspath;
+ const char *repos_name;
+ } opts;
+ opts.username = opts.fspath = opts.repos_name = NULL;
/* Initialize the app. Send all error messages to 'stderr'. */
if (svn_cmdline_init(argv[0], stderr) != EXIT_SUCCESS)
@@ -61,16 +85,69 @@
pool = svn_pool_create(NULL);
- /* Read the access file and validate it. */
- err = svn_repos_authz_read(&authz, authz_file, TRUE, pool);
+ /* Repeat svn_cmdline__getopt_init() inline. */
+ apr_err = apr_getopt_init(&os, pool, argc, argv);
+ if (apr_err)
+ return svn_cmdline_handle_exit_error(
+ svn_error_wrap_apr(apr_err,
+ ("Error initializing command line arguments")),
+ pool, "svn-rep-sharing-stats: ");
- if (!err && (argc == 4 || argc == 5))
+ os->interleave = 1;
+ while (1)
{
- const char *user = argv[2], *path = argv[3];
- const char *repos = argc == 5 ? argv[4] : "";
+ int opt;
+ const char *arg;
+ apr_status_t status = apr_getopt_long(os, options, &opt, &arg);
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ {
+ return usage(argv[0]);
+ }
+ switch (opt)
+ {
+ case OPT_USERNAME:
+ /* ### TODO: UTF-8? */
+ opts.username = arg;
+ break;
+ case OPT_PATH:
+ /* ### TODO: UTF-8? */
+ opts.fspath = arg;
+ break;
+ case OPT_REPOS:
+ opts.repos_name = arg;
+ break;
+ default:
+ return usage(argv[0]);
+ }
+ }
+
+ /* Exactly 1 non-option argument, and no --repository/--path
+ unless --username. */
+ if (os->ind + 1 != argc || (!opts.username && (opts.fspath || opts.repos_name)))
+ {
+ return usage(argv[0]);
+ }
+
+ /* Grab AUTHZ_FILE from argv. */
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opts.authz_file, os->argv[os->ind],
+ pool));
+ opts.authz_file = svn_dirent_internal_style(opts.authz_file, pool);
+
+ /* Read the access file and validate it. */
+ err = svn_repos_authz_read(&authz, opts.authz_file, TRUE, pool);
+
+ /* Optionally, print the access a USER has to a given PATH in REPOS.
+ PATH and REPOS may be NULL. */
+ if (!err && opts.username)
+ {
+ const char *user = opts.username;
+ const char *path = opts.fspath;
+ const char *repos = opts.repos_name;
svn_boolean_t read_access, write_access;
- if (path[0] != '/')
+ if (path && path[0] != '/')
path = apr_pstrcat(pool, "/", path, NULL);
err = svn_repos_authz_check_access(authz, repos, path, user,
@@ -81,12 +158,9 @@
svn_authz_read, &read_access,
pool);
if (!err)
- printf("user '%s' has %s access to '%s'%s%s\n",
- user,
- write_access ? "rw" : read_access ? "r" : "no",
- path,
- repos[0] ? "in repository " : "",
- repos);
+ printf("%s\n",
+ write_access ? "rw" : read_access ? "r" : "no"
+ );
}
svn_pool_destroy(pool);
diff --git a/tools/server-side/svnpubsub/README.txt b/tools/server-side/svnpubsub/README.txt
index a23a229..5b00414 100644
--- a/tools/server-side/svnpubsub/README.txt
+++ b/tools/server-side/svnpubsub/README.txt
@@ -1,16 +1,42 @@
-### write a README
-
-
-TODO:
-- bulk update at startup time to avoid backlog warnings
-- switch to host:port format in config file
-- fold BDEC into Daemon
-- fold WorkingCopy._get_match() into __init__
-- remove wc_ready(). assume all WorkingCopy instances are usable.
- place the instances into .watch at creation. the .update_applies()
- just returns if the wc is disabled (eg. could not find wc dir)
-- figure out way to avoid the ASF-specific PRODUCTION_RE_FILTER
- (a base path exclusion list should work for the ASF)
-- add support for SIGHUP to reread the config and reinitialize working copies
-- joes will write documentation for svnpubsub as these items become fulfilled
-- make LOGLEVEL configurable
+### write a README
+
+
+TODO:
+- bulk update at startup time to avoid backlog warnings
+- switch to host:port format in config file
+- fold BDEC into Daemon
+- fold WorkingCopy._get_match() into __init__
+- remove wc_ready(). assume all WorkingCopy instances are usable.
+ place the instances into .watch at creation. the .update_applies()
+ just returns if the wc is disabled (eg. could not find wc dir)
+- figure out way to avoid the ASF-specific PRODUCTION_RE_FILTER
+ (a base path exclusion list should work for the ASF)
+- add support for SIGHUP to reread the config and reinitialize working copies
+- joes will write documentation for svnpubsub as these items become fulfilled
+- make LOGLEVEL configurable
+
+
+Installation instructions:
+
+1. Set up an svnpubsub service.
+
+ This directory should be checked out to /usr/local/svnpubsub (or /opt/svnpubsub
+ on Debian).
+
+ There are init scripts for several OSes in the rc.d/ directory; add them
+ to your OS boot process in the usual way for your OS. (For example, via
+ rc.conf(5) or update-rc.d(8).)
+
+2. Run "commit-hook.py $REPOS $REV" from your post-commit hook.
+
+ (As of 1.7, these are the same ordered arguments the post-commmit hook
+ itself receives, so you can just symlink commit-hook.py as hooks/post-commit
+ hook if you don't need any other hooks to run in the server process. (This
+ isn't as insane as it sounds --- post-commit email hooks could also feed of
+ svnpubsub, and thus not be run within the committing server thread, but on
+ any other process or box that listens to the svnpubsub stream!))
+
+3. Set up svnpubsub clients.
+
+ (eg svnwcsub.py, svnpubsub/client.py,
+ 'curl -i http://${hostname}:2069/commits/json')
diff --git a/tools/server-side/svnpubsub/commit-hook.py b/tools/server-side/svnpubsub/commit-hook.py
index 723fcb7..33f8e07 100755
--- a/tools/server-side/svnpubsub/commit-hook.py
+++ b/tools/server-side/svnpubsub/commit-hook.py
@@ -46,7 +46,7 @@
#print data
return {'author': data[0],
'date': data[1],
- 'log': "".join(data[3:])}
+ 'log': "\n".join(data[3:])}
def svncmd_dirs(repo, revision):
cmd = "%s dirs-changed -r %s %s" % (SVNLOOK, revision, repo)
@@ -68,6 +68,7 @@
def main(repo, revision):
+ revision = revision.lstrip('r')
i = svncmd_info(repo, revision)
data = {'revision': int(revision),
'dirs_changed': [],
diff --git a/tools/server-side/svnpubsub/example.conf b/tools/server-side/svnpubsub/example.conf
index f233277..db085ca 100644
--- a/tools/server-side/svnpubsub/example.conf
+++ b/tools/server-side/svnpubsub/example.conf
@@ -3,6 +3,7 @@
[DEFAULT]
svnbin: /usr/local/bin/svn
streams: http://svn.example.org:2069/commits/xml
+hook: /usr/bin/true
## The values below are used by ConfigParser's interpolation syntax.
## See http://docs.python.org/library/configparser
diff --git a/tools/server-side/svnpubsub/irkerbridge.py b/tools/server-side/svnpubsub/irkerbridge.py
new file mode 100644
index 0000000..a9fd60d
--- /dev/null
+++ b/tools/server-side/svnpubsub/irkerbridge.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# IrkerBridge - Bridge an SvnPubSub stream to Irker.
+
+# Example:
+# irkerbridge.py --daemon --pidfile pid --logfile log config
+#
+# For detailed option help use:
+# irkerbridge.py --help
+
+# It expects a config file that has the following parameters:
+# streams=url
+# Space separated list of URLs to streams.
+# This option should only be in the DEFAULT section, is ignored in
+# all other sections.
+# NOTE: At current svnpubsub.client only accepts hostname and port
+# combos so the path is ignored and /commits/xml is used.
+# irker=hostname:port
+# The hostname/port combination of the irker daemon. If port is
+# omitted it defaults to 6659. Irker is connected to over UDP.
+# match=What to use to decide if the commit should be sent to irker.
+# It consists of the repository UUID followed by a slash and a glob pattern.
+# The UUID may be replaced by a * to match all UUIDs. The glob pattern will
+# be matched against all of the dirs_changed. Both the UUID and the glob
+# pattern must match to send the message to irker.
+# to=url
+# Space separated list of URLs (any URL that Irker will accept) to
+# send the resulting message to. At current Irker only supports IRC.
+# template=string
+# A string to use to format the output. The string is a Python
+# string Template. The following variables are available:
+# $author, $rev, $date, $uuid, $log, $log, $log_firstline,
+# $log_firstparagraph, $dirs_changed, $dirs_count, $dirs_count_s,
+# $subdirs_count, $subdirs_count_s, $dirs_root
+# Most of them should be self explanatory. $dirs_count is the number of
+# entries in $dirs_changed, $dirs_count_s is a friendly string version,
+# $dirs_root is the common root of all the $dirs_changed, $subdirs_count
+# is the number of subdirs under the $dirs_root that changed,
+# $subdirs_root_s is a friendly string version. $log_firstparagraph cuts
+# the log message at the first blank line and replaces newlines with spaces.
+#
+# Within the config file you have sections. Any configuration option
+# missing from a given section is found in the [DEFAULT] section.
+#
+# Section names are arbitrary names that mean nothing to the bridge. Each
+# section other than the [DEFAULT] section consists of a configuration that
+# may match and send a message to irker to deliver. All matching sections
+# will generate a message.
+#
+# Interpolation of values within the config file is allowed by including
+# %(name)s within a value. For example I can reference the UUID of a repo
+# repeatedly by doing:
+# [DEFAULT]
+# ASF_REPO=13f79535-47bb-0310-9956-ffa450edef68
+#
+# [#commits]
+# match=%(ASF_REPO)s/
+#
+# You can HUP the process to reload the config file without restarting the
+# process. However, you cannot change the streams it is listening to without
+# restarting the process.
+#
+# TODO: Logging in a better way.
+
+# Messages longer than this will be truncated and ... added to the end such
+# that the resulting message is no longer than this:
+MAX_PRIVMSG = 400
+
+import os
+import sys
+import posixpath
+import socket
+import json
+import urlparse
+import optparse
+import ConfigParser
+import traceback
+import signal
+import re
+import fnmatch
+from string import Template
+
+# Packages that come with svnpubsub
+import svnpubsub.client
+import daemonize
+
+class Daemon(daemonize.Daemon):
+ def __init__(self, logfile, pidfile, bdec):
+ daemonize.Daemon.__init__(self, logfile, pidfile)
+
+ self.bdec = bdec
+
+ def setup(self):
+ # There is no setup which the parent needs to wait for.
+ pass
+
+ def run(self):
+ print 'irkerbridge started, pid=%d' % (os.getpid())
+
+ mc = svnpubsub.client.MultiClient(self.bdec.hostports,
+ self.bdec.commit,
+ self.bdec.event)
+ mc.run_forever()
+
+
+class BigDoEverythingClass(object):
+ def __init__(self, config, options):
+ self.config = config
+ self.options = options
+ self.hostports = []
+ for url in config.get_value('streams').split():
+ parsed = urlparse.urlparse(url.strip())
+ self.hostports.append((parsed.hostname, parsed.port or 80))
+
+ def locate_matching_configs(self, rev):
+ result = [ ]
+ for section in self.config.sections():
+ match = self.config.get(section, "match").split('/', 1)
+ if len(match) < 2:
+ # No slash so assume all paths
+ match.append('*')
+ match_uuid, match_path = match
+ if rev.uuid == match_uuid or match_uuid == "*":
+ for path in rev.dirs_changed:
+ if fnmatch.fnmatch(path, match_path):
+ result.append(section)
+ break
+ return result
+
+ def fill_in_extra_args(self, rev):
+ # Add entries to the rev object that are useful for
+ # formatting.
+ rev.log_firstline = rev.log.split("\n",1)[0]
+ rev.log_firstparagraph = re.split("\r?\n\r?\n",rev.log,1)[0]
+ rev.log_firstparagraph = re.sub("\r?\n"," ",rev.log_firstparagraph)
+ if rev.dirs_changed:
+ rev.dirs_root = posixpath.commonprefix(rev.dirs_changed)
+ rev.dirs_count = len(rev.dirs_changed)
+ if rev.dirs_count > 1:
+ rev.dirs_count_s = " (%d dirs)" %(rev.dirs_count)
+ else:
+ rev.dirs_count_s = ""
+
+ rev.subdirs_count = rev.dirs_count
+ if rev.dirs_root in rev.dirs_changed:
+ rev.subdirs_count -= 1
+ if rev.subdirs_count > 1:
+ rev.subdirs_count_s = " + %d subdirs" % (rev.subdirs_count)
+ else:
+ rev.subdirs_count_s = ""
+
+ def _send(self, irker, msg):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ irker_list = irker.split(':')
+ if len(irker_list) < 2:
+ irker_list.append(6659)
+ json_msg = json.dumps(msg)
+ sock.sendto(json_msg, (irker_list[0],int(irker_list[1])))
+ if self.options.verbose:
+ print "SENT: %s to %s" % (json_msg, irker)
+
+ def join_all(self):
+ # Like self.commit(), but ignores self.config.get(section, "template").
+ for section in self.config.sections():
+ irker = self.config.get(section, "irker")
+ to_list = self.config.get(section, "to").split()
+ if not irker or not to_list:
+ continue
+ for to in to_list:
+ msg = {'to': to, 'privmsg': ''}
+ self._send(irker, msg)
+
+ def commit(self, host, port, rev):
+ if self.options.verbose:
+ print "RECV: from %s:%s" % (host, port)
+ print json.dumps(vars(rev), indent=2)
+
+ try:
+ config_sections = self.locate_matching_configs(rev)
+ if len(config_sections) > 0:
+ self.fill_in_extra_args(rev)
+ for section in config_sections:
+ irker = self.config.get(section, "irker")
+ to_list = self.config.get(section, "to").split()
+ template = self.config.get(section, "template")
+ if not irker or not to_list or not template:
+ continue
+ privmsg = Template(template).safe_substitute(vars(rev))
+ if len(privmsg) > MAX_PRIVMSG:
+ privmsg = privmsg[:MAX_PRIVMSG-3] + '...'
+ for to in to_list:
+ msg = {'to': to, 'privmsg': privmsg}
+ self._send(irker, msg)
+
+ except:
+ print "Unexpected error:"
+ traceback.print_exc()
+ sys.stdout.flush()
+ raise
+
+ def event(self, host, port, event_name):
+ if self.options.verbose or event_name != "ping":
+ print 'EVENT: %s from %s:%s' % (event_name, host, port)
+ sys.stdout.flush()
+
+
+
+class ReloadableConfig(ConfigParser.SafeConfigParser):
+ def __init__(self, fname):
+ ConfigParser.SafeConfigParser.__init__(self)
+
+ self.fname = fname
+ self.read(fname)
+
+ signal.signal(signal.SIGHUP, self.hangup)
+
+ def hangup(self, signalnum, frame):
+ self.reload()
+
+ def reload(self):
+ print "RELOAD: config file: %s" % self.fname
+ sys.stdout.flush()
+
+ # Delete everything. Just re-reading would overlay, and would not
+ # remove sections/options. Note that [DEFAULT] will not be removed.
+ for section in self.sections():
+ self.remove_section(section)
+
+ # Get rid of [DEFAULT]
+ self.remove_section(ConfigParser.DEFAULTSECT)
+
+ # Now re-read the configuration file.
+ self.read(self.fname)
+
+ def get_value(self, which):
+ return self.get(ConfigParser.DEFAULTSECT, which)
+
+
+def main(args):
+ parser = optparse.OptionParser(
+ description='An SvnPubSub client that bridges the data to irker.',
+ usage='Usage: %prog [options] CONFIG_FILE',
+ )
+ parser.add_option('--logfile',
+ help='filename for logging')
+ parser.add_option('--verbose', action='store_true',
+ help="enable verbose logging")
+ parser.add_option('--pidfile',
+ help="the process' PID will be written to this file")
+ parser.add_option('--daemon', action='store_true',
+ help='run as a background daemon')
+
+ options, extra = parser.parse_args(args)
+
+ if len(extra) != 1:
+ parser.error('CONFIG_FILE is requried')
+ config_file = os.path.abspath(extra[0])
+
+ if options.daemon:
+ if options.logfile:
+ logfile = os.path.abspath(options.logfile)
+ else:
+ parser.error('LOGFILE is required when running as a daemon')
+
+ if options.pidfile:
+ pidfile = os.path.abspath(options.pidfile)
+ else:
+ parser.error('PIDFILE is required when running as a daemon')
+
+
+ config = ReloadableConfig(config_file)
+ bdec = BigDoEverythingClass(config, options)
+
+ d = Daemon(logfile, pidfile, bdec)
+ if options.daemon:
+ d.daemonize_exit()
+ else:
+ d.foreground()
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/tools/server-side/svnpubsub/rc.d/svnpubsub b/tools/server-side/svnpubsub/rc.d/svnpubsub
index cc466a3..6e37166 100755
--- a/tools/server-side/svnpubsub/rc.d/svnpubsub
+++ b/tools/server-side/svnpubsub/rc.d/svnpubsub
@@ -1,35 +1 @@
-#!/bin/sh
-#
-# PROVIDE: svnpubsub
-# REQUIRE: DAEMON
-# KEYWORD: shutdown
-
-. /etc/rc.subr
-
-name="svnpubsub"
-rcvar=`set_rcvar`
-
-load_rc_config $name
-
-#
-# DO NOT CHANGE THESE DEFAULT VALUES HERE
-# SET THEM IN THE /etc/rc.conf FILE
-#
-svnpubsub_enable=${svnpubsub_enable-"NO"}
-svnpubsub_user=${svnpubsub_user-"svn"}
-svnpubsub_group=${svnpubsub_group-"svn"}
-svnpubsub_reactor=${svnpubsub_reactor-"poll"}
-svnpubsub_pidfile=${svnpubsub_pidfile-"/var/run/svnpubsub/svnpubsub.pid"}
-pidfile="${svnpubsub_pidfile}"
-
-export PYTHON_EGG_CACHE="/home/svn/.python-eggs"
-
-command="/usr/local/bin/twistd"
-command_args="-y /usr/local/svnpubsub/svnpubsub.tac \
- --logfile=/var/log/vc/svnpubsub.log \
- --pidfile=${pidfile} \
- --uid=${svnpubsub_user} --gid=${svnpubsub_user} \
- -r${svnpubsub_reactor}"
-
-
-run_rc_command "$1"
+link svnpubsub.freebsd
\ No newline at end of file
diff --git a/tools/server-side/svnpubsub/rc.d/svnpubsub.debian b/tools/server-side/svnpubsub/rc.d/svnpubsub.debian
index dab06af..c61057d 100755
--- a/tools/server-side/svnpubsub/rc.d/svnpubsub.debian
+++ b/tools/server-side/svnpubsub/rc.d/svnpubsub.debian
@@ -19,7 +19,7 @@
pidfile="${svnpubsub_pidfile}"
TWSITD_CMD="/usr/bin/twistd -y /opt/svnpubsub/svnpubsub.tac \
- --logfile=/var/bwlog/svnpubsub/svnpubsub.log \
+ --logfile=/var/log/svnpubsub/svnpubsub.log \
--pidfile=${pidfile} \
--uid=${svnpubsub_user} --gid=${svnpubsub_user} \
-r${svnpubsub_reactor}"
diff --git a/tools/server-side/svnpubsub/rc.d/svnpubsub.freebsd b/tools/server-side/svnpubsub/rc.d/svnpubsub.freebsd
new file mode 100644
index 0000000..cc466a3
--- /dev/null
+++ b/tools/server-side/svnpubsub/rc.d/svnpubsub.freebsd
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# PROVIDE: svnpubsub
+# REQUIRE: DAEMON
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="svnpubsub"
+rcvar=`set_rcvar`
+
+load_rc_config $name
+
+#
+# DO NOT CHANGE THESE DEFAULT VALUES HERE
+# SET THEM IN THE /etc/rc.conf FILE
+#
+svnpubsub_enable=${svnpubsub_enable-"NO"}
+svnpubsub_user=${svnpubsub_user-"svn"}
+svnpubsub_group=${svnpubsub_group-"svn"}
+svnpubsub_reactor=${svnpubsub_reactor-"poll"}
+svnpubsub_pidfile=${svnpubsub_pidfile-"/var/run/svnpubsub/svnpubsub.pid"}
+pidfile="${svnpubsub_pidfile}"
+
+export PYTHON_EGG_CACHE="/home/svn/.python-eggs"
+
+command="/usr/local/bin/twistd"
+command_args="-y /usr/local/svnpubsub/svnpubsub.tac \
+ --logfile=/var/log/vc/svnpubsub.log \
+ --pidfile=${pidfile} \
+ --uid=${svnpubsub_user} --gid=${svnpubsub_user} \
+ -r${svnpubsub_reactor}"
+
+
+run_rc_command "$1"
diff --git a/tools/server-side/svnpubsub/rc.d/svnwcsub b/tools/server-side/svnpubsub/rc.d/svnwcsub
index f9e77b4..c5699c9 100755
--- a/tools/server-side/svnpubsub/rc.d/svnwcsub
+++ b/tools/server-side/svnpubsub/rc.d/svnwcsub
@@ -1,38 +1 @@
-#!/bin/sh
-#
-# PROVIDE: svnwcsub
-# REQUIRE: DAEMON
-# KEYWORD: shutdown
-
-. /etc/rc.subr
-
-name="svnwcsub"
-rcvar=`set_rcvar`
-
-load_rc_config $name
-
-#
-# DO NOT CHANGE THESE DEFAULT VALUES HERE
-# SET THEM IN THE /etc/rc.conf FILE
-#
-svnwcsub_enable=${svnwcsub_enable-"NO"}
-svnwcsub_user=${svnwcsub_user-"svnwc"}
-svnwcsub_group=${svnwcsub_group-"svnwc"}
-svnwcsub_pidfile=${svnwcsub_pidfile-"/var/run/svnwcsub/svnwcsub.pub"}
-svnwcsub_env="PYTHON_EGG_CACHE"
-svnwcsub_cmd_int=${svnwcsub_cmd_int-"python"}
-svnwcsub_config=${svnwcsub_config-"/etc/svnwcsub.conf"}
-svnwcsub_logfile=${svnwcsub_logfile-"/var/log/svnwcsub/svnwcsub.log"}
-pidfile="${svnwcsub_pidfile}"
-
-export PYTHON_EGG_CACHE="/var/run/svnwcsub"
-
-command="/usr/local/svnpubsub/svnwcsub.py"
-command_interpreter="/usr/local/bin/${svnwcsub_cmd_int}"
-command_args="--daemon \
- --logfile=${svnwcsub_logfile} \
- --pidfile=${pidfile} \
- --uid=${svnwcsub_user} --gid=${svnwcsub_group} \
- --umask=002 ${svnwcsub_config}"
-
-run_rc_command "$1"
+link svnwcsub.freebsd
\ No newline at end of file
diff --git a/tools/server-side/svnpubsub/rc.d/svnwcsub.debian b/tools/server-side/svnpubsub/rc.d/svnwcsub.debian
index f707c80..caf5511 100755
--- a/tools/server-side/svnpubsub/rc.d/svnwcsub.debian
+++ b/tools/server-side/svnpubsub/rc.d/svnwcsub.debian
@@ -16,7 +16,7 @@
svnwcsub_group=${svnwcsub_group-"svnwc"}
svnwcsub_pidfile=${svnwcsub_pidfile-"/var/run/svnwcsub.pid"}
svnwcsub_config=${svnwcsub_config-"/etc/svnwcsub.conf"}
-svnwcsub_logfile=${svnwcsub_logfile-"/var/bwlog/svnwcsub/svnwcsub.log"}
+svnwcsub_logfile=${svnwcsub_logfile-"/var/log/svnwcsub/svnwcsub.log"}
pidfile="${svnwcsub_pidfile}"
SVNWCSUB_CMD="/opt/svnpubsub/svnwcsub.py \
@@ -24,6 +24,7 @@
--logfile=${svnwcsub_logfile} \
--pidfile=${pidfile} \
--uid=${svnwcsub_user} --gid=${svnwcsub_group} \
+ --umask=002 \
${svnwcsub_config} "
RETVAL=0
diff --git a/tools/server-side/svnpubsub/rc.d/svnwcsub.freebsd b/tools/server-side/svnpubsub/rc.d/svnwcsub.freebsd
new file mode 100644
index 0000000..58ad386
--- /dev/null
+++ b/tools/server-side/svnpubsub/rc.d/svnwcsub.freebsd
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# PROVIDE: svnwcsub
+# REQUIRE: DAEMON
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="svnwcsub"
+rcvar=`set_rcvar`
+
+load_rc_config $name
+
+#
+# DO NOT CHANGE THESE DEFAULT VALUES HERE
+# SET THEM IN THE /etc/rc.conf FILE
+#
+svnwcsub_enable=${svnwcsub_enable-"NO"}
+svnwcsub_user=${svnwcsub_user-"svnwc"}
+svnwcsub_group=${svnwcsub_group-"svnwc"}
+svnwcsub_pidfile=${svnwcsub_pidfile-"/var/run/svnwcsub/svnwcsub.pub"}
+svnwcsub_env="PYTHON_EGG_CACHE"
+svnwcsub_cmd_int=${svnwcsub_cmd_int-"python"}
+svnwcsub_config=${svnwcsub_config-"/etc/svnwcsub.conf"}
+svnwcsub_logfile=${svnwcsub_logfile-"/var/log/svnwcsub/svnwcsub.log"}
+pidfile="${svnwcsub_pidfile}"
+
+export PYTHON_EGG_CACHE="/var/run/svnwcsub"
+
+command="/usr/local/svnpubsub/svnwcsub.py"
+command_interpreter="/usr/local/bin/${svnwcsub_cmd_int}"
+command_args="--daemon \
+ --logfile=${svnwcsub_logfile} \
+ --pidfile=${pidfile} \
+ --uid=${svnwcsub_user} --gid=${svnwcsub_group} \
+ --umask=002 \
+ ${svnwcsub_config}"
+
+run_rc_command "$1"
diff --git a/tools/server-side/svnpubsub/rc.d/svnwcsub.solaris b/tools/server-side/svnpubsub/rc.d/svnwcsub.solaris
index dd09116..bd0c2bd 100755
--- a/tools/server-side/svnpubsub/rc.d/svnwcsub.solaris
+++ b/tools/server-side/svnpubsub/rc.d/svnwcsub.solaris
@@ -14,8 +14,8 @@
--daemon \
--logfile=${svnwcsub_logfile} \
--pidfile=${pidfile} \
- --umask=002 \
--uid=${svnwcsub_user} --gid=${svnwcsub_group} \
+ --umask=002 \
${svnwcsub_config}"
RETVAL=0
diff --git a/tools/server-side/svnpubsub/svnpubsub/client.py b/tools/server-side/svnpubsub/svnpubsub/client.py
index af29974..66b6ba2 100644
--- a/tools/server-side/svnpubsub/svnpubsub/client.py
+++ b/tools/server-side/svnpubsub/svnpubsub/client.py
@@ -137,13 +137,13 @@
elif self.chars and self.rev:
value = self.chars.strip()
if name == 'path':
- self.rev.dirs_changed.append(value)
+ self.rev.dirs_changed.append(value.decode('unicode_escape'))
elif name == 'author':
- self.rev.author = value
+ self.rev.author = value.decode('unicode_escape')
elif name == 'date':
- self.rev.date = value
+ self.rev.date = value.decode('unicode_escape')
elif name == 'log':
- self.rev.log = value
+ self.rev.log = value.decode('unicode_escape')
# Toss out any accumulated characters for this element.
self.chars = ''
diff --git a/tools/server-side/svnpubsub/svnpubsub/server.py b/tools/server-side/svnpubsub/svnpubsub/server.py
index 0e3526f..f454c12 100644
--- a/tools/server-side/svnpubsub/svnpubsub/server.py
+++ b/tools/server-side/svnpubsub/svnpubsub/server.py
@@ -73,12 +73,15 @@
class Revision:
def __init__(self, r):
+ # Don't escape the values; json handles binary values fine.
+ # ET will happily emit literal control characters (eg, NUL),
+ # thus creating invalid XML, so the XML code paths do escaping.
self.rev = r.get('revision')
self.repos = r.get('repos')
- self.dirs_changed = [x.encode('unicode_escape') for x in r.get('dirs_changed')]
- self.author = r.get('author').encode('unicode_escape')
- self.log = r.get('log').encode('unicode_escape')
- self.date = r.get('date').encode('unicode_escape')
+ self.dirs_changed = [x for x in r.get('dirs_changed')]
+ self.author = r.get('author')
+ self.log = r.get('log')
+ self.date = r.get('date')
def render_commit(self, format):
if format == "json":
@@ -90,13 +93,13 @@
'date': self.date}}) +","
elif format == "xml":
c = ET.Element('commit', {'repository': self.repos, 'revision': "%d" % (self.rev)})
- ET.SubElement(c, 'author').text = self.author
- ET.SubElement(c, 'date').text = self.date
- ET.SubElement(c, 'log').text = self.log
+ ET.SubElement(c, 'author').text = self.author.encode('unicode_escape')
+ ET.SubElement(c, 'date').text = self.date.encode('unicode_escape')
+ ET.SubElement(c, 'log').text = self.log.encode('unicode_escape')
d = ET.SubElement(c, 'dirs_changed')
for p in self.dirs_changed:
x = ET.SubElement(d, 'path')
- x.text = p
+ x.text = p.encode('unicode_escape')
str = ET.tostring(c, 'UTF-8') + "\n"
return str[39:]
else:
@@ -112,7 +115,7 @@
d = ET.SubElement(c, 'dirs_changed')
for p in self.dirs_changed:
x = ET.SubElement(d, 'path')
- x.text = p
+ x.text = p.encode('unicode_escape')
str = ET.tostring(c, 'UTF-8') + "\n"
return str[39:]
else:
diff --git a/tools/server-side/svnpubsub/svnwcsub.py b/tools/server-side/svnpubsub/svnwcsub.py
index 70552a4..855cba4 100755
--- a/tools/server-side/svnpubsub/svnwcsub.py
+++ b/tools/server-side/svnpubsub/svnwcsub.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# encoding: UTF-8
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
@@ -29,6 +30,7 @@
# See svnwcsub.conf for more information on its contents.
#
+import errno
import subprocess
import threading
import sys
@@ -71,6 +73,22 @@
info[line[:idx]] = line[idx+1:].strip()
return info
+try:
+ import glob
+ glob.iglob
+ def is_emptydir(path):
+ # ### If the directory contains only dotfile children, this will readdir()
+ # ### the entire directory. But os.readdir() is not exposed to us...
+ for x in glob.iglob('%s/*' % path):
+ return False
+ for x in glob.iglob('%s/.*' % path):
+ return False
+ return True
+except (ImportError, AttributeError):
+ # Python ≤2.4
+ def is_emptydir(path):
+ # This will read the entire directory list to memory.
+ return not os.listdir(path)
class WorkingCopy(object):
def __init__(self, bdec, path, url):
@@ -106,7 +124,7 @@
def _get_match(self, svnbin, env):
### quick little hack to auto-checkout missing working copies
- if not os.path.isdir(self.path):
+ if not os.path.isdir(self.path) or is_emptydir(self.path):
logging.info("autopopulate %s from %s" % (self.path, self.url))
subprocess.check_call([svnbin, 'co', '-q',
'--non-interactive',
@@ -131,7 +149,8 @@
self.svnbin = config.get_value('svnbin')
self.env = config.get_env()
self.tracking = config.get_track()
- self.worker = BackgroundWorker(self.svnbin, self.env)
+ self.hook = config.get_value('hook')
+ self.worker = BackgroundWorker(self.svnbin, self.env, self.hook)
self.watch = [ ]
self.hostports = [ ]
@@ -150,7 +169,7 @@
# Add it to our watchers, and trigger an svn update.
logging.info("Watching WC at %s <-> %s" % (wc.path, wc.url))
self.watch.append(wc)
- self.worker.add_work(OP_UPDATE, wc)
+ self.worker.add_work(OP_BOOT, wc)
def _normalize_path(self, path):
if path[0] != '/':
@@ -182,11 +201,12 @@
# Start logging warnings if the work backlog reaches this many items
BACKLOG_TOO_HIGH = 20
+OP_BOOT = 'boot'
OP_UPDATE = 'update'
OP_CLEANUP = 'cleanup'
class BackgroundWorker(threading.Thread):
- def __init__(self, svnbin, env):
+ def __init__(self, svnbin, env, hook):
threading.Thread.__init__(self)
# The main thread/process should not wait for this thread to exit.
@@ -195,20 +215,28 @@
self.svnbin = svnbin
self.env = env
+ self.hook = hook
self.q = Queue.Queue()
self.has_started = False
def run(self):
while True:
- if self.q.qsize() > BACKLOG_TOO_HIGH:
- logging.warn('worker backlog is at %d', self.q.qsize())
-
# This will block until something arrives
operation, wc = self.q.get()
+
+ # Warn if the queue is too long.
+ # (Note: the other thread might have added entries to self.q
+ # after the .get() and before the .qsize().)
+ qsize = self.q.qsize()+1
+ if operation != OP_BOOT and qsize > BACKLOG_TOO_HIGH:
+ logging.warn('worker backlog is at %d', qsize)
+
try:
if operation == OP_UPDATE:
self._update(wc)
+ elif operation == OP_BOOT:
+ self._update(wc, boot=True)
elif operation == OP_CLEANUP:
self._cleanup(wc)
else:
@@ -228,7 +256,7 @@
self.q.put((operation, wc))
- def _update(self, wc):
+ def _update(self, wc, boot=False):
"Update the specified working copy."
# For giggles, let's clean up the working copy in case something
@@ -239,13 +267,15 @@
### we need to move some of these args into the config. these are
### still specific to the ASF setup.
- args = [self.svnbin, 'update',
+ args = [self.svnbin, 'switch',
'--quiet',
'--non-interactive',
'--trust-server-cert',
'--ignore-externals',
'--config-option',
'config:miscellany:use-commit-times=on',
+ '--',
+ wc.url,
wc.path]
subprocess.check_call(args, env=self.env)
@@ -253,6 +283,15 @@
info = svn_info(self.svnbin, self.env, wc.path)
logging.info("updated: %s now at r%s", wc.path, info['Revision'])
+ ## Run the hook
+ if self.hook:
+ hook_mode = ['post-update', 'boot'][boot]
+ logging.info('running hook: %s at revision %s due to %s',
+ wc.path, info['Revision'], hook_mode)
+ args = [self.hook, hook_mode,
+ wc.path, info['Revision'], wc.url]
+ subprocess.check_call(args, env=self.env)
+
def _cleanup(self, wc):
"Run a cleanup on the specified working copy."