diff --git a/.editorconfig b/.editorconfig index 13868c63e3f8d..b000de12aff16 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ trim_trailing_whitespace = false [**/Makefile*] indent_style = tab +indent_size = 8 [build-outputs.mk] indent_style = tab diff --git a/.github/workflows/autoconf.yml b/.github/workflows/autoconf.yml new file mode 100644 index 0000000000000..15aa3b00c0186 --- /dev/null +++ b/.github/workflows/autoconf.yml @@ -0,0 +1,107 @@ +# +# 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. +# +# autoconf.yml -- Configuration for autoconf GitHub Action workflow. +# + +name: autoconf + +on: + push: + branches: ["*"] + paths-ignore: + - 'doc/**' + - 'notes/**' + - CHANGES + - COMMITTERS + - INSTALL + - STATUS + - README + pull_request: + branches: ["*"] + paths-ignore: + - 'doc/**' + - 'notes/**' + - CHANGES + - COMMITTERS + - INSTALL + - STATUS + - README + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + strategy: + fail-fast: false + matrix: + check-target: [check, davautocheck] + os: [ubuntu-latest, ubuntu-22.04-arm] + + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }}, target ${{ matrix.check-target }} + + steps: + - name: Install dependencies (Linux, apt-get) + if: runner.os == 'Linux' + run: > + sudo apt-get update && + sudo apt-get install + libtool + libtool-bin + libapr1-dev + libaprutil1-dev + libserf-dev + libexpat1-dev + zlib1g-dev + libsqlite3-dev + liblz4-dev + libutf8proc-dev + apache2-dev + libsecret-1-dev + + - name: Use LF for Git checkout + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v4 + + - name: autogen + run: ./autogen.sh + + - name: Configure + run: ./configure --enable-maintainer-mode + + - name: Build (make) + run: make -j + + - name: Run tests + run: make ${{matrix.check-target}} PARALLEL=16 APACHE_MPM=event + + - name: Archive test log + if: always() + uses: actions/upload-artifact@v4 + with: + name: tests-${{matrix.os}}-${{matrix.check-target}}.log + path: tests.log + + - name: Install (make install) + run: sudo make install diff --git a/.github/workflows/detect-backport-conflicts.yml b/.github/workflows/detect-backport-conflicts.yml new file mode 100644 index 0000000000000..d2c19a8b62a07 --- /dev/null +++ b/.github/workflows/detect-backport-conflicts.yml @@ -0,0 +1,53 @@ +# +# 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. +# +# detect-backport-conflicts.yml -- Configuration for backportbot +# GitHub Action workflow. +# + +name: backportbot + +on: + push: + branches: ["1.14.x", "1.15.x"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + conflicts: + runs-on: ubuntu-latest + name: Detect conflicts + + steps: + - name: Install dependencies (Linux, apt-get) + run: > + sudo apt-get update && + sudo apt-get install + subversion + + - name: Obtain tools/dist + run: svn export https://svn.apache.org/repos/asf/subversion/trunk/tools/dist tools-dist + + - name: Checkout backport branch + run: svn co https://svn.apache.org/repos/asf/subversion/branches/${{ github.ref_name }} branch-wc + + - name: Detect conflicting backports + run: cd branch-wc && ../tools-dist/detect-conflicting-backports.py + diff --git a/CHANGES b/CHANGES index 294c4dc223e9b..800249aeacf17 100644 --- a/CHANGES +++ b/CHANGES @@ -4,14 +4,172 @@ # To view an issue listed as (issue #XXXX), visit: # https://subversion.apache.org/issue-XXXX +Version 1.14.5 +(7 Dec 2024, from /branches/1.14.x) +https://svn.apache.org/repos/asf/subversion/tags/1.14.5 + User-visible changes: + - Server-side bugfixes: + * Fix CVE-2024-46901: mod_dav_svn denial-of-service via control chars + Developer-visible changes: + * Fix detection of zlib version 1.3 in gen-make.py on Windows (r1921266) + * Supress gen-make.py errors on Windows without Perl or Ruby (r1921267) + * Fix printf-format build warnings in swig-rb (r1921264) + * Add a regression test for CVE-2024-45720 (r1921266) + * Make swig-py compatible with SWIG 4.3.0 (r1921505) + +Version 1.14.4 +(8 Oct 2024, from /branches/1.14.x) +https://svn.apache.org/repos/asf/subversion/tags/1.14.4 + User-visible changes: + - Client-side improvements and bugfixes: + * Fix CVE-2024-45720: command line argument injection on Windows + * Fix unbounded memory usage in propget and proplist --xml (r1918138, -39) + * Fix cmdline parsing bugs in --change (-c) argument (r1917864, -944) + * Improve help message for svnmucc PUT (r1914222) + - Server-side bugfixes: + * mailer.py: Fix inconsistency in Config.which_group() arguments (r1914518) + + Developer-visible changes: + * Fix svnadmin_tests.py failing with Python 3.11+ on Windows (r1910464) + * Support building swig-rb with clang 16 / GCC14 (r1915236, -39) + * Convert path to local style in error message from diff API (r1917946) + * Fix `invalid escape sequence` in .py scripts for Python 3.12 (r1912632) + * swig-py: Fix `none_dealloc` error caused by refcount issue (r1915316) + * Fix a msgid in svnadmin.c which was not a valid C string (r1887710) + +Version 1.14.3 +(20 Dec 2023, from /branches/1.14.x) +https://svn.apache.org/repos/asf/subversion/tags/1.14.3 + User-visible changes: + - Client-side bugfixes: + * Fix svn:mergeinfo diff parser bug when parsing forward merges (r1906502) + * Fix redirected URL handling with file externals (r1908926, issue #4911) + + - Server-side bugfixes: + (none) + + Developer-visible changes: + * swig-rb: Fix uses of 'File.exist?', deprecated since Ruby 2.1 (r1904472) + * Build: Fix uses of deprecated Python APIs (r1900882, issue #4899) + * Build: Retain ability to build SWIG Python 2 bindings (r1900890) + * Fix reading WC lock status with svn_wc_status2_t (r1904193, issue #4908) + * JavaHL: Add @Deprecated to silence compiler warnings (r1904936) + * JavaHL: Fix crash in case of null message in getMessage (r1904938) + * Fix build breakage of release tarballs by installed swig (r1878379 et al) + * Add regression test for issue #4711 "invalid xml file" (r1877310) + * swig-py: Fix building with SWIG 4.1.0 (r1904167) + * Makefile.in: Fix cleaning of __pycache__ dirs and *.pyc (r1903267) + * swig-py: Avoid deprecated options to SWIG >= 4.1.0 (r1904198, r1904287) + * swig-py: Use sysconfig to allow building with Python 3.12 (r1910098) + * INSTALL: Document not to use SVN with APR 1.7.3 on Windows (r1911278) + * Fix test suite broken by syntax error when --enable-sasl (r1907124) + * swig-py: Fix issues #4916, #4917, #4918 (r1912500 et al) + * swig-py: Improve error when no external diff (r1912724, -743, issue #1778) + * autogen.sh: Fix building when Python is not named "python" (r1903245) + +Version 1.14.2 +(12 Apr 2022, from /branches/1.14.x) + User-visible changes: + - Client-side bugfixes: + * Fix -r option documentation for some svnadmin subcommands (r1896877) + * Fix error message encoding when system() call fails (r1887641, r1890013) + * Fix assertion failure in conflict resolver (r1892470, -471, -541) + + - Server-side bugfixes: + * Fix CVE-2021-28544: authz protected copyfrom paths regression (r1899227) + * Fix CVE-2022-24070: use-after-free in mod_dav_svn (issue #4880) + + Developer-visible changes: + * Add test coverage for CVE-2020-17525 (r1883838 et al) + * Fix «make davautocheck» failure due to unbuilt dependency (r1891908) + * Follow up to r1866587, r1866588: Unbreak a msgid. (r1887704) + * swig-py: Fix double-free with cyclic garbage collector (r1889487) + * swig-py: Skip some tests on Python2 if encoding is 'utf-8' (r1885199) + * swig-py: Fix dependency of make copy-swig-py target (r1886708) + * Document how the port number is passed to custom tunnels (r1889629) + * tests: Include additional information in an error message (r1897449) + * Fix an error message when running make davautocheck (r1892121) + * Use the APR-1.4+ API for flushing file contents to disk (r1883355) + * JavaHL: Fix RequestChannel_nativeRead_AfterException failure (r1898633) + +Version 1.14.1 +(10 Feb 2021, from /branches/1.14.x) + User-visible changes: + - Client-side improvements and bugfixes: + * Fix non-deterministic generation of mergeinfo (issue #4862) + * Fix merge removing a folder with non-inheritable mergeinfo (issue #4859) + * Do not suggest --help -v for commands which do not support -v (r1882157) + * Fix invalid SQL quoting in working copy upgrade system (r1879198) + * Fix problems in human-readable file size formatting (r1878909, -18, -50) + * Improve an error message from svnmucc (r1877072) + * Fix 'svn info --xml' gives wrong 'source-right' of conflict (issue #4869) + * Fix filename encoding/quoting when invoking editor on Windows (r1885953) + * Convert filename for editor from UTF-8 to the locale's encoding (r1882234) + + - Server-side improvements and bugfixes: + * Fix authz doesn't combine global and repository rules (issue #4762) + * Make the hot-backup.py script work with Python 3 (r1878855, r1878859) + * Fix an uninitialized read in FSFS (r1880374) + * Make mailer.py work properly with Python 3 (r1884427 et al) + * Fix CVE-2020-17525: NULL dereference in mod_authz_svn (r1886019) + + Developer-visible changes: + - General: + * Restore support for building with APR 1.4 (r1881958, r1882128) + * Rewrite internal test data to avoid literal trailing spaces (r1875675) + * Remove use of os.dup2() from tests for Python 3.6 on Windows (r1883337) + * Note in INSTALL that non-release mode is required for Python 2 (r1877960) + * Make gen-make.py --debug work with Python 3 (r1876906) + * Make test suite run correctly with Python 3 on Windows (r1876707 et al) + * Fix compilation errors with Visual Studio 2008 (r1877259) + * Use Doxygen comment leader for comments with doxygen syntax (r1877794) + - Bindings: + * Fix win-tests.py to load Python 3 bindings with debug config (r1884642) + * Fix C4204 errors building swig-py with Python 3.9 on Windows (r1883570) + * Fix unable to load *.pyd files with Python 3.8.x on Windows (r1883335) + * Add SWIG4 support for SWIG Python binding on Windows (r1877338) + * Fix incorrect cache in JavaHL SVNBase::createCppBoundObject (r1882115) + * Fix crash in JavaHL JNI wrapper caused by object lifetimes (r1880886) + * autogen.sh: Remove .swig_*_checked files from release tarballs (r1878413) + * Avoid version check for Python if --without-swig is used (r1876662) + * swig-py: Fix a syntax error for Python 3.6 and later (r1885112) + * Fix several crashes and JNI warnings in javahl TunnelAgent (r1886029) + Version 1.14.0 -(?? ??? 20XX, from /branches/1.14.x) +(27 May 2020, from /branches/1.14.x) https://svn.apache.org/repos/asf/subversion/tags/1.14.0 User-visible changes: - Minor new features and improvements: - * Add 'svn info --show-item=changelist' (r1869481) - + * Experimental shelving feature is now disabled by default (r1875039) + * Reinstate support for experimental shelving v2 from SVN 1.11 (r1875037) + * Introduce 'svnadmin build-repcache' command (r1875921 et al) + + - Client-side improvements and bugfixes: + * Add 'changelist' option to 'svn info --show-item' (r1869481) + * Allow simultaneous use of 'svn log' --quiet and --diff options (r1871916) + * Fix 'svn info' for file that was inside replaced directory (issue #4837) + * Don't abort if the server redirects to a non-canonical URL (r1873375) + * Fix merge assertion failure in svn_sort__array_insert (issue #4840) + * Escape filenames when invoking $SVN_EDITOR (r1874057 et al) + * Small performance optimization for FSFS rep-cache.db (r1875918) + * Fix a crash seen when using git-svn with kwallet (r1875680) + + - Server-side improvements and bugfixes: + * Fix 'svnadmin load --normalize-props' for versioned properties (r1868203) + * mailer.py: prevent SMTP errors from impeding later emails (issue #1804) + * mailer.py: Add option to specify the SMTP port to connect to (r1872398) + * Make svn-backup-dump.py work on Python 3 + * validate-files.py: Fix handling for non-ASCII characters (r1874393) + * Fix an undefined behavior problem in FSFS caching code (r1876054) + + Developer-visible changes: + * Require at least version 1.5 of APR (r1874094) + * Support Python3 in the py-swig bindings; requires py3c (r1869354 et al) + * Support building with SWIG 4 on Python 3.x (r1869853) + * Fix svnserveautocheck for Python 3 (r1868151) + * contribulyze.py: Support Python 3 in addition to Python 2 (r1871211) + * Fix Proc.new warnings in Ruby bindings with Ruby >= 2.7 (r1876020) Version 1.13.0 (30 Oct 2019, from /branches/1.13.x) @@ -194,6 +352,45 @@ http://svn.apache.org/repos/asf/subversion/tags/1.11.0 * Enable building against Java 10 (r1841180 et al) * Fix a potential crash in JavaHL (issue #4764) +Version 1.10.8 +(12 Apr 2022, from /branches/1.10.x) + User-visible changes: + - Client-side bugfixes: + * Fix merge assertion failure in svn_sort__array_insert (issue #4840) + + - Server-side bugfixes: + * Fix CVE-2021-28544: authz protected copyfrom paths regression (r1899227) + * Fix CVE-2022-24070: use-after-free in mod_dav_svn (issue #4880) + * Fix authz doesn't combine global and repository rules (issue #4762) + + Developer-visible changes: + * Add test coverage for CVE-2020-17525 (r1883838 et al) + * Fix «make davautocheck» failure due to unbuilt dependency (r1891908) + * Follow up to r1866587, r1866588: Unbreak a msgid (r1887704) + * Remove incorrect include paths from svn_cv_ruby_includes (r1875602) + +Version 1.10.7 +(10 Feb 2021, from /branches/1.10.x) + User-visible changes: + - Client-side bugfixes: + * Fix 'svn patch' setting mode 0600 on patched files with props (r1864440) + * Fix invalid SQL quoting in working copy upgrade system (r1879198) + * Fix non-deterministic generation of mergeinfo (issue #4862) + * Fix a crash seen when using git-svn with kwallet (r1875680) + * Fix merge removing a folder with non-inheritable mergeinfo (issue #4859) + * Fix 'svn info --xml' gives wrong 'source-right' of conflict (issue #4869) + + - Server-side bugfixes: + * mod_dav_svn: install cleanup handler for FS warning logging (r1865266) + * mod_dav_svn: Fix missing Last-Modified header on 'external' GET requests (r1866425) + * Fix formatting type size mismatches in FSFS (r1865987, -8) + * Fix an undefined behavior problem in FSFS caching code (r1876054) + * Fix CVE-2020-17525: NULL dereference in mod_authz_svn (r1886019) + + Developer-visible changes: + * Fix Requires(.private) fields in pkg-config files (r1863987, -90) + * Fix crash in JavaHL JNI wrapper caused by object lifetimes (r1880886) + * Fix an EOL issue in tests on Windows (r1881985 et al) Version 1.10.6 (24 Jul 2019, from /branches/1.10.x) @@ -1302,7 +1499,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.9.0 * cache: fix premature eviction due to 64-bit underflows (r1567996 et al) * svnserve: fix potential integer overflow in Cyrus SASL support (r1570434) * bdb: fix potential integer overflow and underflow (r1570701) - * bdb: prevent silent propogation of some corruption (r1570778) + * bdb: prevent silent propagation of some corruption (r1570778) * svnadmin hotcopy: do not corrupt db/current contents when copying old FSFS repos (r1603485) * svnadmin hotcopy: don't produce broken copies when a concurrent pack @@ -1562,7 +1759,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.9.0 ranges (r1569731) * svn_rangelist_inheritable2() and svn_mergeinfo_inheritable2(): fix a pool lifetime issue (r1569764) - * new APIs to support cancelation during unified diff output and + * new APIs to support cancellation during unified diff output and allow the context size to be specified (r1570149 et al) * APIs related to retrieving logs are now documented to be unlimited when a negative value is passed for the limit (r1570330, 1570335) @@ -1680,7 +1877,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.9.0 * swig-py: Add close to the core.Stream class (r1619077) * javahl: add example clients that use the authn API (r1640533) * swig-py: implement dump stream parser (r1642813) - * swig-pl: remove some unneded cleanup code that triggered a cleanup + * swig-pl: remove some unneeded cleanup code that triggered a cleanup failure on windows (r1643072) * swig-pl: make cancel_func, cancel_baton parameter pairs work (r1648852) * javahl: expose whitespace diff parameters to blame method (issue #4475) @@ -2011,7 +2208,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.8.9 - Client-side bugfixes: * log: use proper peg revision over DAV (r1568872) * upgrade: allow upgrading from 1.7 with exclusive locks (r1572102 et al) - * proplist: resolve inconsitent inherited property results (r1575270 et al) + * proplist: resolve inconsistent inherited property results (r1575270 et al) * increase minimal timestamp sleep from 1ms to 10ms (r1581305 et al) * merge: automatic merge confused by subtree merge (issue #4481) * propget: report proper error on invalid revision for url (r1586255) @@ -2053,7 +2250,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.8.9 Developer-visible changes: - General: * improve consistency checks of DAV inherited property requests (r1498000) - * fix ocassional failure in autoprop_tests.py (r1567752) + * fix occasional failure in autoprop_tests.py (r1567752) * avoid duplicate sqlite analyze information rows (r1571214) * add Mavericks to our sysinfo output (r1573088) * bump copyright years to 2014 (r1555403) @@ -2106,7 +2303,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.8.8 Developer-visible changes: - General: - * fix ocassional failure of check_tests.py 12 (r1496127 et al) + * fix occasional failure of check_tests.py 12 (r1496127 et al) * fix failure with SQLite 3.8.1-3.8.3 when built with SQLITE_ENABLE_STAT3/4 due to bug in SQLite (r1567286, r1567392) * specify SQLite defaults that can be changed when SQLite is built @@ -2212,7 +2409,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.8.3 - Client- and server-side bugfixes: * translation updates for Swedish * enforce strict version equality between tools and libraries (r1502267) - * consistently output revisions as "r%ld" in error messags (r1499044 et al) + * consistently output revisions as "r%ld" in error messages (r1499044 et al) - Client-side bugfixes: * status: always use absolute paths in XML output (issue #4398) @@ -2319,7 +2516,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.8.1 * svnadmin upgrade: fix data loss when cancelling in last stage (r1494298) * mod_dav_svn: fix incorrect path canonicalization (r1503528) See CVE-2013-4131, and descriptive advisory at - http://subversion.apache.org/security/CVE-2013-4131-advisory.txt + https://subversion.apache.org/security/CVE-2013-4131-advisory.txt - Other tool improvements and bugfixes: * fsfs-stats (tool): resolve segfault when passing invalid path (r1492164) @@ -2791,7 +2988,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.7.18 Developer-visible changes: - General: - * fix ocassional failure in checkout_tests.py test 12. (r1496127) + * fix occasional failure in checkout_tests.py test 12. (r1496127) * disable building ZLib's assembly optimizations on Windows. @@ -2916,12 +3113,12 @@ http://svn.apache.org/repos/asf/subversion/tags/1.7.11 - Server-side bugfixes: * mod_dav_svn: fix incorrect path canonicalization (r1503528) See CVE-2013-4131, and descriptive advisory at - http://subversion.apache.org/security/CVE-2013-4131-advisory.txt + https://subversion.apache.org/security/CVE-2013-4131-advisory.txt - Other tool improvements and bugfixes: * fix argument processing in contrib hook scripts (r1485350) See CVE-2013-2088, and descriptive advisory at - http://subversion.apache.org/security/CVE-2013-2088-advisory.txt + https://subversion.apache.org/security/CVE-2013-2088-advisory.txt Developer-visible changes: - Bindings: @@ -2948,10 +3145,10 @@ http://svn.apache.org/repos/asf/subversion/tags/1.7.10 - Server-side bugfixes: * fix FSFS repository corruption due to newline in filename (issue #4340) See CVE-2013-1968, and descriptive advisory at - http://subversion.apache.org/security/CVE-2013-1968-advisory.txt + https://subversion.apache.org/security/CVE-2013-1968-advisory.txt * fix svnserve exiting when a client connection is aborted (r1482759) See CVE-2013-2112, and descriptive advisory at - http://subversion.apache.org/security/CVE-2013-2112-advisory.txt + https://subversion.apache.org/security/CVE-2013-2112-advisory.txt * fix svnserve memory use after clear (issue #4365) * fix repository corruption on power/disk failure on Windows (r1483781) @@ -2979,7 +3176,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.7.9 - Server-side bugfixes: See CVE-2013-1845, CVE-2013-1846, CVE-2013-1847, CVE-2013-1849, and CVE-2013-1884, and descriptive advisories at - http://subversion.apache.org/security/ + https://subversion.apache.org/security/ * svnserve will log the replayed rev not the low-water rev. (r1461278) * mod_dav_svn will omit some property values for activity urls (r1453780) * fix an assertion in mod_dav_svn when acting as a proxy on / (issue #4272) @@ -3319,7 +3516,7 @@ Version 1.7.0 http://svn.apache.org/repos/asf/subversion/tags/1.7.0 See the 1.7 release notes for a more verbose overview of the changes since -the 1.6 release: http://subversion.apache.org/docs/release-notes/1.7.html +the 1.6 release: https://subversion.apache.org/docs/release-notes/1.7.html User-visible changes: - General: @@ -3406,7 +3603,7 @@ the 1.6 release: http://subversion.apache.org/docs/release-notes/1.7.html * fixed: wc-to-wc copy of a switch source (issue #1802) * fixed: 'svn st' reports symlinks as obstructed items (issue #2284) * fixed: 'cd e:\; svn up e:\' fails (issue #2556) - * fixed: svn aborts on commiting from root dir on windows (issue #3346) + * fixed: svn aborts on committing from root dir on windows (issue #3346) * fixed: removing a dir scheduled for deletion corrupts wc (issue #2741) * fixed: 'svn cleanup' fails on obstructed paths (issue #2867) * fixed: case-only renames resulting from merges don't work (issue #3115) @@ -3443,7 +3640,7 @@ the 1.6 release: http://subversion.apache.org/docs/release-notes/1.7.html * fixed: 'svn info' returns parent info on missing dirs (issue #3178) * fixed: spurious prop conflict with 'merge --reintegrate' (issue #3919) * fixed: 'svn --version' fails with non-existent $HOME (issue #3947) - * fixed: unforced export silently overwites existing file (issue #3799) + * fixed: unforced export silently overwrites existing file (issue #3799) * fixed: reverse merge which adds subtree mergeinfo fails (issue #3978) * fixed: 'svn up -r{R>HEAD}' hangs client over ra_svn (issue #3963) * fixed: 'svn up' updates file externals in target siblings (issue #3819) @@ -3518,7 +3715,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.23 * fix FSFS repository corruption due to newline in filename (issue #4340) * fix svnserve exiting when a client connection is aborted (r1482759) See CVE-2013-2112, and descriptive advisory at - http://subversion.apache.org/security/CVE-2013-2112-advisory.txt + https://subversion.apache.org/security/CVE-2013-2112-advisory.txt - Other tool improvements and bugfixes: * fix argument processing in contrib hook scripts (r1485350) @@ -3616,7 +3813,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.17 * fixed: file externals cause mixed-revision working copies (issue #3816) * fix crash in mod_dav_svn with GETs of baselined resources (r1104126) See CVE-2011-1752, and descriptive advisory at - http://subversion.apache.org/security/CVE-2011-1752-advisory.txt + https://subversion.apache.org/security/CVE-2011-1752-advisory.txt * fixed: write-through proxy could directly commit to slave (r917523) * detect a particular corruption condition in FSFS (r1100213) * improve error message when clients refer to unknown revisions (r939000) @@ -3629,10 +3826,10 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.17 * server-side validation of svn:mergeinfo syntax during commit (issue #3895) * fix remotely triggerable mod_dav_svn DoS (r1130303) See CVE-2011-1783, and descriptive advisory at - http://subversion.apache.org/security/CVE-2011-1783-advisory.txt + https://subversion.apache.org/security/CVE-2011-1783-advisory.txt * fix potential leak of authz-protected file contents (r1130303) See CVE-2011-1921, and descriptive advisory at - http://subversion.apache.org/security/CVE-2011-1921-advisory.txt + https://subversion.apache.org/security/CVE-2011-1921-advisory.txt Developer-visible changes: * fix reporting FS-level post-commit processing errors (r1104098) @@ -3648,7 +3845,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.16 * more improvement to the 'blame -g' memory leak from 1.6.15 (r1041438) * avoid a crash in mod_dav_svn when using locks (r1071239, -307) See CVE-2011-0715, and descriptive advisory at - http://subversion.apache.org/security/CVE-2011-0715-advisory.txt + https://subversion.apache.org/security/CVE-2011-0715-advisory.txt * avoid unnecessary globbing for performance (r1068988) * don't add tree conflicts when one already exists (issue #3486) * fix potential crash when requesting mergeinfo (r902467) @@ -3723,7 +3920,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.13 * fixed: record-only merges create self-referential mergeinfo (issue #3646) * fixed: 'SVNPathAuthz short_circuit' unsolicited read access (issue #3695) See CVE-2010-3315, and descriptive advisory at - http://subversion.apache.org/security/CVE-2010-3315-advisory.txt + https://subversion.apache.org/security/CVE-2010-3315-advisory.txt * make 'svnmucc propset' handle existing and non-existing URLs (r1000607) * add new 'propsetf' subcommand to svnmucc (r1000612) * warn about copied dirs during 'svn ci' with limited depth (r1002094) @@ -3787,7 +3984,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.11 Developer-visible changes: * disable checks for wc-ng working copies when running the test suite * on Windows, don't ignore move operation error codes (r896915) - * more precise reporting of errors occuring with sqlite init (r927323, -8) + * more precise reporting of errors occurring with sqlite init (r927323, -8) * ensure rangelist APIs are commutative (r923389, -91) @@ -3899,7 +4096,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.6.4 User-visible changes: * fixed: heap overflow vulnerability on server and client See CVE-2009-2411, and descriptive advisory at - http://subversion.apache.org/security/CVE-2009-2411-advisory.txt + https://subversion.apache.org/security/CVE-2009-2411-advisory.txt Version 1.6.3 @@ -4135,7 +4332,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.5.9 * improve memory performance in 'svn merge' (issue #3393) * fixed: 'SVNPathAuthz short_circuit' unsolicited read access (issue #3695) See CVE-2010-3315, and descriptive advisory at - http://subversion.apache.org/security/CVE-2010-3315-advisory.txt + https://subversion.apache.org/security/CVE-2010-3315-advisory.txt * prevent crash in mod_dav_svn when using SVNParentPath (r1033166) * limit memory fragmentation in svnserve (r1022675) * fix server-side memory leaks triggered by 'blame -g' (r1032808) @@ -4160,7 +4357,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.5.7 User-visible changes: * fixed: heap overflow vulnerability on server and client See CVE-2009-2411, and descriptive advisory at - http://subversion.apache.org/security/CVE-2009-2411-advisory.txt + https://subversion.apache.org/security/CVE-2009-2411-advisory.txt Version 1.5.6 @@ -4593,7 +4790,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.4.5 User-visible changes: * fixed: file placement vulnerability (Win32 clients only) See CVE-2007-3846, and descriptive advisory at - http://subversion.apache.org/security/CVE-2007-3846-advisory.txt + https://subversion.apache.org/security/CVE-2007-3846-advisory.txt Version 1.4.4 @@ -5172,7 +5369,7 @@ Version 1.2.0 http://svn.apache.org/repos/asf/subversion/tags/1.2.0 See the 1.2 release notes for a more verbose overview of the changes since -the 1.1 release: http://subversion.apache.org/docs/release-notes/1.2.html +the 1.1 release: https://subversion.apache.org/docs/release-notes/1.2.html User-visible changes: - Client: @@ -5468,7 +5665,7 @@ Version 1.1.0 http://svn.apache.org/repos/asf/subversion/tags/1.1.0 See the 1.1 release notes for a more verbose overview of the changes since -1.0.x: http://subversion.apache.org/docs/release-notes/1.1.html +1.0.x: https://subversion.apache.org/docs/release-notes/1.1.html User-visible changes: * new non-database repository back-end (libsvn_fs_fs) @@ -5598,7 +5795,7 @@ http://svn.apache.org/repos/asf/subversion/tags/1.0.8 User-visible changes: * fixed: mod_authz_svn path and log-message metadata leaks. See CAN-2004-0749, and descriptive advisory at - http://subversion.apache.org/security/CAN-2004-0749-advisory.txt + https://subversion.apache.org/security/CAN-2004-0749-advisory.txt Version 1.0.7 @@ -7150,7 +7347,7 @@ Version 0.9 (released 15 Feb 2002, revision 1302) - no more 'path styles' in path library - rewrite bootstrapping code for python test framework - rewrite commandline app's help-system and alias-system - - feedback table replaced with notfication callback + - feedback table replaced with notification callback - rewrite sorting of hashes - svnadmin internal rewrite - faster post-update processing diff --git a/COMMITTERS b/COMMITTERS index 0f982d76b7966..9f8995b0b9164 100644 --- a/COMMITTERS +++ b/COMMITTERS @@ -34,6 +34,7 @@ Blanket commit access: maxb Max Bowsher dberlin Daniel Berlin danderson David Anderson + ivan Ivan Zhakov djames David James pburba Paul Burba glasser David Glasser @@ -81,7 +82,6 @@ Full committers who have asked to be listed as dormant: malcolm Malcolm Rowe naked Nuutti Kotivuori ringstrom Tobias Ringström - ivan Ivan Zhakov Partial committers who have asked to be listed as dormant: diff --git a/INSTALL b/INSTALL index d97fe78d429fd..a2098f95cf0c8 100644 --- a/INSTALL +++ b/INSTALL @@ -206,7 +206,7 @@ I. INTRODUCTION commands described in section II.B before installing the following. - 1. Apache Portable Runtime 1.5 or newer (REQUIRED) + 1. Apache Portable Runtime 1.4 or newer (REQUIRED) Whenever you want to build any part of Subversion, you need the Apache Portable Runtime (APR) and the APR Utility (APR-util) @@ -242,22 +242,42 @@ I. INTRODUCTION $ ./configure --with-apr=/usr/local/apache2 \ --with-apr-util=/usr/local/apache2 ... - Be sure to use a native Windows SVN client (as opposed to - Cygwin's version) so that the .dsp files get carriage-returns at - the ends of their lines. Otherwise Visual Studio will complain - that it doesn't recognize the .dsp files. + Notes on Windows platforms: - If you use APR libraries checked out from svn in an Unix - environment, you need to run the 'buildconf' script in each - library's directory, to regenerate the configure scripts and - other files required for compiling the libraries: + * Do not use APR version 1.7.3 as that release contains a bug that + makes it impossible for Subversion to use it properly. This issue + only affects APR builds on Windows. This issue was fixed in APR + version 1.7.4. See: + https://lists.apache.org/thread/xd5t922jvb9423ph4j84rsp5fxks1k0z - $ cd apr; ./buildconf; ./configure ...; make; make install; cd .. + * If you check out APR and APR-util sources from their Subversion + repository, be sure to use a native Windows SVN client (as opposed + to Cygwin's version) so that the .dsp files get carriage-returns at + the ends of their lines. Otherwise Visual Studio will complain that + it doesn't recognize the .dsp files. - $ cd apr-util; ./buildconf; ./configure ...; make; make install; cd .. + Notes on Unix platforms: - Configure build and install both libraries before running Subversion's - configure script. + * If you check out APR and APR-util sources from their Subversion + repository, you need to run the 'buildconf' script in each library's + directory to regenerate the configure scripts and other files + required for compiling the libraries. Afterwards, configure, build, + and install both libraries before running Subversion's configure + script. For example: + + $ cd apr + $ ./buildconf + $ ./configure + $ make + $ make install + $ cd .. + + $ cd apr-util + $ ./buildconf + $ ./configure + $ make + $ make install + $ cd .. 2. SQLite (REQUIRED) @@ -506,6 +526,12 @@ I. INTRODUCTION reached end of life. All users are strongly encouraged to move to Python 3. + Note: If you are using a Subversion distribution tarball and want + to build the Python bindings for Python 2, you should rebuild + the build environment in non-release mode by running + 'sh autogen.sh' before running the ./configure script; see + section II.B for more about autogen.sh. + 13. Perl 5.8 or newer (Windows only) (OPTIONAL) @@ -832,7 +858,7 @@ II. INSTALLATION needed to compile Apache. Note that this is the actual awk program, not an installer - just rename it to awk.exe and it is ready to use. * Apache apr, apr-util, and optionally apr-iconv libraries, version - 1.5 or later (1.2 for apr-iconv). If you are building from a Subversion + 1.4 or later (1.2 for apr-iconv). If you are building from a Subversion checkout and have not downloaded Apache 2, then get these 3 libraries from https://www.apache.org/dist/apr/. * SQLite 3.8.2 or higher from https://www.sqlite.org/download.html @@ -1028,6 +1054,12 @@ II. INSTALLATION Note that you'd make sure to define ZLIB_WINAPI in the ZLib config header and move the lib-file into the zlib root-directory. + Please note that you MUST NOT build ZLib with the included assembler + optimized code. It is known to be buggy, see for example the discussion + https://svn.haxx.se/dev/archive-2013-10/0109.shtml. + This means that you must not define ASMV or ASMINF. Note that the VS + projects in contrib\visualstudio define these in the Debug configuration. + Apache Serf ### Section about Apache Serf might be required/useful to add. diff --git a/Makefile.in b/Makefile.in index 48a14bf803297..7f0a37dd9a9b7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -356,7 +356,7 @@ INSTALL_EXTRA_SWIG_PY=\ $(INSTALL_DATA) "$$i" $(DESTDIR)$(swig_pydir); \ done; \ fi; \ - $(PYTHON) -c 'import compileall; \ + $(SWIG_PY_PYTHON) -c 'import compileall; \ compileall.compile_dir("$(DESTDIR)$(swig_pydir)", 1, "$(swig_pydir)"); \ compileall.compile_dir("$(DESTDIR)$(swig_pydir_extra)", 1, \ "$(swig_pydir_extra)");' @@ -396,7 +396,8 @@ HTTPD_VERSION = @HTTPD_VERSION@ HTTPD_WHITELIST = @HTTPD_WHITELIST@ PYTHON = @PYTHON@ -PERL = @PERL@ +SWIG_PY_PYTHON = @SWIG_PY_PYTHON@ +SWIG_PL_PERL = @SWIG_PL_PERL@ JDK = @JDK@ JAVA = @JAVA@ @@ -412,7 +413,7 @@ javahl_compat_CLASSPATH=$(JAVA_CLASSPATH) javahl_tests_CLASSPATH=$(JAVA_CLASSPATH) javahl_compat_tests_CLASSPATH=$(JAVA_CLASSPATH) -RUBY = @RUBY@ +SWIG_RB_RUBY = @SWIG_RB_RUBY@ RUBY_MAJOR = @RUBY_MAJOR@ RUBY_MINOR = @RUBY_MINOR@ RDOC = @RDOC@ @@ -456,8 +457,9 @@ fast-clean: doc-clean done echo $(CLEAN_FILES) | xargs rm -f -- find $(CTYPES_PYTHON_SRC_DIR) $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) \ - $(abs_srcdir)/build $(top_srcdir)/subversion/tests/cmdline/svntest \ - -name "*.pyc" -exec rm {} ';' + $(abs_srcdir)/build $(top_srcdir)/subversion/tests/cmdline \ + '(' -name "__pycache__" -prune -o -name "*.pyc" ')' \ + -exec rm -rf {} ';'; \ # clean everything, returning to before './configure' was run. SVN_CONFIG_SCRIPT_FILES = @SVN_CONFIG_SCRIPT_FILES@ @@ -474,6 +476,7 @@ local-distclean: local-clean local-extraclean: extraclean-bindings local-distclean rm -f $(top_srcdir)/build-outputs.mk \ $(top_srcdir)/subversion/svn_private_config.h.in \ + $(top_srcdir)/aclocal.m4 \ $(top_srcdir)/configure \ $(top_srcdir)/gen-make.opts \ $(top_srcdir)/build/config.guess \ @@ -633,12 +636,12 @@ check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $(TEST_DEPS) @BDB_TEST_DEPS@ # First, set up Apache as documented in # subversion/tests/cmdline/README. -davcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod +davcheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod mod_dontdothat @$(MAKE) check BASE_URL=http://localhost # Automatically configure and run Apache httpd on a random port, and then # run make check. -davautocheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod +davautocheck: bin $(TEST_DEPS) @BDB_TEST_DEPS@ apache-mod mod_dontdothat @# Takes MODULE_PATH, USE_HTTPV1 and SVN_PATH_AUTHZ in the environment. @APXS=$(APXS) MAKE=$(MAKE) $(SHELL) $(top_srcdir)/subversion/tests/cmdline/davautocheck.sh @@ -881,13 +884,13 @@ $(SWIG_PL_DIR)/native/Makefile.PL: $(SWIG_SRC_DIR)/perl/native/Makefile.PL.in ./config.status subversion/bindings/swig/perl/native/Makefile.PL $(SWIG_PL_DIR)/native/Makefile: libsvn_swig_perl $(SWIG_PL_DIR)/native/Makefile.PL - cd $(SWIG_PL_DIR)/native; $(PERL) Makefile.PL PREFIX=$(prefix) + cd $(SWIG_PL_DIR)/native; $(SWIG_PL_PERL) Makefile.PL PREFIX=$(prefix) # There is a "readlink -f" command on some systems for the same purpose, # but it's not as portable (e.g. Mac OS X doesn't have it). These should # only be used where Python/Perl are known to be available. READLINK_PY=$(PYTHON) -c 'import sys,os; print(os.path.realpath(sys.argv[1]))' -READLINK_PL=$(PERL) -e 'use Cwd; print Cwd::realpath(shift)' +READLINK_PL=$(SWIG_PL_PERL) -e 'use Cwd; print Cwd::realpath(shift)' swig-pl: autogen-swig-pl $(SWIG_PL_DIR)/native/Makefile if test "`$(READLINK_PL) $(SWIG_PL_DIR)`" != "`$(READLINK_PL) $(SWIG_PL_SRC_DIR)`"; then \ @@ -928,22 +931,22 @@ clean-swig-pl: extraclean-swig-pl: clean-swig-pl $(EXTRACLEAN_SWIG_PL) +swig-py: autogen-swig-py + $(SWIG_PY_DIR)/libsvn: mkdir $(SWIG_PY_DIR)/libsvn -copy-swig-py: autogen-swig-py $(SWIG_PY_DIR)/libsvn +copy-swig-py: autogen-swig-py swig-py $(SWIG_PY_DIR)/libsvn @for f in $(SWIG_PY_SRC_DIR)/*.py $(SWIG_PY_DIR)/*.py; do \ ! [ -f "$$f" ] || cp -pf $$f $(SWIG_PY_DIR)/libsvn; \ done @cd $(SWIG_PY_DIR)/libsvn;ln -sf ../.libs/*.so . @touch $(SWIG_PY_DIR)/libsvn/__init__.py -swig-py: autogen-swig-py copy-swig-py - -check-swig-py: swig-py +check-swig-py: swig-py copy-swig-py $(TEST_SHLIB_VAR_SWIG_PY) \ cd $(SWIG_PY_DIR); \ - $(PYTHON) $(SWIG_PY_SRC_DIR)/tests/run_all.py + $(SWIG_PY_PYTHON) $(SWIG_PY_SRC_DIR)/tests/run_all.py EXTRACLEAN_SWIG_PY=rm -rf $(SWIG_PY_DIR)/svn_*.c $(SWIG_PY_DIR)/core.c \ $(SWIG_PY_DIR)/[a-z]*.py @@ -957,7 +960,9 @@ clean-swig-py: do \ cd $$d && rm -rf *.lo *.la *.o *.pyc .libs; \ done - find $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) -name "*.pyc" -exec rm {} ';' + find $(SWIG_PY_SRC_DIR) $(SWIG_PY_DIR) \ + '(' -name "__pycache__" -prune -o -name "*.pyc" ')' \ + -exec rm -rf {} ';' extraclean-swig-py: clean-swig-py $(EXTRACLEAN_SWIG_PY) @@ -968,7 +973,7 @@ check-swig-rb: swig-rb svnserve $(TEST_SHLIB_VAR_SWIG_RB) \ cd $(SWIG_RB_DIR); \ check_rb() { \ - $(RUBY) -I $(SWIG_RB_SRC_DIR) $(SWIG_RB_SRC_DIR)/test/run-test.rb "$$@"; \ + $(SWIG_RB_RUBY) -I $(SWIG_RB_SRC_DIR) $(SWIG_RB_SRC_DIR)/test/run-test.rb "$$@"; \ }; \ if check_rb --help 2>&1 | grep -q -- --collector; then \ check_rb --collector=dir --verbose=$(SWIG_RB_TEST_VERBOSE); \ diff --git a/NOTICE b/NOTICE index a3e794d536e76..e20f9bab50efd 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache Subversion -Copyright 2020 The Apache Software Foundation +Copyright 2024 The Apache Software Foundation This product includes software developed by many people, and distributed under Contributor License Agreements to The Apache Software Foundation diff --git a/README b/README index cf8997a20db3b..7389593af3593 100644 --- a/README +++ b/README @@ -49,8 +49,6 @@ III. PARTICIPATING IN THE SUBVERSION COMMUNITY It describes Subversion coding and log message standards, as well as how to join discussion lists. - Talk on IRC with developers: irc.freenode.net, channel #svn-dev. - Read the FAQ: http://subversion.apache.org/faq.html diff --git a/STATUS b/STATUS new file mode 100644 index 0000000000000..19a0bc919388d --- /dev/null +++ b/STATUS @@ -0,0 +1,90 @@ + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * THIS RELEASE STREAM IS OPEN FOR STABILIZATION. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +This file tracks the status of releases in the 1.14.x line. + +See http://subversion.apache.org/docs/community-guide/releasing.html#release-stabilization +for details on how release lines and voting work, what kinds of bugs can +delay a release, etc. + +Status of 1.14.6: + +Candidate changes: +================== + + * r1883715 + Let users select the C++ language standard. + Justification: + Required for backport of the the upcoming patch to support KWallet 6 + (if we decide to accept some version of the patch). See: + https://lists.apache.org/thread/hycj045wp5jjgzop96w7bc6dc3bv42kf + Votes: + +1: brane, jamessan + + * r1890223, r1890668, r1890673 + Support building on Win64/ARM64. + Justification: + At user request (via SharpSvn) + Votes: + +1: rhuijben + + * r1926541, r1926564, r1926565 + Use the SecItem* API for macOS Keychain support. + Justification: + The SecKeychain* API has been deprecated for more than a decade. + Votes: + +1: brane + + * r1931334, r1931359, r1931369, r1931373 + Remove a potentially data-truncating typecast in our adler32 implementation. + Justification: + Do not silently compute the wrong checksum. + Branch: + 1.14.x-r1931334 + Notes: + Triggering this bug was not likely, but still possible. In addition, we + now use adler32_z() when available, which may be slightly more efficient. + Votes: + +1: brane + + * r1934466 + Fix detection for zlib version in vcxproj build on Windows. + Justification: + Fix vcxproj build with zlib 1.3.2+ on Windows. + Votes: + +1: jun66j5, ivan + + * r1934787, r1934811 + Fix potential use of uninitialized memory in the conflict resolver. + Justification: + Subversion should try to not crash. + Votes: + +1: brane + + * r1934786 + Fix an uninitialized memory access bug in FSX. + Justification: + FSX is experimental, but is shouldn't corrupt memory. + Votes: + +1: brane, ivan + + * r1934788 + Fix sprintf() deprecation warning on macOS newer versions of Xcode. + Justification: + Warnings, especially false positives, are distracting. + Votes: + +1: brane + + * r1935107 + Fix a bug in JavaHL where native code could modify a final attribute. + Votes: + +1: brane + +Veto-blocked changes: +===================== + +Approved changes: +================= diff --git a/aclocal.m4 b/aclocal.m4.in similarity index 100% rename from aclocal.m4 rename to aclocal.m4.in diff --git a/autogen.sh b/autogen.sh index e1961d6ffd34f..8bc5cffd5bdde 100755 --- a/autogen.sh +++ b/autogen.sh @@ -51,6 +51,17 @@ while test $# != 0; do ;; esac done + +# Generate aclocal.m4 +cp -f aclocal.m4.in aclocal.m4 +if test -n "$RELEASE_MODE"; then + cat <>aclocal.m4 + +# Generated by 'autogen.sh --release' +AC_DEFUN([SVN_RELEASE_MODE],[1]) +EOF +fi + # ### The order of parameters is important; buildcheck.sh depends on it and # ### we don't want to copy the fancy option parsing loop there. For the # ### same reason, all parameters should be quoted, so that buildcheck.sh @@ -167,6 +178,7 @@ if test -z "$PYTHON"; then echo "to the Python executable, and re-run autogen.sh" exit 1 fi +export PYTHON # Compile SWIG headers into standalone C files if we are in release mode if test -n "$RELEASE_MODE"; then @@ -176,10 +188,10 @@ if test -n "$RELEASE_MODE"; then "$PYTHON" ./gen-make.py build.conf || gen_failed=1 # Build the SWIG-related files - make -f autogen-standalone.mk autogen-swig + make -f autogen-standalone.mk autogen-swig || gen_failed=1 - # Remove the .swig_checked file - rm -f .swig_checked + # Remove the .swig_*checked files + rm -f .swig_checked .swig_pl_checked .swig_py_checked .swig_rb_checked fi if test -n "$SKIP_DEPS"; then diff --git a/build.conf b/build.conf index 95422b6600b5a..6981982384704 100644 --- a/build.conf +++ b/build.conf @@ -53,6 +53,7 @@ private-includes = subversion/libsvn_subr/utf8proc/utf8proc_internal.h subversion/libsvn_subr/utf8proc/utf8proc.c subversion/libsvn_subr/utf8proc/utf8proc_data.c + subversion/svn/filesize.c private-built-includes = subversion/svn_private_config.h subversion/libsvn_fs_fs/rep-cache-db.h @@ -152,7 +153,7 @@ libs = libsvn_client libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr apriconv apr manpages = subversion/svn/svn.1 install = bin -msvc-libs = setargv.obj +msvc-libs = wsetargv.obj # The subversion repository administration tool [svnadmin] @@ -162,7 +163,7 @@ path = subversion/svnadmin install = bin manpages = subversion/svnadmin/svnadmin.1 libs = libsvn_repos libsvn_fs libsvn_delta libsvn_subr apriconv apr -msvc-libs = setargv.obj +msvc-libs = wsetargv.obj # The subversion repository dump filtering tool [svndumpfilter] @@ -248,7 +249,7 @@ type = lib path = subversion/libsvn_client libs = libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr apriconv apr install = lib -msvc-export = svn_client.h private/svn_client_mtcc.h private/svn_client_private.h +msvc-export = svn_client.h private/svn_client_mtcc.h private/svn_client_private.h private/svn_client_shelf.h private/svn_client_shelf2.h # Routines for binary diffing and tree-deltas [libsvn_delta] @@ -831,7 +832,7 @@ type = exe path = subversion/tests/libsvn_fs_fs sources = fs-fs-private-test.c install = test -libs = libsvn_test libsvn_fs libsvn_delta +libs = libsvn_test libsvn_fs libsvn_fs_fs libsvn_delta libsvn_repos libsvn_subr apriconv apr msvc-force-static = yes @@ -1338,6 +1339,18 @@ install = test libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr msvc-force-static = yes +# ---------------------------------------------------------------------------- +# Tests for the client's internal functions + +[filesize-test] +description = Test conversion of file sizes to human-readable form +type = exe +path = subversion/tests/client +sources = filesize-test.c +install = test +libs = libsvn_client libsvn_test libsvn_wc libsvn_subr apriconv apr +msvc-force-static = yes + # ---------------------------------------------------------------------------- # These are not unit tests at all, they are small programs that exercise # parts of the libsvn_delta API from the command line. They are stuck here @@ -1565,7 +1578,7 @@ libs = __ALL__ checksum-test compat-test config-test hashdump-test mergeinfo-test opt-test packed-data-test path-test prefix-string-test priority-queue-test root-pools-test stream-test - string-test time-test utf-test bit-array-test + string-test time-test utf-test bit-array-test filesize-test error-test error-code-test cache-test spillbuf-test crypto-test revision-test subst_translate-test io-test diff --git a/build/ac-macros/macosx.m4 b/build/ac-macros/macosx.m4 index 92fa58e0bcb27..b131379e8343b 100644 --- a/build/ac-macros/macosx.m4 +++ b/build/ac-macros/macosx.m4 @@ -23,22 +23,25 @@ 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_RUN_IFELSE([AC_LANG_PROGRAM([[ - #include - #include - ]],[[ - const struct mach_header *header = _dyld_get_image_header(0); - const char *name = _dyld_get_image_name(0); - if (name && header) return 0; - return 1; - ]])],[ + AC_CACHE_CHECK([for Mach-O dynamic module iteration functions], + [ac_cv_mach_o_dynamic_module_iteration_works], [ + AC_RUN_IFELSE([AC_LANG_PROGRAM([[ + #include + #include + ]],[[ + const struct mach_header *header = _dyld_get_image_header(0); + const char *name = _dyld_get_image_name(0); + if (name && header) return 0; + return 1; + ]])], + [ac_cv_mach_o_dynamic_module_iteration_works=yes], + [ac_cv_mach_o_dynamic_module_iteration_works=no], + [ac_cv_mach_o_dynamic_module_iteration_works=no]) + ]) + if test "$ac_cv_mach_o_dynamic_module_iteration_works" = yes; then AC_DEFINE([SVN_HAVE_MACHO_ITERATE], [1], [Is Mach-O low-level _dyld API available?]) - AC_MSG_RESULT([yes]) - ],[ - AC_MSG_RESULT([no]) - ]) + fi ]) dnl SVN_LIB_MACOS_PLIST diff --git a/build/ac-macros/swig.m4 b/build/ac-macros/swig.m4 index c707a8c282539..66e9fc8beaa75 100644 --- a/build/ac-macros/swig.m4 +++ b/build/ac-macros/swig.m4 @@ -21,33 +21,40 @@ dnl check to see if SWIG is current enough. dnl dnl if it is, then check to see if we have the correct version of python. dnl -dnl if we do, then set up the appropriate SWIG_ variables to build the -dnl python bindings. +dnl if we do, then set up the appropriate SWIG_ variables to build the +dnl Python, Perl, and Ruby bindings. AC_DEFUN(SVN_CHECK_SWIG, [ - AC_ARG_WITH(swig, - AS_HELP_STRING([--with-swig=PATH], - [Try to use 'PATH/bin/swig' to build the - swig bindings. If PATH is not specified, - look for a 'swig' binary in your PATH.]), + m4_ifndef([SVN_RELEASE_MODE], [ - case "$withval" in - "no") - SWIG_SUITABLE=no - SVN_FIND_SWIG(no) - ;; - "yes") - SVN_FIND_SWIG(required) + AC_ARG_WITH(swig, + AS_HELP_STRING([--with-swig=PATH], + [Try to use 'PATH/bin/swig' to build the + swig bindings. If PATH is not specified, + look for a 'swig' binary in your PATH.]), + [ + case "$withval" in + yes) + svn_find_swig_arg=required ;; *) - SVN_FIND_SWIG($withval) + svn_find_swig_arg=$withval ;; - esac - ], - [ - SVN_FIND_SWIG(check) + esac + ], + [ + if test "$SWIG_PY_PYTHON" != "none" \ + || test "$SWIG_PL_PERL" != "none" \ + || test "$SWIG_RB_RUBY" != "none" ; then + svn_find_swig_arg=check + else + svn_find_swig_arg=no + fi + ]) + SVN_FIND_SWIG($svn_find_swig_arg) ]) + SVN_DETERMINE_SWIG_OPTS ]) AC_DEFUN(SVN_FIND_SWIG, @@ -69,7 +76,7 @@ AC_DEFUN(SVN_FIND_SWIG, fi if test ! -f "$SWIG" || test ! -x "$SWIG"; then AC_MSG_ERROR([Could not find swig binary at $SWIG]) - fi + fi fi if test "$SWIG" != "none"; then @@ -78,11 +85,11 @@ AC_DEFUN(SVN_FIND_SWIG, $SED -ne 's/^.*Version \(.*\)$/\1/p'`" # We want the version as an integer so we can test against # which version we're using. SWIG doesn't provide this - # to us so we have to come up with it on our own. + # to us so we have to come up with it on our own. # The major is passed straight through, # the minor is zero padded to two places, # and the patch level is zero padded to three places. - # e.g. 1.3.24 becomes 103024 + # e.g. 1.3.40 becomes 103040 SWIG_VERSION="`echo \"$SWIG_VERSION_RAW\" | \ $SED -e 's/[[^0-9\.]].*$//' \ -e 's/\.\([[0-9]]\)$/.0\1/' \ @@ -91,246 +98,490 @@ AC_DEFUN(SVN_FIND_SWIG, AC_MSG_RESULT([$SWIG_VERSION_RAW]) # If you change the required swig version number, don't forget to update: # subversion/bindings/swig/INSTALL - if test -n "$SWIG_VERSION" && test "$SWIG_VERSION" -ge "103024"; then - SWIG_SUITABLE=yes - else - SWIG_SUITABLE=no + if test ! -n "$SWIG_VERSION" || test "$SWIG_VERSION" -lt "103040"; then AC_MSG_WARN([Detected SWIG version $SWIG_VERSION_RAW]) - AC_MSG_WARN([Subversion requires SWIG >= 1.3.24]) + AC_MSG_WARN([Subversion requires SWIG >= 1.3.40]) fi fi - - SWIG_PY_COMPILE="none" - SWIG_PY_LINK="none" - SWIG_PY_OPTS="none" - SWIG_PY_ERRMSG="check config.log for details" - if test "$PYTHON" != "none"; then - AC_MSG_NOTICE([Configuring python swig binding]) - - AC_CACHE_CHECK([for Python includes], [ac_cv_python_includes],[ - ac_cv_python_includes="`$PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" - ]) - SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" - - if test "$ac_cv_python_includes" = "none"; then - SWIG_PY_ERRMSG="no distutils found" - AC_MSG_WARN([python bindings cannot be built without distutils module]) - else - - python_header_found="no" +]) - save_cppflags="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $ac_cv_python_includes" - AC_CHECK_HEADER(Python.h, [ - python_header_found="yes" - ]) - CPPFLAGS="$save_cppflags" - if test "$python_header_found" = "no"; then - SWIG_PY_ERRMSG="no Python.h found" - AC_MSG_WARN([Python.h not found; disabling python swig bindings]) +AC_DEFUN(SVN_DETERMINE_SWIG_OPTS, +[ + m4_ifndef([SVN_RELEASE_MODE], + [ + # not in release mode + SWIG_PY_COMPILE="none" + SWIG_PY_LINK="none" + SWIG_PY_OPTS="none" + SWIG_PY_ERRMSG="check config.log for details" + if test "$SWIG_PY_PYTHON" = "none"; then + SWIG_PY_ERRMSG="You specfied not to build Python bindings or \ +suitable Python interpreter is not found." + else + if test "$SWIG" = "none"; then + AC_MSG_WARN([You specified to build SWIG Python bindings, but SWIG is not found.]) + SWIG_PY_ERRMSG="SWIG is need to build SWIG Python bindings, but it is not found." else - SVN_PY3C() + AC_MSG_NOTICE([Configuring python swig binding]) + + AC_CACHE_CHECK([for Python includes], [ac_cv_python_includes],[ + ac_cv_python_includes="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" + ]) + SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" - if test "$py3c_found" = "no"; then - SWIG_PY_ERRMSG="py3c library not found" - AC_MSG_WARN([py3c library not found; disabling python swig bindings]) + if test "$ac_cv_python_includes" = "none"; then + SWIG_PY_ERRMSG="no distutils found" + AC_MSG_WARN([python bindings cannot be built without distutils module]) else - AC_CACHE_CHECK([for compiling Python extensions], [ac_cv_python_compile],[ - ac_cv_python_compile="`$PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" - ]) - SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" - - AC_CACHE_CHECK([for linking Python extensions], [ac_cv_python_link],[ - ac_cv_python_link="`$PYTHON ${abs_srcdir}/build/get-py-info.py --link`" - ]) - SWIG_PY_LINK="$ac_cv_python_link" - - AC_CACHE_CHECK([for linking Python libraries], [ac_cv_python_libs],[ - ac_cv_python_libs="`$PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" - ]) - SWIG_PY_LIBS="`SVN_REMOVE_STANDARD_LIB_DIRS($ac_cv_python_libs)`" - AC_CACHE_CHECK([for Python >= 3], [ac_cv_python_is_py3],[ - ac_cv_python_is_py3="no" - $PYTHON -c 'import sys; sys.exit(0x3000000 > sys.hexversion)' && \ - ac_cv_python_is_py3="yes" + python_header_found="no" + + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $ac_cv_python_includes" + AC_CHECK_HEADER(Python.h, [ + python_header_found="yes" ]) + CPPFLAGS="$save_cppflags" - if test "$ac_cv_python_is_py3" = "yes"; then - if test "$SWIG_VERSION" -ge "300010"; then - dnl SWIG Python bindings successfully configured, clear the error message dnl - SWIG_PY_ERRMSG="" - else - SWIG_PY_ERRMSG="SWIG version is not suitable" - AC_MSG_WARN([Subversion Python bindings for Python 3 require SWIG 3.0.10 or newer]) - fi - if test "$SWIG_VERSION" -lt "400000"; then - SWIG_PY_OPTS="-python -py3 -nofastunpack -modern" - else - SWIG_PY_OPTS="-python -py3 -nofastunpack" - fi + if test "$python_header_found" = "no"; then + SWIG_PY_ERRMSG="no Python.h found" + AC_MSG_WARN([Python.h not found; disabling python swig bindings]) else - if test "$SWIG_VERSION" -lt "400000"; then - SWIG_PY_OPTS="-python -classic" - dnl SWIG Python bindings successfully configured, clear the error message dnl - SWIG_PY_ERRMSG="" + SVN_PY3C() + + if test "$py3c_found" = "no"; then + SWIG_PY_ERRMSG="py3c library not found" + AC_MSG_WARN([py3c library not found; disabling python swig bindings]) else - SWIG_PY_OPTS="-python -nofastunpack" - SWIG_PY_ERRMSG="SWIG version is not suitable" - AC_MSG_WARN([Subversion Python bindings for Python 2 require 1.3.24 <= SWIG < 4.0.0]) + AC_CACHE_CHECK([for compiling Python extensions], [ac_cv_python_compile],[ + ac_cv_python_compile="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" + ]) + SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" + + AC_CACHE_CHECK([for linking Python extensions], [ac_cv_python_link],[ + ac_cv_python_link="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --link`" + ]) + SWIG_PY_LINK="$ac_cv_python_link" + + AC_CACHE_CHECK([for linking Python libraries], [ac_cv_python_libs],[ + ac_cv_python_libs="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" + ]) + SWIG_PY_LIBS="`SVN_REMOVE_STANDARD_LIB_DIRS($ac_cv_python_libs)`" + + # Look more closely at the SWIG and Python versions to + # determine SWIG_PY_OPTS. We can skip this if we already + # have the SWIG-generated files. + AC_CACHE_CHECK([for Python >= 3], [ac_cv_python_is_py3],[ + ac_cv_python_is_py3="no" + $SWIG_PY_PYTHON -c 'import sys; sys.exit(0x3000000 > sys.hexversion)' && \ + ac_cv_python_is_py3="yes" + ]) + + if test "$ac_cv_python_is_py3" = "yes"; then + if test "$SWIG_VERSION" -ge "300010"; then + dnl SWIG Python bindings successfully configured, clear the error message dnl + SWIG_PY_ERRMSG="" + if test "$SWIG_VERSION" -lt "400000"; then + SWIG_PY_OPTS="-python -py3 -nofastunpack -modern" + elif test "$SWIG_VERSION" -lt "401000"; then + SWIG_PY_OPTS="-python -py3 -nofastunpack" + else + SWIG_PY_OPTS="-python -nofastunpack" + fi + if test "$SWIG_VERSION" -gt "400002"; then + AC_MSG_WARN([Subversion Python bindings may work,]) + AC_MSG_WARN([but we didn't check with this SWIG version.]) + fi + else + SWIG_PY_OPTS="-no-such-option" # fool proof + SWIG_PY_ERRMSG="SWIG version is not suitable" + AC_MSG_WARN([Subversion Python bindings for Python 3 require SWIG 3.0.10 or newer]) + fi + else + if test "$SWIG_VERSION" -lt "400000"; then + SWIG_PY_OPTS="-python -classic" + dnl SWIG Python bindings successfully configured, clear the error message dnl + SWIG_PY_ERRMSG="" + else + SWIG_PY_OPTS="-no-such-option" # fool proof + SWIG_PY_ERRMSG="SWIG version is not suitable" + AC_MSG_WARN([Subversion Python bindings for Python 2 require 1.3.40 <= SWIG < 4.0.0]) + fi + fi fi fi fi - fi fi - fi - - SWIG_PL_ERRMSG="check config.log for details" - if test "$PERL" != "none"; then - AC_MSG_CHECKING([perl version]) - dnl Note that the q() bit is there to avoid unbalanced brackets - dnl which m4 really doesn't like. - PERL_VERSION="`$PERL -e 'q([[); print $]] * 1000000,$/;'`" - AC_MSG_RESULT([$PERL_VERSION]) - if test "$PERL_VERSION" -ge "5008000"; then - SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$PERL -MExtUtils::Embed -e ccopts`" - SWIG_PL_LINK="`$PERL -MExtUtils::Embed -e ldopts`" - SWIG_PL_LINK="`SVN_REMOVE_STANDARD_LIB_DIRS($SWIG_PL_LINK)`" - - dnl SWIG Perl bindings successfully configured, clear the error message - SWIG_PL_ERRMSG="" + SWIG_PL_ERRMSG="check config.log for details" + if test "$SWIG_PL_PERL" = "none"; then + SWIG_PL_ERRMSG="You specfied not to build Perl bindings or \ +suitable Perl interpreter is not found." else - AC_MSG_WARN([perl bindings require perl 5.8.0 or newer.]) + if test "$SWIG" = "none"; then + AC_MSG_WARN([You specified to build SWIG Perl bindings, but SWIG is not found.]) + SWIG_PL_ERRMSG="SWIG is need to build SWIG Perl bindings, but it is not found." + else + AC_MSG_CHECKING([perl version]) + dnl Note that the q() bit is there to avoid unbalanced brackets + dnl which m4 really doesn't like. + PERL_VERSION="`$SWIG_PL_PERL -e 'q([[); print $]] * 1000000,$/;'`" + AC_MSG_RESULT([$PERL_VERSION]) + if test "$PERL_VERSION" -ge "5008000"; then + SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$SWIG_PL_PERL -MExtUtils::Embed -e ccopts`" + SWIG_PL_LINK="`$SWIG_PL_PERL -MExtUtils::Embed -e ldopts`" + SWIG_PL_LINK="`SVN_REMOVE_STANDARD_LIB_DIRS($SWIG_PL_LINK)`" + + dnl SWIG Perl bindings successfully configured, clear the error message + SWIG_PL_ERRMSG="" + else + AC_MSG_WARN([perl bindings require perl 5.8.0 or newer.]) + fi + fi fi - fi - SWIG_RB_COMPILE="none" - SWIG_RB_LINK="none" - SWIG_RB_ERRMSG="check config.log for details" - if test "$RUBY" != "none"; then - if test x"$SWIG_VERSION" = x"3""00""008"; then - # Use a local variable to escape the '#' sign. - ruby_swig_issue_602='https://subversion.apache.org/docs/release-notes/1.11#ruby-swig-issue-602' - AC_MSG_WARN([Ruby bindings are known not to support swig 3.0.8; see $ruby_swig_issue_602]) + SWIG_RB_COMPILE="none" + SWIG_RB_LINK="none" + SWIG_RB_ERRMSG="check config.log for details" + if test "$SWIG_RB_RUBY" = "none"; then + SWIG_RB_ERRMSG="You specfied not to build Ruby bindings or \ +suitable Ruby interpreter is not found." + else + if test "$SWIG" = "none"; then + AC_MSG_WARN([You specified to build SWIG Ruby bindings, but SWIG is not found.]) + SWIG_RB_ERRMSG="SWIG is need to build SWIG Ruby bindings, but it is not found." + else + if test x"$SWIG_VERSION" = x"3""00""008"; then + # Use a local variable to escape the '#' sign. + ruby_swig_issue_602='https://subversion.apache.org/docs/release-notes/1.11#ruby-swig-issue-602' + AC_MSG_WARN([Ruby bindings are known not to support swig 3.0.8; see $ruby_swig_issue_602]) + fi + rbconfig="$SWIG_RB_RUBY -rrbconfig -e " + + for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ + rubyhdrdir rubyarchhdrdir sitedir sitelibdir sitearchdir libdir + do + rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG@<:@'$var_name'@:>@"` + eval "rbconfig_$var_name=\"$rbconfig_tmp\"" + done + + AC_MSG_NOTICE([Configuring Ruby SWIG binding]) + + AC_CACHE_CHECK([for Ruby include path], [svn_cv_ruby_includes],[ + if test -d "$rbconfig_rubyhdrdir"; then + dnl Ruby >=1.9 + svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir" + if test -d "$rbconfig_rubyarchhdrdir"; then + dnl Ruby >=2.0 + svn_cv_ruby_includes="$svn_cv_ruby_includes -I$rbconfig_rubyarchhdrdir" + else + svn_cv_ruby_includes="$svn_cv_ruby_includes -I$rbconfig_rubyhdrdir/$rbconfig_arch" + fi + else + dnl Ruby 1.8 + svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + fi + ]) + SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" + + AC_CACHE_CHECK([how to compile Ruby extensions], [svn_cv_ruby_compile],[ + svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" + ]) + SWIG_RB_COMPILE="$svn_cv_ruby_compile" + SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-ansi]) + SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-std=c89]) + SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-std=c90]) + dnl FIXME: Check that the compiler for Ruby actually supports this flag + SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" + + AC_CACHE_CHECK([how to link Ruby extensions], [svn_cv_ruby_link],[ + svn_cv_ruby_link="`$SWIG_RB_RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ + $rbconfig_LDSHARED`" + svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" + svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" + ]) + SWIG_RB_LINK="$svn_cv_ruby_link" + + AC_CACHE_CHECK([how to link Ruby libraries], [ac_cv_ruby_libs], [ + ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" + ]) + SWIG_RB_LIBS="`SVN_REMOVE_STANDARD_LIB_DIRS($ac_cv_ruby_libs)`" + + AC_MSG_CHECKING([for rb_errinfo]) + old_CFLAGS="$CFLAGS" + old_LIBS="$LIBS" + CFLAGS="$CFLAGS $svn_cv_ruby_includes" + SVN_STRIP_FLAG([CFLAGS], [-ansi]) + SVN_STRIP_FLAG([CFLAGS], [-std=c89]) + SVN_STRIP_FLAG([CFLAGS], [-std=c90]) + LIBS="$SWIG_RB_LIBS" + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +#include +int main() +{rb_errinfo();}]])], have_rb_errinfo="yes", have_rb_errinfo="no") + if test "$have_rb_errinfo" = "yes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_RB_ERRINFO], [1], + [Define to 1 if you have the `rb_errinfo' function.]) + else + AC_MSG_RESULT([no]) + fi + CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" + + AC_CACHE_VAL([svn_cv_ruby_sitedir],[ + svn_cv_ruby_sitedir="$rbconfig_sitedir" + ]) + AC_ARG_WITH([ruby-sitedir], + AS_HELP_STRING([--with-ruby-sitedir=SITEDIR], + [install Ruby bindings in SITEDIR + (default is same as ruby's one)]), + [svn_ruby_installdir="$withval"], + [svn_ruby_installdir="$svn_cv_ruby_sitedir"]) + + AC_MSG_CHECKING([where to install Ruby scripts]) + AC_CACHE_VAL([svn_cv_ruby_sitedir_libsuffix],[ + svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + ]) + SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" + AC_MSG_RESULT([$SWIG_RB_SITE_LIB_DIR]) + + AC_MSG_CHECKING([where to install Ruby extensions]) + AC_CACHE_VAL([svn_cv_ruby_sitedir_archsuffix],[ + svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + ]) + SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" + AC_MSG_RESULT([$SWIG_RB_SITE_ARCH_DIR]) + + AC_MSG_CHECKING([how to use output level for Ruby bindings tests]) + AC_CACHE_VAL([svn_cv_ruby_test_verbose],[ + svn_cv_ruby_test_verbose="normal" + ]) + AC_ARG_WITH([ruby-test-verbose], + AS_HELP_STRING([--with-ruby-test-verbose=LEVEL], + [how to use output level for Ruby bindings tests + (default is normal)]), + [svn_ruby_test_verbose="$withval"], + [svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"]) + SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" + AC_MSG_RESULT([$SWIG_RB_TEST_VERBOSE]) + + dnl SWIG Ruby bindings successfully configured, clear the error message + SWIG_RB_ERRMSG="" + fi fi - rbconfig="$RUBY -rrbconfig -e " - - for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ - rubyhdrdir rubyarchhdrdir sitedir sitelibdir sitearchdir libdir - do - rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG@<:@'$var_name'@:>@"` - eval "rbconfig_$var_name=\"$rbconfig_tmp\"" - done - - AC_MSG_NOTICE([Configuring Ruby SWIG binding]) - - AC_CACHE_CHECK([for Ruby include path], [svn_cv_ruby_includes],[ - if test -d "$rbconfig_rubyhdrdir"; then - dnl Ruby >=1.9 - svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir" - if test -d "$rbconfig_rubyarchhdrdir"; then - dnl Ruby >=2.0 - svn_cv_ruby_includes="$svn_cv_ruby_includes -I$rbconfig_rubyarchhdrdir" + ], + [ + # in release mode + SWIG_PY_COMPILE="none" + SWIG_PY_LINK="none" + SWIG_PY_OPTS="none" + SWIG_PY_ERRMSG="check config.log for details" + if test "$SWIG_PY_PYTHON" = "none"; then + SWIG_PY_ERRMSG="You specfied not to build Python bindings or \ +suitable Python interpreter is not found." + else + AC_MSG_NOTICE([Configuring python swig binding]) + + AC_CACHE_CHECK([for Python includes], [ac_cv_python_includes],[ + ac_cv_python_includes="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --includes`" + ]) + SWIG_PY_INCLUDES="\$(SWIG_INCLUDES) $ac_cv_python_includes" + + if test "$ac_cv_python_includes" = "none"; then + SWIG_PY_ERRMSG="no distutils found" + AC_MSG_WARN([python bindings cannot be built without distutils module]) else - svn_cv_ruby_includes="$svn_cv_ruby_includes -I$rbconfig_rubyhdrdir/$rbconfig_arch" + + python_header_found="no" + + save_cppflags="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $ac_cv_python_includes" + AC_CHECK_HEADER(Python.h, [ + python_header_found="yes" + ]) + CPPFLAGS="$save_cppflags" + + if test "$python_header_found" = "no"; then + SWIG_PY_ERRMSG="no Python.h found" + AC_MSG_WARN([Python.h not found; disabling python swig bindings]) + else + SVN_PY3C() + + if test "$py3c_found" = "no"; then + SWIG_PY_ERRMSG="py3c library not found" + AC_MSG_WARN([py3c library not found; disabling python swig bindings]) + else + AC_CACHE_CHECK([for compiling Python extensions], [ac_cv_python_compile],[ + ac_cv_python_compile="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --compile`" + ]) + SWIG_PY_COMPILE="$ac_cv_python_compile $CFLAGS" + + AC_CACHE_CHECK([for linking Python extensions], [ac_cv_python_link],[ + ac_cv_python_link="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --link`" + ]) + SWIG_PY_LINK="$ac_cv_python_link" + + AC_CACHE_CHECK([for linking Python libraries], [ac_cv_python_libs],[ + ac_cv_python_libs="`$SWIG_PY_PYTHON ${abs_srcdir}/build/get-py-info.py --libs`" + ]) + SWIG_PY_LIBS="`SVN_REMOVE_STANDARD_LIB_DIRS($ac_cv_python_libs)`" + + SWIG_PY_ERRMSG="" + fi + fi fi + fi + + SWIG_PL_ERRMSG="check config.log for details" + if test "$SWIG_PL_PERL" = "none"; then + SWIG_PL_ERRMSG="You specfied not to build Perl bindings or \ +suitable Perl interpreter is not found." else - dnl Ruby 1.8 - svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + AC_MSG_CHECKING([perl version]) + dnl Note that the q() bit is there to avoid unbalanced brackets + dnl which m4 really doesn't like. + PERL_VERSION="`$SWIG_PL_PERL -e 'q([[); print $]] * 1000000,$/;'`" + AC_MSG_RESULT([$PERL_VERSION]) + if test "$PERL_VERSION" -ge "5008000"; then + SWIG_PL_INCLUDES="\$(SWIG_INCLUDES) `$SWIG_PL_PERL -MExtUtils::Embed -e ccopts`" + SWIG_PL_LINK="`$SWIG_PL_PERL -MExtUtils::Embed -e ldopts`" + SWIG_PL_LINK="`SVN_REMOVE_STANDARD_LIB_DIRS($SWIG_PL_LINK)`" + + dnl SWIG Perl bindings successfully configured, clear the error message + SWIG_PL_ERRMSG="" + else + AC_MSG_WARN([perl bindings require perl 5.8.0 or newer.]) + fi fi - ]) - SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" - AC_CACHE_CHECK([how to compile Ruby extensions], [svn_cv_ruby_compile],[ - svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" - ]) - SWIG_RB_COMPILE="$svn_cv_ruby_compile" - SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-ansi]) - SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-std=c89]) - SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-std=c90]) - dnl FIXME: Check that the compiler for Ruby actually supports this flag - SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" - - AC_CACHE_CHECK([how to link Ruby extensions], [svn_cv_ruby_link],[ - svn_cv_ruby_link="`$RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ - $rbconfig_LDSHARED`" - svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" - svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" - ]) - SWIG_RB_LINK="$svn_cv_ruby_link" + SWIG_RB_COMPILE="none" + SWIG_RB_LINK="none" + SWIG_RB_ERRMSG="check config.log for details" + if test "$SWIG_RB_RUBY" = "none"; then + SWIG_RB_ERRMSG="You specfied not to build Ruby bindings or \ +suitable Ruby interpreter is not found." + else + rbconfig="$SWIG_RB_RUBY -rrbconfig -e " - AC_CACHE_CHECK([how to link Ruby libraries], [ac_cv_ruby_libs], [ - ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" - ]) - SWIG_RB_LIBS="`SVN_REMOVE_STANDARD_LIB_DIRS($ac_cv_ruby_libs)`" - - AC_MSG_CHECKING([for rb_errinfo]) - old_CFLAGS="$CFLAGS" - old_LIBS="$LIBS" - CFLAGS="$CFLAGS $svn_cv_ruby_includes" - SVN_STRIP_FLAG([CFLAGS], [-ansi]) - SVN_STRIP_FLAG([CFLAGS], [-std=c89]) - SVN_STRIP_FLAG([CFLAGS], [-std=c90]) - LIBS="$SWIG_RB_LIBS" - AC_LINK_IFELSE([AC_LANG_SOURCE([[ + for var_name in arch archdir CC LDSHARED DLEXT LIBS LIBRUBYARG \ + rubyhdrdir rubyarchhdrdir sitedir sitelibdir sitearchdir libdir + do + rbconfig_tmp=`$rbconfig "print RbConfig::CONFIG@<:@'$var_name'@:>@"` + eval "rbconfig_$var_name=\"$rbconfig_tmp\"" + done + + AC_MSG_NOTICE([Configuring Ruby SWIG binding]) + + AC_CACHE_CHECK([for Ruby include path], [svn_cv_ruby_includes],[ + if test -d "$rbconfig_rubyhdrdir"; then + dnl Ruby >=1.9 + svn_cv_ruby_includes="-I. -I$rbconfig_rubyhdrdir" + if test -d "$rbconfig_rubyarchhdrdir"; then + dnl Ruby >=2.0 + svn_cv_ruby_includes="$svn_cv_ruby_includes -I$rbconfig_rubyarchhdrdir" + else + svn_cv_ruby_includes="$svn_cv_ruby_includes -I$rbconfig_rubyhdrdir/$rbconfig_arch" + fi + else + dnl Ruby 1.8 + svn_cv_ruby_includes="-I. -I$rbconfig_archdir" + fi + ]) + SWIG_RB_INCLUDES="\$(SWIG_INCLUDES) $svn_cv_ruby_includes" + + AC_CACHE_CHECK([how to compile Ruby extensions], [svn_cv_ruby_compile],[ + svn_cv_ruby_compile="$rbconfig_CC $CFLAGS" + ]) + SWIG_RB_COMPILE="$svn_cv_ruby_compile" + SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-ansi]) + SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-std=c89]) + SVN_STRIP_FLAG([SWIG_RB_COMPILE], [-std=c90]) + dnl FIXME: Check that the compiler for Ruby actually supports this flag + SWIG_RB_COMPILE="$SWIG_RB_COMPILE -Wno-int-to-pointer-cast" + + AC_CACHE_CHECK([how to link Ruby extensions], [svn_cv_ruby_link],[ + svn_cv_ruby_link="`$SWIG_RB_RUBY -e 'ARGV.shift; print ARGV.join(%q( ))' \ + $rbconfig_LDSHARED`" + svn_cv_ruby_link="$rbconfig_CC $svn_cv_ruby_link" + svn_cv_ruby_link="$svn_cv_ruby_link -shrext .$rbconfig_DLEXT" + ]) + SWIG_RB_LINK="$svn_cv_ruby_link" + + AC_CACHE_CHECK([how to link Ruby libraries], [ac_cv_ruby_libs], [ + ac_cv_ruby_libs="$rbconfig_LIBRUBYARG $rbconfig_LIBS" + ]) + SWIG_RB_LIBS="`SVN_REMOVE_STANDARD_LIB_DIRS($ac_cv_ruby_libs)`" + + AC_MSG_CHECKING([for rb_errinfo]) + old_CFLAGS="$CFLAGS" + old_LIBS="$LIBS" + CFLAGS="$CFLAGS $svn_cv_ruby_includes" + SVN_STRIP_FLAG([CFLAGS], [-ansi]) + SVN_STRIP_FLAG([CFLAGS], [-std=c89]) + SVN_STRIP_FLAG([CFLAGS], [-std=c90]) + LIBS="$SWIG_RB_LIBS" + AC_LINK_IFELSE([AC_LANG_SOURCE([[ #include int main() {rb_errinfo();}]])], have_rb_errinfo="yes", have_rb_errinfo="no") - if test "$have_rb_errinfo" = "yes"; then - AC_MSG_RESULT([yes]) - AC_DEFINE([HAVE_RB_ERRINFO], [1], - [Define to 1 if you have the `rb_errinfo' function.]) - else - AC_MSG_RESULT([no]) - fi - CFLAGS="$old_CFLAGS" - LIBS="$old_LIBS" + if test "$have_rb_errinfo" = "yes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_RB_ERRINFO], [1], + [Define to 1 if you have the `rb_errinfo' function.]) + else + AC_MSG_RESULT([no]) + fi + CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" - AC_CACHE_VAL([svn_cv_ruby_sitedir],[ - svn_cv_ruby_sitedir="$rbconfig_sitedir" - ]) - AC_ARG_WITH([ruby-sitedir], - AS_HELP_STRING([--with-ruby-sitedir=SITEDIR], - [install Ruby bindings in SITEDIR - (default is same as ruby's one)]), - [svn_ruby_installdir="$withval"], - [svn_ruby_installdir="$svn_cv_ruby_sitedir"]) - - AC_MSG_CHECKING([where to install Ruby scripts]) - AC_CACHE_VAL([svn_cv_ruby_sitedir_libsuffix],[ - svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ - $SED -e "s,^$rbconfig_sitedir,,"`" - ]) - SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" - AC_MSG_RESULT([$SWIG_RB_SITE_LIB_DIR]) + AC_CACHE_VAL([svn_cv_ruby_sitedir],[ + svn_cv_ruby_sitedir="$rbconfig_sitedir" + ]) + AC_ARG_WITH([ruby-sitedir], + AS_HELP_STRING([--with-ruby-sitedir=SITEDIR], + [install Ruby bindings in SITEDIR + (default is same as ruby's one)]), + [svn_ruby_installdir="$withval"], + [svn_ruby_installdir="$svn_cv_ruby_sitedir"]) - AC_MSG_CHECKING([where to install Ruby extensions]) - AC_CACHE_VAL([svn_cv_ruby_sitedir_archsuffix],[ - svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ - $SED -e "s,^$rbconfig_sitedir,,"`" - ]) - SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" - AC_MSG_RESULT([$SWIG_RB_SITE_ARCH_DIR]) + AC_MSG_CHECKING([where to install Ruby scripts]) + AC_CACHE_VAL([svn_cv_ruby_sitedir_libsuffix],[ + svn_cv_ruby_sitedir_libsuffix="`echo "$rbconfig_sitelibdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + ]) + SWIG_RB_SITE_LIB_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_libsuffix}" + AC_MSG_RESULT([$SWIG_RB_SITE_LIB_DIR]) - AC_MSG_CHECKING([how to use output level for Ruby bindings tests]) - AC_CACHE_VAL([svn_cv_ruby_test_verbose],[ - svn_cv_ruby_test_verbose="normal" - ]) - AC_ARG_WITH([ruby-test-verbose], - AS_HELP_STRING([--with-ruby-test-verbose=LEVEL], - [how to use output level for Ruby bindings tests - (default is normal)]), - [svn_ruby_test_verbose="$withval"], - [svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"]) - SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" - AC_MSG_RESULT([$SWIG_RB_TEST_VERBOSE]) - - dnl SWIG Ruby bindings successfully configured, clear the error message - SWIG_RB_ERRMSG="" - fi + AC_MSG_CHECKING([where to install Ruby extensions]) + AC_CACHE_VAL([svn_cv_ruby_sitedir_archsuffix],[ + svn_cv_ruby_sitedir_archsuffix="`echo "$rbconfig_sitearchdir" | \ + $SED -e "s,^$rbconfig_sitedir,,"`" + ]) + SWIG_RB_SITE_ARCH_DIR="${svn_ruby_installdir}${svn_cv_ruby_sitedir_archsuffix}" + AC_MSG_RESULT([$SWIG_RB_SITE_ARCH_DIR]) + + AC_MSG_CHECKING([how to use output level for Ruby bindings tests]) + AC_CACHE_VAL([svn_cv_ruby_test_verbose],[ + svn_cv_ruby_test_verbose="normal" + ]) + AC_ARG_WITH([ruby-test-verbose], + AS_HELP_STRING([--with-ruby-test-verbose=LEVEL], + [how to use output level for Ruby bindings tests + (default is normal)]), + [svn_ruby_test_verbose="$withval"], + [svn_ruby_test_verbose="$svn_cv_ruby_test_verbose"]) + SWIG_RB_TEST_VERBOSE="$svn_ruby_test_verbose" + AC_MSG_RESULT([$SWIG_RB_TEST_VERBOSE]) + + dnl SWIG Ruby bindings successfully configured, clear the error message + SWIG_RB_ERRMSG="" + fi + ]) AC_SUBST(SWIG) AC_SUBST(SWIG_PY_INCLUDES) AC_SUBST(SWIG_PY_COMPILE) diff --git a/build/generator/gen_base.py b/build/generator/gen_base.py index f3041f902378c..4a3f178c17f72 100644 --- a/build/generator/gen_base.py +++ b/build/generator/gen_base.py @@ -35,6 +35,7 @@ except ImportError: # Python <3.0 import ConfigParser as configparser + configparser.ConfigParser.read_file = configparser.ConfigParser.readfp import generator.swig import getversion @@ -76,7 +77,7 @@ def __init__(self, fname, verfname, options=None): # Now read and parse build.conf parser = configparser.ConfigParser() - parser.readfp(open(fname)) + parser.read_file(open(fname)) self.conf = build_path(os.path.abspath(fname)) diff --git a/build/generator/gen_make.py b/build/generator/gen_make.py index 3a3d4cae2837c..c532b02c8eef0 100644 --- a/build/generator/gen_make.py +++ b/build/generator/gen_make.py @@ -511,6 +511,7 @@ def write_standalone(self): standalone.write('top_srcdir = .\n') standalone.write('top_builddir = .\n') standalone.write('SWIG = swig\n') + standalone.write('SWIG_PY_OPTS = -python -py3 -nofastunpack -modern\n') standalone.write('PYTHON = ' + sys.executable + '\n') standalone.write('\n') standalone.write(open("build-outputs.mk","r").read()) diff --git a/build/generator/gen_win.py b/build/generator/gen_win.py index 7565a9a7e8672..85867a0c4b2ca 100644 --- a/build/generator/gen_win.py +++ b/build/generator/gen_win.py @@ -574,7 +574,7 @@ def adjust_win_depends(self, target, name): # This section parses those dependencies and adds them to the dependency list # for this target. if name.startswith('javahl') or name == 'libsvnjavahl': - for dep in re.findall('\$\(([^\)]*)_DEPS\)', target.add_deps): + for dep in re.findall(r'\$\(([^\)]*)_DEPS\)', target.add_deps): dep = dep.replace('_', '-') depends.extend(self.sections[dep].get_targets()) @@ -728,7 +728,7 @@ def get_win_defines(self, target, cfg): for dep in self.get_win_depends(target, FILTER_EXTERNALLIBS): if dep.external_lib: - for elib in re.findall('\$\(SVN_([^\)]*)_LIBS\)', dep.external_lib): + for elib in re.findall(r'\$\(SVN_([^)]*)_LIBS\)', dep.external_lib): external_lib = elib.lower() if external_lib in self._libraries: @@ -758,7 +758,7 @@ def get_win_includes(self, target, cfg='Release'): for dep in self.get_win_depends(target, FILTER_EXTERNALLIBS): if dep.external_lib: - for elib in re.findall('\$\(SVN_([^\)]*)_LIBS\)', dep.external_lib): + for elib in re.findall(r'\$\(SVN_([^)]*)_LIBS\)', dep.external_lib): external_lib = elib.lower() if external_lib in self._libraries: @@ -833,7 +833,7 @@ def get_win_lib_dirs(self, target, cfg): for dep in self.get_win_depends(target, FILTER_LIBS): if dep.external_lib: - for elib in re.findall('\$\(SVN_([^\)]*)_LIBS\)', dep.external_lib): + for elib in re.findall(r'\$\(SVN_([^)]*)_LIBS\)', dep.external_lib): external_lib = elib.lower() if external_lib not in self._libraries: @@ -887,7 +887,7 @@ def get_win_libs(self, target, cfg): nondeplibs.extend(dep.msvc_libs) if dep.external_lib: - for elib in re.findall('\$\(SVN_([^\)]*)_LIBS\)', dep.external_lib): + for elib in re.findall(r'\$\(SVN_([^)]*)_LIBS\)', dep.external_lib): external_lib = elib.lower() @@ -938,7 +938,7 @@ def get_win_forced_includes(self, target, cfg): for dep in self.get_win_depends(target, FILTER_EXTERNALLIBS): if dep.external_lib: - for elib in re.findall('\$\(SVN_([^\)]*)_LIBS\)', dep.external_lib): + for elib in re.findall(r'\$\(SVN_([^)]*)_LIBS\)', dep.external_lib): external_lib = elib.lower() if external_lib in self._libraries: diff --git a/build/generator/gen_win_dependencies.py b/build/generator/gen_win_dependencies.py index 03048c0fe4b9c..e47a14e63465c 100644 --- a/build/generator/gen_win_dependencies.py +++ b/build/generator/gen_win_dependencies.py @@ -230,22 +230,22 @@ def parse_options(self, options): elif opt == '-D': self.cpp_defines.append(val) elif opt == '--vsnet-version': - if val == '2002' or re.match('^7(\.\d+)?$', val): + if val == '2002' or re.match(r'^7(\.\d+)?$', val): self.vs_version = '2002' self.sln_version = '7.00' self.vcproj_version = '7.00' self.vcproj_extension = '.vcproj' - elif val == '2003' or re.match('^8(\.\d+)?$', val): + elif val == '2003' or re.match(r'^8(\.\d+)?$', val): self.vs_version = '2003' self.sln_version = '8.00' self.vcproj_version = '7.10' self.vcproj_extension = '.vcproj' - elif val == '2005' or re.match('^9(\.\d+)?$', val): + elif val == '2005' or re.match(r'^9(\.\d+)?$', val): self.vs_version = '2005' self.sln_version = '9.00' self.vcproj_version = '8.00' self.vcproj_extension = '.vcproj' - elif val == '2008' or re.match('^10(\.\d+)?$', val): + elif val == '2008' or re.match(r'^10(\.\d+)?$', val): self.vs_version = '2008' self.sln_version = '10.00' self.vcproj_version = '9.00' @@ -280,14 +280,14 @@ def parse_options(self, options): self.sln_version = '12.00' self.vcproj_version = '14.2' self.vcproj_extension = '.vcxproj' - elif re.match('^20\d+$', val): + elif re.match(r'^20\d+$', val): print('WARNING: Unknown VS.NET version "%s",' ' assuming VS2012. Your VS can probably upgrade') self.vs_version = '2012' self.sln_version = '12.00' self.vcproj_version = '11.0' self.vcproj_extension = '.vcxproj' - elif re.match('^1\d+$', val): + elif re.match(r'^1\d+$', val): self.vs_version = val self.sln_version = '12.00' self.vcproj_version = val + '.0' @@ -345,7 +345,7 @@ def find_libraries(self, show_warnings): def _find_apr(self): "Find the APR library and version" - minimal_apr_version = (1, 5, 0) + minimal_apr_version = (1, 4, 0) if not self.apr_path: sys.stderr.write("ERROR: Use '--with-apr' option to configure APR " + \ @@ -366,13 +366,16 @@ def _find_apr(self): version_file_path = os.path.join(inc_path, 'apr_version.h') txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+APR_MAJOR_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+APR_MAJOR_VERSION\s+(\d+)', + txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+APR_MINOR_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+APR_MINOR_VERSION\s+(\d+)', + txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+APR_PATCH_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+APR_PATCH_VERSION\s+(\d+)', + txt, re.M) patch = int(vermatch.group(1)) version = (major, minor, patch) @@ -469,13 +472,16 @@ def _find_apr_util_etc(self): version_file_path = os.path.join(inc_path, 'apu_version.h') txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+APU_MAJOR_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+APU_MAJOR_VERSION\s+(\d+)', + txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+APU_MINOR_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+APU_MINOR_VERSION\s+(\d+)', + txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+APU_PATCH_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+APU_PATCH_VERSION\s+(\d+)', + txt, re.M) patch = int(vermatch.group(1)) version = (major, minor, patch) @@ -580,13 +586,16 @@ def _find_apr_util_etc(self): txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+XML_MAJOR_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+XML_MAJOR_VERSION\s+(\d+)', + txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+XML_MINOR_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+XML_MINOR_VERSION\s+(\d+)', + txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+XML_MICRO_VERSION\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+XML_MICRO_VERSION\s+(\d+)', + txt, re.M) patch = int(vermatch.group(1)) # apr-Util 0.9-1.4 compiled expat to 'xml.lib', but apr-util 1.5 switched @@ -635,16 +644,16 @@ def _find_httpd(self, show_warnings): version_file_path = os.path.join(inc_path, 'ap_release.h') txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+AP_SERVER_MAJORVERSION_NUMBER\s+(\d+)', - txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+AP_SERVER_MAJORVERSION_NUMBER' + r'\s+(\d+)', txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+AP_SERVER_MINORVERSION_NUMBER\s+(\d+)', - txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+AP_SERVER_MINORVERSION_NUMBER' + r'\s+(\d+)', txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+AP_SERVER_PATCHLEVEL_NUMBER\s+(\d+)', - txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+AP_SERVER_PATCHLEVEL_NUMBER' + r'\s+(\d+)', txt, re.M) patch = int(vermatch.group(1)) version = (major, minor, patch) @@ -754,11 +763,11 @@ def _find_zlib(self): txt = open(version_file_path).read() vermatch = re.search( - r'^\s*#define\s+ZLIB_VERSION\s+"(\d+)\.(\d+)\.(\d+)(?:\.\d)?"', - txt, re.M) + r'^\s*#\s*define\s+ZLIB_VERSION\s+"(\d+(?:\.\d+){1,3})(?:-\w+)?"', + txt, re.M) - version = tuple(map(int, vermatch.groups())) - self.zlib_version = '%d.%d.%d' % version + version = tuple(map(int, vermatch.group(1).split('.'))) + self.zlib_version = '.'.join(map(str, version)) if version < minimal_zlib_version: sys.stderr.write("ERROR: ZLib %s or higher is required " @@ -920,6 +929,11 @@ def _find_openssl(self, show_warnings): def _find_perl(self, show_warnings): "Find the right perl library name to link swig bindings with" + try: + subprocess.run(['perl', '-v'], capture_output=True) + except OSError: + return # not found, permission error, ... + fp = os.popen('perl -MConfig -e ' + escape_shell_arg( 'print "$Config{libperl}\\n"; ' 'print "$Config{PERL_REVISION}.$Config{PERL_VERSION}.' @@ -959,6 +973,11 @@ def _find_perl(self, show_warnings): def _find_ruby(self, show_warnings): "Find the right Ruby library name to link swig bindings with" + try: + subprocess.run(['ruby', '--version'], capture_output=True) + except OSError: + return # not found, permission error, ... + lib_dir = None inc_dirs = [] @@ -1004,7 +1023,7 @@ def _find_ruby(self, show_warnings): else: defines = [] - ver = ruby_version.split('.') + ver = ruby_version.split('+')[0].split('.') ver = tuple(map(int, ver)) if ver >= (1, 8, 0): defines.extend(["HAVE_RB_ERRINFO"]) @@ -1030,18 +1049,36 @@ def _find_python(self, show_warnings): "Find the appropriate options for creating SWIG-based Python modules" try: - from distutils import sysconfig - - inc_dir = sysconfig.get_python_inc() - lib_dir = os.path.join(sysconfig.PREFIX, "libs") + import sysconfig except ImportError: return + config_vars = sysconfig.get_config_vars() + inc_dir = config_vars['INCLUDEPY'] + base_dir = config_vars.get('installed_base') or config_vars.get('base') + lib_dir = os.path.join(base_dir, 'libs') if sys.version_info[0] >= 3: - self.user_macros.append(UserMacro("SWIG_PY_OPTS", "-python -py3")) + if self.swig_version < (3, 0, 10): + if show_warnings: + print("WARNING: Subversion Python bindings for Python 3 require SWIG 3.0.10 or newer") + return + if self.swig_version < (4, 0, 0): + opts = "-python -py3 -nofastunpack -modern" + elif self.swig_version < (4, 1, 0): + opts = "-python -py3 -nofastunpack" + else: + opts = "-python -nofastunpack" + if show_warnings and self.swig_version > (4, 0, 2): + print("WARNING: Subversion Python bindings may work,\n" + "but we didn't check with this SWIG version.") else: - self.user_macros.append(UserMacro("SWIG_PY_OPTS", "-python -classic")) + if not ((1, 3, 24) <= self.swig_version < (4, 0, 0)): + if show_warnings: + print("WARNING: Subversion Python bindings for Python 2 require 1.3.24 <= SWIG < 4.0.0") + return + opts = "-python -classic" + self.user_macros.append(UserMacro("SWIG_PY_OPTS", opts)) self._libraries['python'] = SVNCommonLibrary('python', inc_dir, lib_dir, None, sys.version.split(' ')[0]) @@ -1350,13 +1387,16 @@ def _find_sasl(self, show_warnings): txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+SASL_VERSION_MAJOR\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+SASL_VERSION_MAJOR\s+(\d+)', + txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+SASL_VERSION_MINOR\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+SASL_VERSION_MINOR\s+(\d+)', + txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+SASL_VERSION_STEP\s+(\d+)', txt, re.M) + vermatch = re.search(r'^\s*#\s*define\s+SASL_VERSION_STEP\s+(\d+)', + txt, re.M) patch = int(vermatch.group(1)) version = (major, minor, patch) @@ -1424,8 +1464,8 @@ def _find_libintl(self, show_warnings): version_file_path = os.path.join(inc_dir, 'libintl.h') txt = open(version_file_path).read() - match = re.search(r'^\s*#define\s+LIBINTL_VERSION\s+((0x)?[0-9A-Fa-f]+)', - txt, re.M) + match = re.search(r'^\s*#\s*define\s+LIBINTL_VERSION\s+' + r'((0x)?[0-9A-Fa-f]+)', txt, re.M) ver = int(match.group(1), 0) version = (ver >> 16, (ver >> 8) & 0xFF, ver & 0xFF) @@ -1497,7 +1537,7 @@ def _find_sqlite(self, show_warnings): txt = open(version_file_path).read() - match = re.search(r'^\s*#define\s+SQLITE_VERSION\s+' + match = re.search(r'^\s*#\s*define\s+SQLITE_VERSION\s+' r'"(\d+)\.(\d+)\.(\d+)(?:\.(\d))?"', txt, re.M) version = match.groups() @@ -1531,15 +1571,15 @@ def _find_lz4(self): 'lz4', 'lz4internal.h') txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_MAJOR\s+(\d+)', + vermatch = re.search(r'^\s*#\s*define\s+LZ4_VERSION_MAJOR\s+(\d+)', txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_MINOR\s+(\d+)', + vermatch = re.search(r'^\s*#\s*define\s+LZ4_VERSION_MINOR\s+(\d+)', txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+LZ4_VERSION_RELEASE\s+(\d+)', + vermatch = re.search(r'^\s*#\s*define\s+LZ4_VERSION_RELEASE\s+(\d+)', txt, re.M) rel = vermatch.group(1) @@ -1556,15 +1596,15 @@ def _find_utf8proc(self): 'utf8proc', 'utf8proc_internal.h') txt = open(version_file_path).read() - vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_MAJOR\s+(\d+)', + vermatch = re.search(r'^\s*#\s*define\s+UTF8PROC_VERSION_MAJOR\s+(\d+)', txt, re.M) major = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_MINOR\s+(\d+)', + vermatch = re.search(r'^\s*#\s*define\s+UTF8PROC_VERSION_MINOR\s+(\d+)', txt, re.M) minor = int(vermatch.group(1)) - vermatch = re.search(r'^\s*#define\s+UTF8PROC_VERSION_PATCH\s+(\d+)', + vermatch = re.search(r'^\s*#\s*define\s+UTF8PROC_VERSION_PATCH\s+(\d+)', txt, re.M) patch = int(vermatch.group(1)) diff --git a/build/generator/swig/__init__.py b/build/generator/swig/__init__.py index 000524ea8196b..361a400975eac 100644 --- a/build/generator/swig/__init__.py +++ b/build/generator/swig/__init__.py @@ -72,7 +72,7 @@ def version(self): if not self._swigVersion: swig_version = subprocess.check_output([self.swig_path, "-version"]).decode('utf8') - m = re.search("Version (\d+).(\d+).(\d+)", swig_version) + m = re.search(r"Version (\d+).(\d+).(\d+)", swig_version) if m: self._swigVersion = tuple(map(int, m.groups())) else: diff --git a/build/get-py-info.py b/build/get-py-info.py index 95bad41a4eb59..e31635c69385e 100644 --- a/build/get-py-info.py +++ b/build/get-py-info.py @@ -44,15 +44,24 @@ def usage(): usage() try: - from distutils import sysconfig + if sys.version_info[0] == 2: + from distutils import sysconfig + get_include = lambda: sysconfig.get_python_inc() + get_platinclude = lambda: sysconfig.get_python_inc(plat_specific=1) + get_purelib = lambda: sysconfig.get_python_lib() + else: + import sysconfig + get_include = lambda: sysconfig.get_path('include') + get_platinclude = lambda: sysconfig.get_path('platinclude') + get_purelib = lambda: sysconfig.get_path('purelib') except ImportError: # No information available print("none") sys.exit(1) if sys.argv[1] == '--includes': - inc = sysconfig.get_python_inc() - plat = sysconfig.get_python_inc(plat_specific=1) + inc = get_include() + plat = get_platinclude() if inc == plat: print("-I" + inc) else: @@ -140,7 +149,7 @@ def lib_options(): sys.exit(0) if sys.argv[1] == '--site': - print(sysconfig.get_python_lib()) + print(get_purelib()) sys.exit(0) usage() diff --git a/build/run_tests.py b/build/run_tests.py index 5a661af9f452d..25dbdecb8d916 100755 --- a/build/run_tests.py +++ b/build/run_tests.py @@ -47,7 +47,7 @@ separated list of test numbers; the default is to run all the tests in it. ''' -import os, sys, shutil, codecs +import os, sys, shutil import re import logging import optparse, subprocess, threading, traceback @@ -141,6 +141,18 @@ def ensure_str(s): else: return s.decode("latin-1") +def open_logfile(filename, mode, encoding='utf-8'): + if sys.version_info[0] != 2: + return open(filename, mode, encoding=encoding, errors='surrogateescape') + else: + class Wrapper(object): + def __init__(self, stream, encoding): + self._stream = stream + self.encoding = encoding + def __getattr__(self, name): + return getattr(self._stream, name) + return Wrapper(open(filename, mode), encoding) + class TestHarness: '''Test harness for Subversion tests. ''' @@ -700,7 +712,7 @@ def printxfail(x): # Copy the truly interesting verbose logs to a separate file, for easier # viewing. if xpassed or failed_list: - faillog = codecs.open(self.faillogfile, 'w', encoding="latin-1") + faillog = open_logfile(self.faillogfile, 'w') last_start_lineno = None last_start_re = re.compile('^(FAIL|SKIP|XFAIL|PASS|START|CLEANUP|END):') for lineno, line in enumerate(log_lines): @@ -733,7 +745,7 @@ def _open_log(self, mode): 'Open the log file with the required MODE.' if self.logfile: self._close_log() - self.log = codecs.open(self.logfile, mode, encoding="latin-1") + self.log = open_logfile(self.logfile, mode) def _close_log(self): 'Close the log file.' @@ -764,10 +776,12 @@ def _check_for_unknown_failure(self, log, progbase, test_failed): # ### Even if failure==1 it could be that the test didn't run at all. if test_failed and test_failed != 1: if self.log: - log.write('FAIL: %s: Unknown test failure; see tests.log.\n' % progbase) + log.write('FAIL: %s: Unknown test failure (%s); see tests.log.\n' + % (progbase, test_failed)) log.flush() else: - log.write('FAIL: %s: Unknown test failure.\n' % progbase) + log.write('FAIL: %s: Unknown test failure (%s).\n' + % (progbase, test_failed)) def _run_c_test(self, progabs, progdir, progbase, test_nums, dot_count): 'Run a c test, escaping parameters as required.' @@ -843,14 +857,13 @@ def _run_py_test(self, progabs, progdir, progbase, test_nums, dot_count): sys.exit(1) # setup the output pipes + old_stdout = sys.stdout.fileno() if self.log: sys.stdout.flush() sys.stderr.flush() self.log.flush() - 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()) + saved_stds = sys.stdout, sys.stderr + sys.stdout = sys.stderr = self.log # These have to be class-scoped for use in the progress_func() self.dots_written = 0 @@ -891,12 +904,8 @@ def progress_func(completed, total): # restore some values if self.log: - sys.stdout.flush() - sys.stderr.flush() - os.dup2(old_stdout, sys.stdout.fileno()) - os.dup2(old_stderr, sys.stderr.fileno()) - os.close(old_stdout) - os.close(old_stderr) + self.log.flush() + sys.stdout, sys.stderr = saved_stds return failed diff --git a/build/transform_sql.py b/build/transform_sql.py index 18091f21ea135..73e09400dab85 100755 --- a/build/transform_sql.py +++ b/build/transform_sql.py @@ -50,7 +50,7 @@ class Processor(object): # a few SQL comments that act as directives for this transform system re_format = re.compile('-- *format: *([0-9]+)') - re_statement = re.compile('-- *STMT_([A-Z_0-9]+)( +\(([^\)]*)\))?') + re_statement = re.compile(r'-- *STMT_([A-Z_0-9]+)( +\(([^)]*)\))?') re_include = re.compile('-- *include: *([-a-z]+)') re_define = re.compile('-- *define: *([A-Z_0-9]+)') diff --git a/configure.ac b/configure.ac index 07103e550a813..31f536820bbde 100644 --- a/configure.ac +++ b/configure.ac @@ -91,7 +91,7 @@ AC_SUBST([MKDIR]) dnl verify apr version and set apr flags dnl These regular expressions should not contain "\(" and "\)". -APR_VER_REGEXES=["1\.[5-9]\. 2\."] +APR_VER_REGEXES=["1\.[4-9]\. 2\."] SVN_LIB_APR($APR_VER_REGEXES) @@ -1272,35 +1272,142 @@ AS_HELP_STRING([--enable-gprof], # Scripting and Bindings languages -# Python: Used for testsuite, and bindings +# Python: Used for testsuite AC_ARG_VAR([PYTHON], [Python interpreter command]) PYTHON="`$abs_srcdir/build/find_python.sh`" if test -z "$PYTHON"; then - AC_MSG_WARN([Python 2.7 or later is required to run the testsuite]) - AC_MSG_WARN([or to use the Subversion Python bindings]) + AC_MSG_WARN([Python 2.7 or later is required to run the testsuite.]) AC_MSG_WARN([]) AC_MSG_WARN([If you have a suitable Python installed, but not on the]) AC_MSG_WARN([PATH, set the environment variable PYTHON to the full path]) AC_MSG_WARN([to the Python executable, and re-run configure]) + PYTHON=none fi -AC_PATH_PROGS(PYTHON, "$PYTHON", none) +AC_SUBST(PYTHON) # The minimum version for the JVM runtime for our Java bytecode. JAVA_OLDEST_WORKING_VER='1.8' # SVN_CHECK_JDK sets $JAVA_CLASSPATH SVN_CHECK_JDK($JAVA_OLDEST_WORKING_VER) -AC_PATH_PROG(PERL, perl, none) - +AC_ARG_WITH(swig_perl, +[AS_HELP_STRING([[--with-swig-perl[=PATH|auto|no]|--without-swig-perl]], + [Specify path to SWIG bindings target Perl interpreter + [default=auto]. If the option value is 'auto' or it is not + specfied, search for the Perl program.] + m4_ifndef([SVN_RELEASE_MODE], + [[Implies --with-swig=yes if PATH is specfied or Perl + is found on 'auto' detection.]])) +], +[], +[ +if test -n "$PERL"; then + if test "$PERL" = "no" -o "$PERL" = "none"; then + with_swig_perl=no + AC_MSG_WARN([Disabling the SWIG Perl bindings' build by setting the PERL]) + AC_MSG_WARN([environment variable to "none" is deprecated.]) + AC_MSG_WARN([]) + AC_MSG_WARN([Please use --without-swig-perl instead.]) + else + with_swig_perl=auto + AC_MSG_WARN([Specfying the Perl path for SWIG Perl bindings' build]) + AC_MSG_WARN([by setting the PERL environment variable is deprecated.]) + AC_MSG_WARN([]) + AC_MSG_WARN([Please use --with-swig-perl=PATH instead.]) + fi +else + with_swig_perl=auto +fi +]) +case $with_swig_perl in + yes|auto|"") + # honor PERL variable only if it is set and is full path. + AC_PATH_PROG(PERL, perl, none) + SWIG_PL_PERL="$PERL" + ;; + no|none) + SWIG_PL_PERL=none + ;; + *) + SWIG_PL_PERL="$with_swig_perl" + ;; +esac +AC_SUBST(SWIG_PL_PERL) + +# Python: as a target of SWIG Python bindings +AC_ARG_WITH(swig_python, +[AS_HELP_STRING([[--with-swig-python[=PATH|auto|no]|--without-swig-python]], + [Specify path to SWIG bindings target Python interpreter + [default=auto]. If the option value is 'auto' or it is not + specfied, search for the Python program.] + m4_ifndef([SVN_RELEASE_MODE], + [[Implies --with-swig=yes if PATH is specfied or Python + is found on 'auto' detection.]])) +], +[], +[ + with_swig_python=auto +]) +case $with_swig_python in + yes|auto|"") + SWIG_PY_PYTHON="$PYTHON" + ;; + no|none) + SWIG_PY_PYTHON=none + ;; + *) + SWIG_PY_PYTHON="$with_swig_python" + ;; +esac +AC_SUBST(SWIG_PY_PYTHON) + +AC_ARG_WITH(swig_ruby, +[AS_HELP_STRING([[--with-swig-ruby[=PATH|auto|no]|--without-swig-ruby]], + [specify path to SWIG bindings target Ruby interpreter + [default=auto]. If the option value is 'auto' or it is not + specfied, search for the Ruby program.] + m4_ifndef([SVN_RELEASE_MODE], + [[Implies --with-swig=yes if PATH is specfied or Ruby + is found on 'auto' detection.]])) +], +[], +[ if test -n "$RUBY"; then - AC_PATH_PROG(RUBY, "$RUBY", none) + if test "$RUBY" = "no" -o "$RUBY" = "none"; then + with_swig_ruby=no + AC_MSG_WARN([Disabling the SWIG Ruby bindings' build by setting the RUBY]) + AC_MSG_WARN([environment variable to "none" is deprecated.]) + AC_MSG_WARN([]) + AC_MSG_WARN([Please use --without-swig-ruby instead.]) + else + with_swig_ruby="$RUBY" + AC_MSG_WARN([Specfying the Ruby path for SWIG Ruby bindings' build]) + AC_MSG_WARN([by setting the RUBY environment variable is deprecated.]) + AC_MSG_WARN([]) + AC_MSG_WARN([Please use --with-swig-ruby=PATH instead.]) + fi else - AC_PATH_PROGS(RUBY, ruby ruby1 ruby1.8 ruby18 ruby1.9 ruby19 ruby1.9.3 ruby193 ruby2 ruby2.0 ruby20 ruby2.1 ruby21 ruby2.2 ruby22 ruby2.3 ruby23 ruby2.4 ruby24, none) + with_swig_ruby=auto fi -if test "$RUBY" != "none"; then +]) +case $with_swig_ruby in + yes|auto|"") + # honor RUBY variable only if it is set and is full path. + AC_PATH_PROGS(RUBY, ruby ruby1 ruby1.8 ruby18 ruby1.9 ruby19 ruby1.9.3 ruby193 ruby2 ruby2.0 ruby20 ruby2.1 ruby21 ruby2.2 ruby22 ruby2.3 ruby23 ruby2.4 ruby24, none) + SWIG_RB_RUBY="$RUBY" + ;; + no|none) + SWIG_RB_RUBY=none + ;; + *) + SWIG_RB_RUBY="$with_swig_ruby" + ;; +esac +AC_SUBST(SWIG_RB_RUBY) +if test "$SWIG_RB_RUBY" != "none"; then AC_MSG_CHECKING([rb_hash_foreach]) - if "$RUBY" -r mkmf -e 'exit(have_func("rb_hash_foreach") ? 0 : 1)' >/dev/null; then + if "$SWIG_RB_RUBY" -r mkmf -e 'exit(have_func("rb_hash_foreach") ? 0 : 1)' >/dev/null; then AC_MSG_RESULT([yes]) if test -n "$RDOC"; then AC_PATH_PROG(RDOC, "$RDOC", none) @@ -1308,17 +1415,17 @@ if test "$RUBY" != "none"; then AC_PATH_PROGS(RDOC, rdoc rdoc1 rdoc1.8 rdoc18 rdoc1.9 rdoc19 rdoc1.9.3 rdoc193 rdoc2 rdoc2.0 rdoc20 rdoc2.1 rdoc21 rdoc2.2 rdoc22 rdoc2.3 rdoc23 rdoc2.4 rdoc24, none) fi AC_CACHE_CHECK([for Ruby major version], [svn_cv_ruby_major],[ - svn_cv_ruby_major="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MAJOR))'`" + svn_cv_ruby_major="`$SWIG_RB_RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MAJOR))'`" ]) RUBY_MAJOR="$svn_cv_ruby_major" AC_CACHE_CHECK([for Ruby minor version], [svn_cv_ruby_minor],[ - svn_cv_ruby_minor="`$RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MINOR))'`" + svn_cv_ruby_minor="`$SWIG_RB_RUBY -rrbconfig -e 'print RbConfig::CONFIG.fetch(%q(MINOR))'`" ]) RUBY_MINOR="$svn_cv_ruby_minor" AC_CACHE_CHECK([for Ruby teeny version], [svn_cv_ruby_teeny],[ - svn_cv_ruby_teeny="`$RUBY -rrbconfig -e 'major, minor, teeny = RUBY_VERSION.split("."); print teeny;'`" + svn_cv_ruby_teeny="`$SWIG_RB_RUBY -rrbconfig -e 'major, minor, teeny = RUBY_VERSION.split("."); print teeny;'`" ]) RUBY_TEENY="$svn_cv_ruby_teeny" @@ -1327,13 +1434,13 @@ if test "$RUBY" != "none"; then AC_SUBST(RUBY_TEENY) if test \( "$RUBY_MAJOR" -eq "1" -a "$RUBY_MINOR" -gt "8" -a "$RUBY_TEENY" -lt "3" \); then # Disallow Ruby between 1.8.7 and 1.9.3 - RUBY="none" + SWIG_RB_RUBY="none" AC_MSG_WARN([The detected Ruby is between 1.9 and 1.9.3]) AC_MSG_WARN([Only 1.8.x and 1.9.3 or later are supported at this time]) fi else AC_MSG_RESULT([no]) - RUBY="none" + SWIG_RB_RUBY="none" AC_MSG_WARN([The detected Ruby is too old for Subversion to use]) AC_MSG_WARN([A Ruby which has rb_hash_foreach is required to use the]) AC_MSG_WARN([Subversion Ruby bindings]) diff --git a/gen-make.py b/gen-make.py index bdb359394e1ca..5811705322ccd 100755 --- a/gen-make.py +++ b/gen-make.py @@ -70,7 +70,7 @@ def main(fname, gentype, verfname=None, if ('--debug', '') in other_options: for dep_type, target_dict in generator.graph.deps.items(): - sorted_targets = list(target_dict.keys()); sorted_targets.sort() + sorted_targets = sorted(target_dict.keys(), key=str) for target in sorted_targets: print(dep_type + ": " + _objinfo(target)) for source in target_dict[target]: diff --git a/notes/knobs b/notes/knobs index 66cfdb2a3ab13..b4bccdaccb113 100644 --- a/notes/knobs +++ b/notes/knobs @@ -38,7 +38,7 @@ SUFFIX_LINES_TO_KEEP SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR SVN_FS_FS_MAX_LINEAR_DELTIFICATION SVN_FS_FS_MAX_DELTIFICATION_WALK -SVN_UNALIGNED_ACCESS_IS_OK +SVN_UNALIGNED_ACCESS_IS_OK (deprecated) 2.2 Features @@ -164,12 +164,14 @@ SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2 3.8 SVN_UNALIGNED_ACCESS_IS_OK + DEPRECATED, unused. The rest of this entry is historical. + Scope: (global) Purpose: enable data accesss optimizations. If your target CPU supports unaligned memory access without significant performance penalties, you should enable this optimization as it allows for processing 4 or 8 bytes at - once instead of just one byte at a time. + once instead of just one byte at a time. Range: 0 1 Default: platform dependant (see svn_types.h) Suggested: 0 diff --git a/subversion/bindings/javahl/native/JNIUtil.cpp b/subversion/bindings/javahl/native/JNIUtil.cpp index d62b56af429bc..60be39b884ce6 100644 --- a/subversion/bindings/javahl/native/JNIUtil.cpp +++ b/subversion/bindings/javahl/native/JNIUtil.cpp @@ -551,6 +551,11 @@ std::string JNIUtil::makeSVNErrorMessage(svn_error_t *err, jstring *jerror_message, jobject *jmessage_stack) { + // This function may be called with a pending Java exception. + // It is incorrect to call Java methods (see code below) with a pending + // exception. Stash it away until this function exits. + StashException stash(getEnv()); + if (jerror_message) *jerror_message = NULL; if (jmessage_stack) @@ -761,16 +766,27 @@ namespace { const char* known_exception_to_cstring(apr_pool_t* pool) { JNIEnv *env = JNIUtil::getEnv(); + + // This function may be called with a pending Java exception. + // It is incorrect to call Java methods (see code below) with a pending + // exception. Stash it away until this function exits. jthrowable t = env->ExceptionOccurred(); + StashException stashed(env); + jclass cls = env->GetObjectClass(t); jstring jclass_name; { jmethodID mid = env->GetMethodID(cls, "getClass", "()Ljava/lang/Class;"); jobject clsobj = env->CallObjectMethod(t, mid); + if (JNIUtil::isJavaExceptionThrown()) + return NULL; + jclass basecls = env->GetObjectClass(clsobj); mid = env->GetMethodID(basecls, "getName", "()Ljava/lang/String;"); jclass_name = (jstring) env->CallObjectMethod(clsobj, mid); + if (JNIUtil::isJavaExceptionThrown()) + return NULL; } jstring jmessage; @@ -778,6 +794,8 @@ const char* known_exception_to_cstring(apr_pool_t* pool) jmethodID mid = env->GetMethodID(cls, "getMessage", "()Ljava/lang/String;"); jmessage = (jstring) env->CallObjectMethod(t, mid); + if (JNIUtil::isJavaExceptionThrown()) + return NULL; } JNIStringHolder class_name(jclass_name); @@ -1169,3 +1187,28 @@ jthrowable JNIUtil::unwrapJavaException(const svn_error_t *err) return WrappedException::get_exception(err->pool); } + +StashException::StashException(JNIEnv* env) +{ + m_env = env; + m_stashed = NULL; + stashException(); +} + +StashException::~StashException() +{ + if (m_stashed) + m_env->Throw(m_stashed); +} + +void StashException::stashException() +{ + jthrowable jexc = m_env->ExceptionOccurred(); + if (!jexc) + return; + + if (!m_stashed) + m_stashed = jexc; + + m_env->ExceptionClear(); +} diff --git a/subversion/bindings/javahl/native/JNIUtil.h b/subversion/bindings/javahl/native/JNIUtil.h index e05d1ef6abfee..deaf6f619b1db 100644 --- a/subversion/bindings/javahl/native/JNIUtil.h +++ b/subversion/bindings/javahl/native/JNIUtil.h @@ -330,4 +330,37 @@ class JNIUtil } \ } while(0) +/** + * If there's an exception pending, temporarily stash it away, then + * re-throw again in destructor. The goal is to allow some Java calls + * to be made despite a pending exception. For example, doing some + * necessary cleanup. + */ +class StashException +{ + public: + /* + * Works like stashException(). + */ + StashException(JNIEnv* env); + + /** + * If there's an exception stashed, re-throws it. + */ + ~StashException(); + + /** + * Check for a pending exception. + * If present, stash it away until this class's destructor. + * If another exception is already stashed, forget the _new_ one. The + * reason behind it is that usually the first exception is the most + * informative. + */ + void stashException(); + + private: + JNIEnv* m_env; + jthrowable m_stashed; +}; + #endif // JNIUTIL_H diff --git a/subversion/bindings/javahl/native/OperationContext.cpp b/subversion/bindings/javahl/native/OperationContext.cpp index d9c0543cec834..16817225b2a66 100644 --- a/subversion/bindings/javahl/native/OperationContext.cpp +++ b/subversion/bindings/javahl/native/OperationContext.cpp @@ -492,6 +492,8 @@ class TunnelContext request_out(NULL), response_in(NULL), response_out(NULL), + jrequest(NULL), + jresponse(NULL), jclosecb(NULL) { status = apr_file_pipe_create_ex(&request_in, &request_out, @@ -512,6 +514,8 @@ class TunnelContext apr_file_t *response_in; apr_file_t *response_out; apr_status_t status; + jobject jrequest; + jobject jresponse; jobject jclosecb; }; @@ -523,7 +527,10 @@ jobject create_Channel(const char *class_name, JNIEnv *env, apr_file_t *fd) jmethodID ctor = env->GetMethodID(cls, "", "(J)V"); if (JNIUtil::isJavaExceptionThrown()) return NULL; - return env->NewObject(cls, ctor, reinterpret_cast(fd)); + jobject channel = env->NewObject(cls, ctor, reinterpret_cast(fd)); + if (JNIUtil::isJavaExceptionThrown()) + return NULL; + return env->NewGlobalRef(channel); } jobject create_RequestChannel(JNIEnv *env, apr_file_t *fd) @@ -534,6 +541,24 @@ jobject create_ResponseChannel(JNIEnv *env, apr_file_t *fd) { return create_Channel(JAVAHL_CLASS("/util/ResponseChannel"), env, fd); } +void close_TunnelChannel(JNIEnv* env, jobject channel) +{ + // Usually after this function, the memory will be freed behind + // 'TunnelChannel.nativeChannel'. Ask Java side to forget it. This is the + // only way to avoid a JVM crash when 'TunnelAgent' tries to read/write, + // not knowing that 'TunnelChannel' is already closed in native side. + static jmethodID mid = 0; + if (0 == mid) + { + jclass cls; + SVN_JNI_CATCH_VOID( + cls = env->FindClass(JAVAHL_CLASS("/util/TunnelChannel"))); + SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "syncClose", "()V")); + } + + SVN_JNI_CATCH_VOID(env->CallVoidMethod(channel, mid)); + env->DeleteGlobalRef(channel); +} } // anonymous namespace svn_boolean_t @@ -590,10 +615,10 @@ OperationContext::openTunnel(svn_stream_t **request, svn_stream_t **response, JNIEnv *env = JNIUtil::getEnv(); - jobject jrequest = create_RequestChannel(env, tc->request_in); + tc->jrequest = create_RequestChannel(env, tc->request_in); SVN_JNI_CATCH(, SVN_ERR_BASE); - jobject jresponse = create_ResponseChannel(env, tc->response_out); + tc->jresponse = create_ResponseChannel(env, tc->response_out); SVN_JNI_CATCH(, SVN_ERR_BASE); jstring jtunnel_name = JNIUtil::makeJString(tunnel_name); @@ -623,29 +648,32 @@ OperationContext::openTunnel(svn_stream_t **request, svn_stream_t **response, } jobject jtunnelcb = jobject(tunnel_baton); - SVN_JNI_CATCH( - tc->jclosecb = env->CallObjectMethod( - jtunnelcb, mid, jrequest, jresponse, - jtunnel_name, juser, jhostname, jint(port)), - SVN_ERR_BASE); + tc->jclosecb = env->CallObjectMethod( + jtunnelcb, mid, tc->jrequest, tc->jresponse, + jtunnel_name, juser, jhostname, jint(port)); + svn_error_t* openTunnelError = JNIUtil::checkJavaException(SVN_ERR_BASE); + if (SVN_NO_ERROR != openTunnelError) + { + // OperationContext::closeTunnel() will never be called, clean up here. + // This also prevents a JVM native crash, see comment in + // close_TunnelChannel(). + *close_baton = 0; + tc->jclosecb = 0; + OperationContext::closeTunnel(tc, 0); + SVN_ERR(openTunnelError); + } + + if (tc->jclosecb) + { + tc->jclosecb = env->NewGlobalRef(tc->jclosecb); + SVN_JNI_CATCH(, SVN_ERR_BASE); + } return SVN_NO_ERROR; } -void -OperationContext::closeTunnel(void *tunnel_context, void *) +void callCloseTunnelCallback(JNIEnv* env, jobject jclosecb) { - TunnelContext* tc = static_cast(tunnel_context); - jobject jclosecb = tc->jclosecb; - delete tc; - - if (!jclosecb) - return; - - JNIEnv *env = JNIUtil::getEnv(); - if (JNIUtil::isJavaExceptionThrown()) - return; - static jmethodID mid = 0; if (0 == mid) { @@ -656,4 +684,41 @@ OperationContext::closeTunnel(void *tunnel_context, void *) SVN_JNI_CATCH_VOID(mid = env->GetMethodID(cls, "closeTunnel", "()V")); } SVN_JNI_CATCH_VOID(env->CallVoidMethod(jclosecb, mid)); + env->DeleteGlobalRef(jclosecb); +} + +void +OperationContext::closeTunnel(void *tunnel_context, void *) +{ + TunnelContext* tc = static_cast(tunnel_context); + jobject jrequest = tc->jrequest; + jobject jresponse = tc->jresponse; + jobject jclosecb = tc->jclosecb; + + // Note that this closes other end of the pipe, which cancels and + // prevents further read/writes in 'TunnelAgent' + delete tc; + + JNIEnv *env = JNIUtil::getEnv(); + + // Cleanup is important, otherwise TunnelAgent may crash when + // accessing freed native objects. For this reason, cleanup is done + // despite a pending exception. If more exceptions occur, they are + // stashed as well in order to complete all cleanup steps. + StashException ex(env); + + if (jclosecb) + callCloseTunnelCallback(env, jclosecb); + + if (jrequest) + { + ex.stashException(); + close_TunnelChannel(env, jrequest); + } + + if (jresponse) + { + ex.stashException(); + close_TunnelChannel(env, jresponse); + } } diff --git a/subversion/bindings/javahl/native/SVNBase.cpp b/subversion/bindings/javahl/native/SVNBase.cpp index 5e20f93def4e5..1943ffcad9cbd 100644 --- a/subversion/bindings/javahl/native/SVNBase.cpp +++ b/subversion/bindings/javahl/native/SVNBase.cpp @@ -107,13 +107,9 @@ jobject SVNBase::createCppBoundObject(const char *clazzName) if (JNIUtil::isJavaExceptionThrown()) return NULL; - static jmethodID ctor = 0; - if (ctor == 0) - { - ctor = env->GetMethodID(clazz, "", "(J)V"); - if (JNIUtil::isJavaExceptionThrown()) - return NULL; - } + jmethodID ctor = env->GetMethodID(clazz, "", "(J)V"); + if (JNIUtil::isJavaExceptionThrown()) + return NULL; jlong cppAddr = this->getCppAddr(); diff --git a/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp b/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp index db05e28e2abdc..3d6b5886ec95f 100644 --- a/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp +++ b/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp @@ -144,7 +144,8 @@ class BaseImmutableMap : public Object { const jstring jkey = jstring(m_env.CallObjectMethod(m_jthis, impl().m_mid_get_key)); - const String::Contents key(String(m_env, jkey)); + const String str(m_env, jkey); + const String::Contents key(str); return std::string(key.c_str()); } diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java index 17444381a81a0..dae70c6e7e878 100644 --- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java +++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/NativeException.java @@ -85,7 +85,11 @@ public int getAprError() */ public String getMessage() { - StringBuffer msg = new StringBuffer(super.getMessage()); + StringBuilder msg = new StringBuilder(); + String message = super.getMessage(); + if (message != null) { + msg.append(message); + } // ### This might be better off in JNIUtil::handleSVNError(). String src = getSource(); if (src != null) diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java index 3040536f903e9..eb012f45edba6 100644 --- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java +++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/RequestChannel.java @@ -42,15 +42,18 @@ private RequestChannel(long nativeChannel) public int read(ByteBuffer dst) throws IOException { - long channel = nativeChannel.get(); - if (channel != 0) - try { - return nativeRead(channel, dst); - } catch (IOException ex) { - nativeChannel.set(0); // Close the channel - throw ex; - } - throw new ClosedChannelException(); + synchronized (nativeChannel) + { + long channel = nativeChannel.get(); + if (channel != 0) + try { + return nativeRead(channel, dst); + } catch (IOException ex) { + nativeChannel.set(0); // Close the channel + throw ex; + } + throw new ClosedChannelException(); + } } private static native int nativeRead(long nativeChannel, ByteBuffer dst) diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java index 5a948427132de..8f6ab829a2a3f 100644 --- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java +++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/ResponseChannel.java @@ -42,15 +42,18 @@ private ResponseChannel(long nativeChannel) public int write(ByteBuffer src) throws IOException { - long channel = this.nativeChannel.get(); - if (channel != 0) - try { - return nativeWrite(channel, src); - } catch (IOException ex) { - nativeChannel.set(0); // Close the channel - throw ex; - } - throw new ClosedChannelException(); + synchronized (nativeChannel) + { + long channel = this.nativeChannel.get(); + if (channel != 0) + try { + return nativeWrite(channel, src); + } catch (IOException ex) { + nativeChannel.set(0); // Close the channel + throw ex; + } + throw new ClosedChannelException(); + } } private static native int nativeWrite(long nativeChannel, ByteBuffer src) diff --git a/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java b/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java index 933d8834d3fd5..7f95d9739d566 100644 --- a/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java +++ b/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/TunnelChannel.java @@ -50,6 +50,39 @@ public void close() throws IOException nativeClose(channel); } + /** + * Wait for current read/write to complete, then close() channel. + * Compared to close(), it has the following differences: + *
    + *
  • + * Prevents race condition where read/write could use incorrect file : + *
      + *
    1. Writer thread extracts OS file descriptor from nativeChannel.
    2. + *
    3. Writer thread calls OS API to write to file, passing file descriptor.
    4. + *
    5. Writer thread is interrupted.
    6. + *
    7. Closer thread closes OS file descriptor. The file descriptor number is now free.
    8. + *
    9. Unrelated thread opens a new file. OS reuses the old file descriptor (currently free).
    10. + *
    11. Writer thread resumes inside OS API to write to file.
    12. + *
    13. Writer thread writes to unrelated file, corrupting it with unexpected data.
    14. + *
    + *
  • + *
  • + * It can no longer cancel a read/write operation already in progress. + * The native implementation closes the other end of the pipe, breaking the pipe, + * which prevents the risk of never-completing read/write. + *
  • + *
      + * + * @throws IOException + */ + public void syncClose() throws IOException + { + synchronized (nativeChannel) + { + close(); + } + } + private native static void nativeClose(long nativeChannel) throws IOException; diff --git a/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java b/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java index e95abdc975993..99ce9e1789c06 100644 --- a/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java +++ b/subversion/bindings/javahl/src/org/tigris/subversion/javahl/NativeException.java @@ -88,7 +88,11 @@ public int getAprError() */ public String getMessage() { - StringBuffer msg = new StringBuffer(super.getMessage()); + StringBuilder msg = new StringBuilder(); + String message = super.getMessage(); + if (message != null) { + msg.append(message); + } // ### This might be better off in JNIUtil::handleSVNError(). String src = getSource(); if (src != null) diff --git a/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java b/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java index 36c37e569a2b8..64179ae159d4f 100644 --- a/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java +++ b/subversion/bindings/javahl/src/org/tigris/subversion/javahl/SVNClient.java @@ -1200,6 +1200,7 @@ public void resolve(String path, int depth, int conflictResult) * boolean, boolean, int, String)} instead. * @since 1.0 */ + @Deprecated public long doExport(String srcPath, String destPath, Revision revision, boolean force) throws ClientException @@ -1213,6 +1214,7 @@ public long doExport(String srcPath, String destPath, * boolean, boolean, int, String)} instead. * @since 1.2 */ + @Deprecated public long doExport(String srcPath, String destPath, Revision revision, Revision pegRevision, boolean force, boolean ignoreExternals, boolean recurse, @@ -1251,6 +1253,7 @@ public long doExport(String srcPath, String destPath, Revision revision, * instead. * @since 1.0 */ + @Deprecated public long doSwitch(String path, String url, Revision revision, boolean recurse) throws ClientException @@ -1265,6 +1268,7 @@ public long doSwitch(String path, String url, Revision revision, * boolean)} instead. * @since 1.5 */ + @Deprecated public long doSwitch(String path, String url, Revision revision, Revision pegRevision, int depth, boolean depthIsSticky, boolean ignoreExternals, @@ -1359,6 +1363,7 @@ public String[] suggestMergeSources(String path, Revision pegRevision) * boolean, boolean)} instead. * @since 1.0 */ + @Deprecated public void merge(String path1, Revision revision1, String path2, Revision revision2, String localPath, boolean force, boolean recurse) @@ -1374,6 +1379,7 @@ public void merge(String path1, Revision revision1, String path2, * boolean, boolean)} instead. * @since 1.2 */ + @Deprecated public void merge(String path1, Revision revision1, String path2, Revision revision2, String localPath, boolean force, boolean recurse, boolean ignoreAncestry, boolean dryRun) diff --git a/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java b/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java index 30f5e9e8adac9..c1eae971894c3 100644 --- a/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java +++ b/subversion/bindings/javahl/tests/org/apache/subversion/javahl/BasicTests.java @@ -25,7 +25,9 @@ import static org.junit.Assert.*; import org.apache.subversion.javahl.callback.*; +import org.apache.subversion.javahl.remote.*; import org.apache.subversion.javahl.types.*; +import org.apache.subversion.javahl.NativeException; import java.io.File; import java.io.FileOutputStream; @@ -36,6 +38,7 @@ import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.text.ParseException; @@ -4417,6 +4420,344 @@ public void testTunnelAgent() throws Throwable assertEquals("fake", new String(revprop)); } + public static int FLAG_ECHO = 0x00000001; + public static int FLAG_THROW_IN_OPEN = 0x00000002; + + public enum Actions + { + READ_CLIENT, // Read a request from SVN client + EMUL_SERVER, // Emulate server response + WAIT_TUNNEL, // Wait for tunnel to be closed + }; + + public static class ScriptItem + { + Actions action; + String value; + + ScriptItem(Actions action, String value) + { + this.action = action; + this.value = value; + } + } + + private static class TestTunnelAgent extends Thread + implements TunnelAgent + { + ScriptItem[] script; + int flags; + String error = null; + ReadableByteChannel request; + WritableByteChannel response; + + final CloseTunnelCallback closeTunnelCallback = () -> + { + if ((flags & FLAG_ECHO) != 0) + System.out.println("TunnelAgent.CloseTunnelCallback"); + }; + + TestTunnelAgent(int flags, ScriptItem[] script) + { + this.flags = flags; + this.script = script; + } + + public void joinAndTest() + { + try + { + join(); + } + catch (InterruptedException e) + { + fail("InterruptedException was caught"); + } + + if (error != null) + fail(error); + } + + @Override + public boolean checkTunnel(String name) + { + return true; + } + + private String readClient(ByteBuffer readBuffer) + throws IOException + { + readBuffer.reset(); + request.read(readBuffer); + + final int offset = readBuffer.arrayOffset(); + return new String(readBuffer.array(), + offset, + readBuffer.position() - offset); + } + + private void emulateServer(String serverMessage) + throws IOException + { + final byte[] responseBytes = serverMessage.getBytes(); + response.write(ByteBuffer.wrap(responseBytes)); + } + + private void doScriptItem(ScriptItem scriptItem, ByteBuffer readBuffer) + throws Exception + { + switch (scriptItem.action) + { + case READ_CLIENT: + final String actualLine = readClient(readBuffer); + + if ((flags & FLAG_ECHO) != 0) + { + System.out.println("SERVER: " + scriptItem.value); + System.out.flush(); + } + + if (!actualLine.contains(scriptItem.value)) + { + System.err.println("Expected: " + scriptItem.value); + System.err.println("Actual: " + actualLine); + System.err.flush(); + + // Unblock the SVN thread by emulating a server error + final String serverError = "( success ( ( ) 0: ) ) ( failure ( ( 160000 39:Test script received unexpected request 0: 0 ) ) ) "; + emulateServer(serverError); + + fail("Unexpected client request"); + } + break; + case EMUL_SERVER: + if ((flags & FLAG_ECHO) != 0) + { + System.out.println("CLIENT: " + scriptItem.value); + System.out.flush(); + } + + emulateServer(scriptItem.value); + break; + case WAIT_TUNNEL: + // The loop will end with an exception when tunnel is closed + for (;;) + { + readClient(readBuffer); + } + } + } + + public void run() + { + final ByteBuffer readBuffer = ByteBuffer.allocate(1024 * 1024); + readBuffer.mark(); + + for (ScriptItem scriptItem : script) + { + try + { + doScriptItem(scriptItem, readBuffer); + } + catch (ClosedChannelException ex) + { + // Expected when closed properly + } + catch (IOException e) + { + // IOException occurs when already-freed apr_file_t was lucky + // to have reasonable fields to avoid the crash. It still + // indicates a problem. + error = "IOException was caught in run()"; + return; + } + catch (Throwable t) + { + // No other exceptions are expected here. + error = "Exception was caught in run()"; + t.printStackTrace(); + return; + } + } + } + + @Override + public CloseTunnelCallback openTunnel(ReadableByteChannel request, + WritableByteChannel response, + String name, + String user, + String hostname, + int port) + throws Throwable + { + this.request = request; + this.response = response; + + start(); + + if ((flags & FLAG_THROW_IN_OPEN) != 0) + throw ClientException.fromException(new RuntimeException("Test exception")); + + return closeTunnelCallback; + } + }; + + /** + * Test scenario which previously caused a JVM crash. + * In this scenario, GC is invoked before closing tunnel. + */ + public void testCrash_RemoteSession_nativeDispose() + { + final ScriptItem[] script = new ScriptItem[] + { + new ScriptItem(Actions.EMUL_SERVER, "( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) "), + new ScriptItem(Actions.READ_CLIENT, "edit-pipeline"), + new ScriptItem(Actions.EMUL_SERVER, "( success ( ( ANONYMOUS ) 36:0113e071-0208-4a7b-9f20-3038f9caf0f0 ) ) "), + new ScriptItem(Actions.READ_CLIENT, "ANONYMOUS"), + new ScriptItem(Actions.EMUL_SERVER, "( success ( ) ) ( success ( 36:00000000-0000-0000-0000-000000000000 25:svn+test://localhost/test ( mergeinfo ) ) ) "), + }; + + final TestTunnelAgent tunnelAgent = new TestTunnelAgent(0, script); + final RemoteFactory remoteFactory = new RemoteFactory(); + remoteFactory.setTunnelAgent(tunnelAgent); + + ISVNRemote remote = null; + try + { + remote = remoteFactory.openRemoteSession("svn+test://localhost/test", 1); + } + catch (SubversionException e) + { + fail("SubversionException was caught"); + } + + // Previously, 'OperationContext::openTunnel()' didn't 'NewGlobalRef()' + // callback returned by 'TunnelAgent.openTunnel()'. This caused JVM to + // dispose it on next GC. JavaHL calls callback in 'remote.dispose()'. + // If the callback was disposed, this caused a JVM crash. + System.gc(); + remote.dispose(); + + tunnelAgent.joinAndTest(); + } + + /** + * Test scenario which previously caused a JVM crash. + * In this scenario, tunnel was not properly closed after exception in + * 'TunnelAgent.openTunnel()'. + */ + public void testCrash_RequestChannel_nativeRead_AfterException() + { + // Previously, exception caused TunnelChannel's native side to be + // destroyed with the following abbreviated stack: + // TunnelChannel.nativeClose() + // svn_pool_destroy(sesspool) + // svn_ra_open5() + // TunnelAgent was unaware and called 'RequestChannel.nativeRead()' + // or 'ResponseChannel.nativeWrite()', causing either a crash or + // an attempt to use a random file. + final int flags = FLAG_THROW_IN_OPEN; + + final ScriptItem[] script = new ScriptItem[] + { + new ScriptItem(Actions.EMUL_SERVER, "( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) "), + new ScriptItem(Actions.WAIT_TUNNEL, ""), + }; + + final TestTunnelAgent tunnelAgent = new TestTunnelAgent(flags, script); + final SVNClient svnClient = new SVNClient(); + svnClient.setTunnelAgent(tunnelAgent); + + try + { + svnClient.openRemoteSession("svn+test://localhost/test"); + } + catch (SubversionException e) + { + // RuntimeException("Test exception") is expected here + } + + // In this test, there is a race condition that sometimes results in + // IOException when 'WAIT_TUNNEL' tries to read from a pipe that + // already has its read end closed. This is not an error, but + // it's hard to distinguish this case from other IOException which + // indicate a problem. To reproduce, simply wrap this test's body in + // a loop. The workaround is to ignore any detected IOException. + try + { + tunnelAgent.join(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + /** + * Test scenario which previously caused a JVM crash. + * In this scenario, tunnel was not properly closed after an SVN error. + */ + public void testCrash_RequestChannel_nativeRead_AfterSvnError() + { + final String wcRoot = new File("tempSvnRepo").getAbsolutePath(); + + final ScriptItem[] script = new ScriptItem[] + { + // openRemoteSession + new ScriptItem(Actions.EMUL_SERVER, "( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops depth log-revprops atomic-revprops partial-replay inherited-props ephemeral-txnprops file-revs-reverse ) ) ) "), + new ScriptItem(Actions.READ_CLIENT, "edit-pipeline"), + new ScriptItem(Actions.EMUL_SERVER, "( success ( ( ANONYMOUS ) 36:0113e071-0208-4a7b-9f20-3038f9caf0f0 ) ) "), + new ScriptItem(Actions.READ_CLIENT, "ANONYMOUS"), + new ScriptItem(Actions.EMUL_SERVER, "( success ( ) ) ( success ( 36:00000000-0000-0000-0000-000000000000 25:svn+test://localhost/test ( mergeinfo ) ) ) "), + // checkout + new ScriptItem(Actions.READ_CLIENT, "( get-latest-rev ( ) ) "), + // Previously, error caused a SubversionException to be created, + // which then skipped closing the Tunnel properly due to + // 'ExceptionOccurred()' in 'OperationContext::closeTunnel()'. + // If TunnelAgent was unaware and called 'RequestChannel.nativeRead()', + // it either crashed or tried to use a random file. + new ScriptItem(Actions.EMUL_SERVER, "( success ( ( ) 0: ) ) ( failure ( ( 160006 20:This is a test error 0: 0 ) ) ) "), + // Pretend that TunnelAgent tries to read more + new ScriptItem(Actions.WAIT_TUNNEL, ""), + }; + + final TestTunnelAgent tunnelAgent = new TestTunnelAgent(0, script); + final SVNClient svnClient = new SVNClient(); + svnClient.setTunnelAgent(tunnelAgent); + + try + { + svnClient.checkout("svn+test://localhost/test", + wcRoot, + Revision.getInstance(1), + null, + Depth.infinity, + true, + false); + + svnClient.dispose(); + } + catch (ClientException ex) + { + final int SVN_ERR_FS_NO_SUCH_REVISION = 160006; + if (SVN_ERR_FS_NO_SUCH_REVISION != ex.getAllMessages().get(0).getCode()) + ex.printStackTrace(); + } + + tunnelAgent.joinAndTest(); + } + + /** + * Test getMessage in NativeException. + * @throws Throwable + */ + public void testGetMessage() throws Throwable + { + /* NativeException with a null message previously threw a NullPointerException */ + assertEquals("", new NativeException(null, null, null, 0).getMessage()); + assertEquals("messagesvn: source: (apr_err=0)", new NativeException("message", "source", null, 0).getMessage()); + } + /** * @return file converted into a -- possibly * canonical-ized -- Subversion-internal path diff --git a/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java b/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java index 62a881f27d8e3..70e6249d1537b 100644 --- a/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java +++ b/subversion/bindings/javahl/tests/org/tigris/subversion/javahl/BasicTests.java @@ -22,6 +22,8 @@ */ package org.tigris.subversion.javahl; +import org.tigris.subversion.javahl.NativeException; + import java.io.File; import java.io.FileOutputStream; import java.io.FileNotFoundException; @@ -3320,6 +3322,17 @@ public Map getRevprops() } } + /** + * Test getMessage in NativeException. + * @throws Throwable + */ + public void testGetMessage() throws Throwable + { + /* NativeException with a null message previously threw a NullPointerException */ + assertEquals("", new NativeException(null, null, 0).getMessage()); + assertEquals("messagesvn: source: (apr_err=0)", new NativeException("message", "source", 0).getMessage()); + } + /** * @return file converted into a -- possibly * canonical-ized -- Subversion-internal path diff --git a/subversion/bindings/swig/INSTALL b/subversion/bindings/swig/INSTALL index ed1d8eb0a36a7..4a44526c503b6 100644 --- a/subversion/bindings/swig/INSTALL +++ b/subversion/bindings/swig/INSTALL @@ -5,7 +5,12 @@ STATUS OF THE SWIG BINDINGS * Python The Python bindings are fairly well developed, although there are some - missing parts. + missing parts. We support both of Python 2.7 and Python 3.x; however, + SWIG Python bindings for different versions of Python cannot be + simultaneously installed in the same environment, because they need to + install mutually incompatible C shared libraries under the same name. The + distribution tarballs are shipped with SWIG-generated C source files for + Python 3.x. (N.B. As discussed below, they will not compile in Debug mode on Windows.) @@ -67,18 +72,20 @@ BUILDING SWIG BINDINGS FOR SVN ON UNIX Step 1: [Optional] Install a suitable version of SWIG * SWIG installation is optional. You do not need to install SWIG - if you are using a Subversion distribution tarball because it - already contains the source files generated by SWIG. You will - need a suitable version of SWIG if you are using a working copy - of Subversion's sources checked out from the repository, or if - you want to regenerate the SWIG language bindings. + if you are using a Subversion distribution tarball because it already + contains the source files generated by SWIG. You will need a suitable + version of SWIG if you are using a working copy of Subversion's sources + checked out from the repository; if you want to generate the SWIG + language bindings C source files by yourself; or if you want to build + Python 2.x bindings (since the SWIG-generated C source files in the + distribution tarballs target Python 3.x). * We currently support SWIG versions 2.0.0 and later, with the following notes: - - SWIG 1.3.24 and later 1.3.x may work, but we do not test these - versions on our latest source code. - - For Python 2 bindings, SWIG 4.0.0 or later is not supported. - - For Python 3 bindings, SWIG 3.0.10 or later is required. + - For Python 3 bindings, use SWIG 3.0.10 or later. (SWIG 4.0.0 + or later is supported as well.) + - For Python 2 bindings, use SWIG 1.3.40-3.x. (SWIG 4.0.0 or later + is not supported as it lacks the '-classic' option.) - Note that SWIG 3.0.9 has some trouble with Python support. (See https://sourceforge.net/p/swig/news/2016/06/swig-3010-released/) - For Perl 5.16 and later, SWIG 2.0.8 or later is required. @@ -128,42 +135,60 @@ Step 1a: Install py3c library if building Python SWIG bindings. * This is a header-only library, so no configuring or compiling is necessary, simply 'make install'. - + Step 2: Build and Install Subversion. See Subversion's own INSTALL file for details. - Make sure that Subversion's ./configure script sees your installed SWIG! - It tries to detect SWIG near the very end of its output. + If you are using a Subversion distribution tarball and want to rebuild + the SWIG language bindings C source files with your installed SWIG, + you need to execute autogen.sh, because the bundled configure script + and makefiles don't support it. - Also make sure that the configure script sees the paths to the perl and/or - python executable you used to configure SWIG as above. If it does not then - you can specify the correct path by adding PYTHON=/path/to/python or - PERL=/path/to/perl onto the command line for configure. For example: - ./configure PYTHON=/usr/bin/python2.7 PERL=/usr/bin/perl5.8.0 + If you don't use SWIG bindings C source files already generated, + make sure that Subversion's ./configure script sees your installed SWIG! + It tries to detect SWIG near the very end of its output. + You can find it by running 'grep "^SWIG=" config.log'. + + Also make sure that the configure script sees the paths to the perl, + ruby, and/or python executable you used to configure SWIG as above. + If it does not then you can specify the correct path by specifying + --with-swig-python=/path/to/python, --with-swig-perl=/path/to/perl, + and/or --with-swig-ruby=/path/to/ruby to the command line for configure. + For example: + ./configure --with-swig-python=/usr/bin/python3.7 \ + --with-swig-perl=/usr/bin/perl5.28.2 \ + --with-swig-ruby=/usr/bin/ruby2.7 If Subversion's ./configure finds a SWIG that it's happy with, then it will build special glue libraries to link svn to the swig bindings: - libsvn_swig_py.so (for Python) - libsvn_swig_perl.so (for Perl) - + libsvn_swig_py-1.so (for Python) + libsvn_swig_perl-1.so (for Perl) + libsvn_swig_ruby-1.so (for Ruby) Step 3: Install Specific Language Bindings * Python - 1. Run 'make swig-py' from the top of the Subversion build tree, + 1. (Optional) If you want to build Python bindings for a version of + Python than other than that the prebuilt bindings C sources target + (e.g., if you use the Subversion distribution tarball but want to build + Python 2 bindings), run 'make clean-swig-py' from the top of the + Subversion build tree, to ensure not to use incompatible version of + bindings source files. + + 2. Run 'make swig-py' from the top of the Subversion build tree, to build the bindings. (This will invoke SWIG on the *.i files, resulting in a collection of .c source files. It will then compile and link those .c files into Python libraries.) - 2. Run 'make check-swig-py' from the top of the Subversion build - tree, to test the bindings + 3. Run 'make check-swig-py' from the top of the Subversion build + tree, to test the bindings - 3. Run 'make install-swig-py' (as root, typically) + 4. Run 'make install-swig-py' (as root, typically) from the top of the Subversion build tree. This will copy your new Python libraries into the appropriate system location. @@ -177,14 +202,14 @@ Step 3: Install Specific Language Bindings example of doing this for building rpms looks like 'make install-swig-py DESTDIR=$RPM_BUILD_ROOT/usr'. - 4. Make sure that whatever directory the bindings got installed in + 5. Make sure that whatever directory the bindings got installed in is in your Python search path. That directory depends on how you installed; a typical location is /usr/local/lib/svn-python/. There are several ways to do this. See Python's documentation for 'sys.path' and 'PYTHONPATH'. A nice way to do this is: $ echo /usr/local/lib/svn-python \ - > /usr/lib/python2.x/site-packages/subversion.pth + > /usr/lib/python3.x/site-packages/subversion.pth You may also need to update your operating system's dynamic linker configuration to enable Python to load these new libraries. On some @@ -194,15 +219,15 @@ Step 3: Install Specific Language Bindings * Perl Perl 5.8.0 is required. You can specify the perl binary by passing - PERL=/path/to/perl as part of the configure command in the top level - of the Subversion source tree. Make sure that the Perl version used - is the same one that you configured SWIG to run against during the - SWIG configure (see above). + --with-swig-perl=/path/to/perl as an option to the configure command + in the top level of the Subversion source tree. Make sure that the + Perl version used is the same one that you configured SWIG to run + against during the SWIG configure (see above). 1. Run `make swig-pl' from the top of the Subversion build tree. 2. Run `make check-swig-pl' from the top of the Subversion build - tree, to test the bindings + tree, to test the bindings 3. to install run `make install-swig-pl' from the top of the Subversion build tree. @@ -214,9 +239,9 @@ Step 3: Install Specific Language Bindings 2. Run `make install-swig-pl-lib' - 3. cd subversion/bindings/swig/perl/native + 3. cd subversion/bindings/swig/perl/native - 4. Run `perl Makefile.PL EXTRAOPTIONSHERE` + 4. Run `perl Makefile.PL EXTRAOPTIONSHERE` 5. Run `make install' @@ -226,21 +251,22 @@ Step 3: Install Specific Language Bindings * Ruby - + 1. Run `make swig-rb' from the top of the Subversion build tree, to build the bindings. 2. Run `make check-swig-rb' from the top of the Subversion build tree, to test the bindings. - 3. To install, run `make install-swig-rb' from the top of the + 3. To install, run `make install-swig-rb' from the top of the Subversion build tree. - You can specify the ruby binary by passing RUBY=/path/to/ruby as part - of the configure command in the top level of the Subversion source - tree. Make sure that the Ruby version used is the same one that you - configured SWIG to run against during the SWIG configure (see above). - + You can specify the ruby binary by passing --with-swig-ruby=/path/to/ruby + as an option to the configure command in the top level of the + Subversion source tree. Make sure that the Ruby version used is + the same one that you configured SWIG to run against during the + SWIG configure (see above). + BUILDING SWIG BINDINGS FOR SVN ON WINDOWS @@ -260,7 +286,7 @@ BUILDING SWIG BINDINGS FOR SVN ON WINDOWS If you plan to build python bindings, you will also need to download and extract the py3c library from https://github.com/encukou/py3c/releases. - + NOTE: Our Python SWIG bindings will currently NOT compile in Debug mode unless you have python24_d.lib (which binary distributions of Python do not contain). Therefore, the Python bindings will only @@ -276,9 +302,9 @@ BUILDING SWIG BINDINGS FOR SVN ON WINDOWS Example: > gen-make.py --with-swig="C:\Program Files\SWIG-2.0.2" - + If you are building python bindings, point to the py3c directory as well: - + > gen-make.py --with-swig="C:\Program Files\SWIG-2.0.2" \ --with-py3c="C:\SVN\py3c" @@ -364,12 +390,12 @@ TESTING SWIG BINDINGS * Perl The Perl bindings are using the standard module testing facilities - to do regression tests. Simply run 'make check-swig-pl' as described in + to do regression tests. Simply run 'make check-swig-pl' as described in the install section. * Ruby - To test the Ruby bindings, simply run `make check-swig-rb' as described + To test the Ruby bindings, simply run `make check-swig-rb' as described in the install section. diff --git a/subversion/bindings/swig/core.i b/subversion/bindings/swig/core.i index e7222b80c6b90..63d6922274b38 100644 --- a/subversion/bindings/swig/core.i +++ b/subversion/bindings/swig/core.i @@ -23,16 +23,16 @@ * of the more specific module files. */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") core +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) core #elif defined(SWIGPERL) %module "SVN::_Core" #elif defined(SWIGRUBY) %module "svn::ext::core" #endif -%include svn_global.swg - %{ #include #include diff --git a/subversion/bindings/swig/include/proxy.swg b/subversion/bindings/swig/include/proxy.swg index ac67d434ca8aa..7d2d0dd033352 100644 --- a/subversion/bindings/swig/include/proxy.swg +++ b/subversion/bindings/swig/include/proxy.swg @@ -66,7 +66,6 @@ fn() %} -#if defined(SWIGPYTHON_PY3) #if SWIG_VERSION >= 0x040000 %pythoncode %{ # -classic and -modern options have been dropped and this variable @@ -76,7 +75,7 @@ _set_instance_attr = _swig_setattr_nondynamic_instance_variable(object.__setattr__) %} -#else +#elif defined(SWIGPYTHON_PY3) %pythoncode %{ # SWIG classes generated with -modern do not define this variable try: @@ -90,7 +89,6 @@ _set_instance_attr = _swig_setattr_nondynamic_method(object.__setattr__) %} -#endif #else %pythoncode %{ # SWIG classes generated with -classic do not define this variable, diff --git a/subversion/bindings/swig/include/proxy_apr.swg b/subversion/bindings/swig/include/proxy_apr.swg index 110d3952c311a..a1608b4e0e8a1 100644 --- a/subversion/bindings/swig/include/proxy_apr.swg +++ b/subversion/bindings/swig/include/proxy_apr.swg @@ -142,9 +142,15 @@ struct apr_pool_t { are still valid""" try: self._is_valid - return True except AttributeError: return False + # We must check whether the parent pool is valid even if + # the pool is valid because weakref's callback is not + # invoked when it is finalized by cyclic garbage collector + if self._parent_pool: + return self._parent_pool.valid() + else: + return True def assert_valid(self): """Assert that this memory_pool is still valid.""" diff --git a/subversion/bindings/swig/include/svn_containers.swg b/subversion/bindings/swig/include/svn_containers.swg index d3c6305a412b6..7dc3b0dbedea1 100644 --- a/subversion/bindings/swig/include/svn_containers.swg +++ b/subversion/bindings/swig/include/svn_containers.swg @@ -299,7 +299,7 @@ $1 = svn_swig_rb_hash_to_apr_hash_svn_string($input, _global_pool); _global_pool = NULL; if (!NIL_P(rb_pool)) { - if (NIL_P($1)) { + if ($1 == NULL) { svn_swig_rb_destroy_pool(rb_pool); } else { svn_swig_rb_set_pool_for_no_swig_type($input, rb_pool); @@ -310,7 +310,7 @@ %typemap(out) apr_hash_t *PROPHASH { - %append_output(svn_swig_rb_apr_hash_to_hash_svn_string($1)); + $result = svn_swig_rb_apr_hash_to_hash_svn_string($1); } #endif @@ -326,10 +326,8 @@ #ifdef SWIGRUBY %typemap(out) apr_hash_t *CHANGED_PATH_HASH { - VALUE rb_changed_path_hash; - rb_changed_path_hash = + $result = svn_swig_rb_apr_hash_to_hash_swig_type($1, "svn_log_changed_path_t *"); - %append_output(rb_changed_path_hash); } %apply apr_hash_t *CHANGED_PATH_HASH { @@ -373,7 +371,7 @@ svn_swig_rb_hash_to_apr_hash_string($input, _global_pool); _global_pool = NULL; if (!NIL_P(rb_pool)) { - if (NIL_P($1)) { + if ($1 == NULL) { svn_swig_rb_destroy_pool(rb_pool); } else { svn_swig_rb_set_pool_for_no_swig_type($input, rb_pool); @@ -760,7 +758,7 @@ %typemap(out) apr_array_header_t *PROP_LIST { - %append_output(svn_swig_rb_prop_apr_array_to_hash_prop($1)); + $result = svn_swig_rb_prop_apr_array_to_hash_prop($1); } %typemap(in) apr_array_header_t *PROP_LIST_MAY_BE_NULL @@ -778,7 +776,7 @@ %typemap(out) apr_array_header_t *PROP_LIST_MAY_BE_NULL { - %append_output($1 ? svn_swig_rb_prop_apr_array_to_hash_prop($1) : Qnil); + $result = $1 ? svn_swig_rb_prop_apr_array_to_hash_prop($1) : Qnil; } %apply apr_array_header_t *PROP_LIST { diff --git a/subversion/bindings/swig/include/svn_global.swg b/subversion/bindings/swig/include/svn_global.swg index 57b1e236db237..95f896d259e55 100644 --- a/subversion/bindings/swig/include/svn_global.swg +++ b/subversion/bindings/swig/include/svn_global.swg @@ -34,6 +34,11 @@ #ifdef SWIGPYTHON %begin %{ #define SWIG_PYTHON_STRICT_BYTE_CHAR +#if defined(_MSC_VER) +/* Prevent "non-constant aggregate initializer" errors from Python.h in + * Python 3.9 */ +# pragma warning(default : 4204) +#endif %} #endif @@ -242,3 +247,40 @@ static VALUE *_global_vresult_address = &vresult; /* Now, include the main Subversion typemap library. */ %include svn_types.swg %include proxy.swg + + +#ifdef SWIGPYTHON +/* Since Python 3.8+ on Windows, DLL dependencies when loading *.pyd file + * searches only the system paths, the directory containing the *.pyd file and + * the directories added with os.add_dll_directory(). + * See also https://bugs.python.org/issue36085. + */ +%define SVN_PYTHON_MODULEIMPORT +" +def _dll_paths(): + import os + if hasattr(os, 'add_dll_directory'): # Python 3.8+ on Windows + cookies = [] + for path in os.environ.get('PATH', '').split(os.pathsep): + if path and os.path.isabs(path): + try: + cookie = os.add_dll_directory(path) + except OSError: + continue + else: + cookies.append(cookie) + return cookies + else: + return () + +_dll_paths = _dll_paths() +try: + from . import $module +finally: + _dll_path = None + for _dll_path in _dll_paths: + _dll_path.close() + del _dll_paths, _dll_path +" +%enddef +#endif diff --git a/subversion/bindings/swig/include/svn_types.swg b/subversion/bindings/swig/include/svn_types.swg index 7c933b1ac7a75..e1e0009c992b9 100644 --- a/subversion/bindings/swig/include/svn_types.swg +++ b/subversion/bindings/swig/include/svn_types.swg @@ -394,7 +394,7 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE self) if (NIL_P($input)) { } else if (TYPE($input) == T_FIXNUM) { - $1 = (char *)NUM2INT($input); + $1 = (char *)NUM2SWIG($input); if (!($1 == APR_LOCALE_CHARSET || $1 == APR_DEFAULT_CHARSET)) { $1 = NULL; } @@ -435,8 +435,31 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE self) svn_error_clear($1); SWIG_fail; } - Py_INCREF(Py_None); - $result = Py_None; + Py_XDECREF($result); + $result = PyList_New(0); +} + +%typemap(ret) svn_error_t * { + if ($result == NULL) { + $result = Py_None; + Py_INCREF($result); + } + else { + switch (PyList_Size($result)) { + case 0: + $result = Py_None; + Py_INCREF($result); + break; + case 1: + { + PyObject *tmp = $result; + $result = PyList_GetItem(tmp, 0); + Py_INCREF($result); + Py_DECREF(tmp); + } + break; + } + } } #endif @@ -462,14 +485,29 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE self) #endif #ifdef SWIGRUBY -%typemap(out) svn_error_t * +%typemap(out) svn_error_t * (VALUE *svn_presult = NULL) { if ($1) { svn_swig_rb_destroy_pool(_global_svn_swig_rb_pool); svn_swig_rb_pop_pool(_global_svn_swig_rb_pool); svn_swig_rb_handle_svn_error($1); } - $result = Qnil; + $result = rb_ary_new(); + svn_presult = &$result; +} + +%typemap(ret) svn_error_t * +{ + if (TYPE(*svn_presult) == T_ARRAY) { + switch (RARRAY_LEN(*svn_presult)) { + case 0: + *svn_presult = Qnil; + break; + case 1: + *svn_presult = rb_ary_entry(*svn_presult, 0); + break; + } + } } #endif @@ -563,7 +601,7 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE self) %typemap(in, noblock=1) apr_pool_t * { /* Verify that the user supplied a valid pool */ if ($input != Py_None && $input != _global_py_pool) { - SWIG_Python_TypeError(SWIG_TypePrettyName($descriptor), $input); + SWIG_Error(SWIG_TypeError, "a '$type' is expected"); SWIG_arg_fail($svn_argnum); SWIG_fail; } @@ -1205,7 +1243,7 @@ svn_ ## TYPE ## _swig_rb_closed(VALUE self) if (NIL_P($input)) { $1 = NULL; } else if (RSTRING_LEN($input) != APR_MD5_DIGESTSIZE) { - rb_raise(rb_eArgError, "digest size (%d) must be %d", + rb_raise(rb_eArgError, "digest size (%" PRIuSIZE ") must be %d", RSTRING_LEN($input), APR_MD5_DIGESTSIZE); } else { $1 = ($1_ltype)StringValuePtr($input); 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 346041fe05011..ffc036dd6470b 100644 --- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c +++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c @@ -26,6 +26,12 @@ /* Avoid deprecation warnings about PY_SSIZE_T_CLEAN since Python 3.8 */ #define PY_SSIZE_T_CLEAN +#if defined(_MSC_VER) +/* Prevent "non-constant aggregate initializer" errors from Python.h in + * Python 3.9 */ +# pragma warning(default : 4204) +#endif + #include @@ -1171,6 +1177,7 @@ apr_hash_t *svn_swig_py_stringhash_from_dict(PyObject *dict, PyObject *key = PyList_GetItem(keys, i); PyObject *value = PyDict_GetItem(dict, key); const char *propname = make_string_from_ob(key, pool); + const char *propval; if (!propname) { if (!PyErr_Occurred()) @@ -1180,7 +1187,7 @@ apr_hash_t *svn_swig_py_stringhash_from_dict(PyObject *dict, Py_DECREF(keys); return NULL; } - const char *propval = make_string_from_ob_maybe_null(value, pool); + propval = make_string_from_ob_maybe_null(value, pool); if (PyErr_Occurred()) { Py_DECREF(keys); @@ -1215,6 +1222,7 @@ apr_hash_t *svn_swig_py_mergeinfo_from_dict(PyObject *dict, PyObject *key = PyList_GetItem(keys, i); PyObject *value = PyDict_GetItem(dict, key); const char *pathname = make_string_from_ob(key, pool); + const svn_rangelist_t *ranges; if (!pathname) { if (!PyErr_Occurred()) @@ -1224,7 +1232,7 @@ apr_hash_t *svn_swig_py_mergeinfo_from_dict(PyObject *dict, Py_DECREF(keys); return NULL; } - const svn_rangelist_t *ranges = svn_swig_py_seq_to_array(value, + ranges = svn_swig_py_seq_to_array(value, sizeof(const svn_merge_range_t *), svn_swig_py_unwrap_struct_ptr, svn_swig_TypeQuery("svn_merge_range_t *"), @@ -1314,6 +1322,7 @@ apr_hash_t *svn_swig_py_prophash_from_dict(PyObject *dict, PyObject *key = PyList_GetItem(keys, i); PyObject *value = PyDict_GetItem(dict, key); const char *propname = make_string_from_ob(key, pool); + svn_string_t *propval; if (!propname) { if (!PyErr_Occurred()) @@ -1323,7 +1332,7 @@ apr_hash_t *svn_swig_py_prophash_from_dict(PyObject *dict, Py_DECREF(keys); return NULL; } - svn_string_t *propval = make_svn_string_from_ob_maybe_null(value, pool); + propval = make_svn_string_from_ob_maybe_null(value, pool); if (PyErr_Occurred()) { Py_DECREF(keys); @@ -1753,44 +1762,97 @@ static svn_error_t *type_conversion_error(const char *datatype) /*** Editor Wrapping ***/ -/* this baton is used for the editor, directory, and file batons. */ -typedef struct item_baton { - PyObject *editor; /* the editor handling the callbacks */ - PyObject *baton; /* the dir/file baton (or NULL for edit baton) */ - apr_pool_t *pool; /* top-level pool */ -} item_baton; +static PyObject * +make_baton(apr_pool_t *pool, PyObject *parent, PyObject *baton) +{ + PyObject *newb; + + newb = PyObject_CallMethod(parent, "make_decendant", "O&O", + make_ob_pool, pool, baton); + /* We always borrow the reference in ancestor's dict during the C API + processing, so that we never leak the reference even the API aborted + by some error */ + Py_XDECREF(newb); + return newb; +} -static item_baton *make_baton(apr_pool_t *pool, - PyObject *editor, - PyObject *baton) +/* Get 'editor' and 'baton' from _ItemBaton instance. The caller + should be within a Python thread lock. */ +static svn_error_t * +unwrap_item_baton(PyObject **editor, PyObject **baton, PyObject *item_baton) { - item_baton *newb = apr_palloc(pool, sizeof(*newb)); + svn_error_t *err; - /* Note: We steal the caller's reference to 'baton'. */ - Py_INCREF(editor); - newb->editor = editor; - newb->baton = baton; - newb->pool = pool; + if ((*editor = PyObject_GetAttrString(item_baton, "editor")) == NULL) + { + err = callback_exception_error(); + *baton = NULL; + goto finished; + } + if ((*baton = PyObject_GetAttrString(item_baton, "baton")) == NULL) + { + Py_CLEAR(*editor); + err = callback_exception_error(); + goto finished; + } + err = SVN_NO_ERROR; + finished: + Py_XDECREF(*editor); + Py_XDECREF(*baton); + return err; +} - return newb; +/* Get 'editor', 'baton', 'pool' from _ItemBaton instance. The caller + should be within a Python thread lock. */ +static svn_error_t * +unwrap_item_baton_with_pool(PyObject **editor, PyObject **baton, + PyObject **py_pool, PyObject *item_baton) +{ + svn_error_t *err; + + if ((err = unwrap_item_baton(editor, baton, item_baton)) != SVN_NO_ERROR) + { + *py_pool = NULL; + goto finished; + } + if ((*py_pool = PyObject_GetAttrString(item_baton, "pool")) == NULL) + { + err = callback_exception_error(); + *editor = NULL; + *baton = NULL; + goto finished; + } + err = SVN_NO_ERROR; + finished: + Py_XDECREF(*py_pool); + return err; } -static svn_error_t *close_baton(void *baton, - const char *method) +static svn_error_t * +close_baton(void *baton, const char *method, svn_boolean_t without_item) { - item_baton *ib = baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } + if (without_item) + { + baton_item = NULL; + } /* If there is no baton object, then it is an edit_baton, and we should not bother to pass an object. Note that we still shove a NULL onto the stack, but the format specified just won't reference it. */ /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)method, - ib->baton ? (char *)"(O)" : NULL, - ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)method, + baton_item ? (char *)"(O)" : NULL, + baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -1799,19 +1861,24 @@ static svn_error_t *close_baton(void *baton, /* there is no return value, so just toss this object (probably Py_None) */ Py_DECREF(result); - /* Release the editor object */ - Py_DECREF(ib->editor); - - /* We're now done with the baton. Since there isn't really a free, all - we need to do is note that its objects are no longer referenced by - the baton. */ - Py_XDECREF(ib->baton); - -#ifdef SVN_DEBUG - ib->editor = ib->baton = NULL; -#endif - - err = SVN_NO_ERROR; + /* We're now done with the baton. Release it from ancestor's dict */ + if (PyObject_HasAttrString(ib, "release_self")) + { + /* Get reference for ib, because following function call remove + ib object from ancestor's dict, which we borrow the reference */ + Py_INCREF(ib); + result = PyObject_CallMethod(ib, "release_self", NULL, NULL); + /* Now we can release the reference safely */ + Py_DECREF(ib); + if (result == NULL) + { + err = callback_exception_error(); + goto finished; + } + /* there is no return value, so just toss this object + (probably Py_None) */ + Py_DECREF(result); + } finished: svn_swig_py_release_py_lock(); @@ -1822,14 +1889,19 @@ static svn_error_t *set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool) { - item_baton *ib = edit_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = edit_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_target_revision", + if ((result = PyObject_CallMethod(editor, (char *)"set_target_revision", (char *)"l", target_revision)) == NULL) { err = callback_exception_error(); @@ -1850,14 +1922,19 @@ static svn_error_t *open_root(void *edit_baton, apr_pool_t *dir_pool, void **root_baton) { - item_baton *ib = edit_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = edit_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"open_root", + if ((result = PyObject_CallMethod(editor, (char *)"open_root", (char *)"lO&", base_revision, make_ob_pool, dir_pool)) == NULL) { @@ -1865,11 +1942,15 @@ static svn_error_t *open_root(void *edit_baton, goto finished; } - /* make_baton takes our 'result' reference */ - *root_baton = make_baton(dir_pool, ib->editor, result); + if ((*root_baton = make_baton(dir_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -1879,16 +1960,21 @@ static svn_error_t *delete_entry(const char *path, void *parent_baton, apr_pool_t *pool) { - item_baton *ib = parent_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"delete_entry", + if ((result = PyObject_CallMethod(editor, (char *)"delete_entry", (char *)SVN_SWIG_BYTES_FMT "lOO&", - path, revision, ib->baton, + path, revision, baton_item, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); @@ -1911,20 +1997,25 @@ static svn_error_t *add_directory(const char *path, apr_pool_t *dir_pool, void **child_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"add_directory", + if ((result = PyObject_CallMethod(editor, (char *)"add_directory", #if IS_PY3 (char *)"yOylO&", #else (char *)"sOslO&", #endif - path, ib->baton, + path, baton_item, copyfrom_path, copyfrom_revision, make_ob_pool, dir_pool)) == NULL) { @@ -1932,11 +2023,15 @@ static svn_error_t *add_directory(const char *path, goto finished; } - /* make_baton takes our 'result' reference */ - *child_baton = make_baton(dir_pool, ib->editor, result); + if ((*child_baton = make_baton(dir_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -1947,27 +2042,36 @@ static svn_error_t *open_directory(const char *path, apr_pool_t *dir_pool, void **child_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"open_directory", + if ((result = PyObject_CallMethod(editor, (char *)"open_directory", (char *)SVN_SWIG_BYTES_FMT "OlO&", - path, ib->baton, base_revision, + path, baton_item, base_revision, make_ob_pool, dir_pool)) == NULL) { err = callback_exception_error(); goto finished; } - /* make_baton takes our 'result' reference */ - *child_baton = make_baton(dir_pool, ib->editor, result); + if ((*child_baton = make_baton(dir_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -1977,20 +2081,25 @@ static svn_error_t *change_dir_prop(void *dir_baton, const svn_string_t *value, apr_pool_t *pool) { - item_baton *ib = dir_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = dir_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"change_dir_prop", + if ((result = PyObject_CallMethod(editor, (char *)"change_dir_prop", #if IS_PY3 (char *)"Oyy#O&", #else (char *)"Oss#O&", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0), make_ob_pool, pool)) == NULL) @@ -2011,7 +2120,7 @@ static svn_error_t *change_dir_prop(void *dir_baton, static svn_error_t *close_directory(void *dir_baton, apr_pool_t *pool) { - return close_baton(dir_baton, "close_directory"); + return close_baton(dir_baton, "close_directory", FALSE); } static svn_error_t *add_file(const char *path, @@ -2021,20 +2130,25 @@ static svn_error_t *add_file(const char *path, apr_pool_t *file_pool, void **file_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"add_file", + if ((result = PyObject_CallMethod(editor, (char *)"add_file", #if IS_PY3 (char *)"yOylO&", #else (char *)"sOslO&", #endif - path, ib->baton, + path, baton_item, copyfrom_path, copyfrom_revision, make_ob_pool, file_pool)) == NULL) { @@ -2042,12 +2156,16 @@ static svn_error_t *add_file(const char *path, goto finished; } - /* make_baton takes our 'result' reference */ - *file_baton = make_baton(file_pool, ib->editor, result); + if ((*file_baton = make_baton(file_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2058,27 +2176,36 @@ static svn_error_t *open_file(const char *path, apr_pool_t *file_pool, void **file_baton) { - item_baton *ib = parent_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parent_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"open_file", + if ((result = PyObject_CallMethod(editor, (char *)"open_file", (char *)SVN_SWIG_BYTES_FMT "OlO&", - path, ib->baton, base_revision, + path, baton_item, base_revision, make_ob_pool, file_pool)) == NULL) { err = callback_exception_error(); goto finished; } - /* make_baton takes our 'result' reference */ - *file_baton = make_baton(file_pool, ib->editor, result); + if ((*file_baton = make_baton(file_pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2086,12 +2213,19 @@ static svn_error_t *open_file(const char *path, static svn_error_t *window_handler(svn_txdelta_window_t *window, void *baton) { - PyObject *handler = baton; - PyObject *result; - svn_error_t *err; + PyObject *editor = NULL, *handler = NULL; + PyObject *ib = baton; + PyObject *result = NULL; + int is_last_call = FALSE; + svn_error_t *err = SVN_NO_ERROR; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &handler, ib)) != SVN_NO_ERROR) + { + is_last_call = TRUE; + goto finished; + } if (window == NULL) { /* the last call; it closes the handler */ @@ -2099,9 +2233,8 @@ static svn_error_t *window_handler(svn_txdelta_window_t *window, /* invoke the handler with None for the window */ /* ### python doesn't have 'const' on the format */ result = PyObject_CallFunction(handler, (char *)"O", Py_None); + is_last_call = TRUE; - /* we no longer need to refer to the handler object */ - Py_DECREF(handler); } else { @@ -2114,14 +2247,40 @@ static svn_error_t *window_handler(svn_txdelta_window_t *window, if (result == NULL) { err = callback_exception_error(); + is_last_call = TRUE; goto finished; } - - /* there is no return value, so just toss this object (probably Py_None) */ - Py_DECREF(result); - err = SVN_NO_ERROR; + else + { + /* there is no return value, so just toss this object + (probably Py_None) */ + Py_DECREF(result); + err = SVN_NO_ERROR; + } finished: + if (is_last_call) + { + /* now we should release the handler object */ + if (PyObject_HasAttrString(ib, "release_self")) + { + /* Get reference for ib, because following function call remove + ib object from ancestor's dict, which we borrow the reference */ + Py_INCREF(ib); + result = PyObject_CallMethod(ib, "release_self", NULL, NULL); + /* Now we can release the reference safely */ + Py_DECREF(ib); + if (result == NULL) + { + if (err == SVN_NO_ERROR) + { + err = callback_exception_error(); + } + } + Py_XDECREF(result); + } + } + svn_swig_py_release_py_lock(); return err; } @@ -2132,20 +2291,25 @@ static svn_error_t *apply_textdelta(void *file_baton, svn_txdelta_window_handler_t *handler, void **h_baton) { - item_baton *ib = file_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = file_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"apply_textdelta", + if ((result = PyObject_CallMethod(editor, (char *)"apply_textdelta", #if IS_PY3 (char *)"(Oy)", #else (char *)"(Os)", #endif - ib->baton, + baton_item, base_checksum)) == NULL) { err = callback_exception_error(); @@ -2157,22 +2321,26 @@ static svn_error_t *apply_textdelta(void *file_baton, in Python. */ if (result == Py_None) { - Py_DECREF(result); - *handler = svn_delta_noop_window_handler; *h_baton = NULL; } else { - /* return the thunk for invoking the handler. the baton takes our - 'result' reference, which is the handler. */ + /* return the thunk for invoking the handler. the baton creates + new reference of our 'result' reference, which is the handler, + so we release it even if no error. */ *handler = window_handler; - *h_baton = result; + if ((*h_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2182,20 +2350,25 @@ static svn_error_t *change_file_prop(void *file_baton, const svn_string_t *value, apr_pool_t *pool) { - item_baton *ib = file_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = file_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"change_file_prop", + if ((result = PyObject_CallMethod(editor, (char *)"change_file_prop", #if IS_PY3 (char *)"Oyy#O&", #else (char *)"Oss#O&", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0), make_ob_pool, pool)) == NULL) @@ -2217,20 +2390,25 @@ static svn_error_t *close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool) { - item_baton *ib = file_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = file_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"close_file", + if ((result = PyObject_CallMethod(editor, (char *)"close_file", #if IS_PY3 (char *)"(Oy)", #else (char *)"(Os)", #endif - ib->baton, + baton_item, text_checksum)) == NULL) { err = callback_exception_error(); @@ -2240,14 +2418,24 @@ static svn_error_t *close_file(void *file_baton, /* there is no return value, so just toss this object (probably Py_None) */ Py_DECREF(result); - /* We're now done with the baton. Since there isn't really a free, all - we need to do is note that its objects are no longer referenced by - the baton. */ - Py_XDECREF(ib->baton); - -#ifdef SVN_DEBUG - ib->editor = ib->baton = NULL; -#endif + /* We're now done with the baton. Release it from ancestor's dict */ + if (PyObject_HasAttrString(ib, "release_self")) + { + /* Get reference for ib, because following function call remove + ib object from ancestor's dict, which we borrow the reference */ + Py_INCREF(ib); + result = PyObject_CallMethod(ib, "release_self", NULL, NULL); + /* Now we can release the reference safely */ + Py_DECREF(ib); + if (result == NULL) + { + err = callback_exception_error(); + goto finished; + } + /* there is no return value, so just toss this object + (probably Py_None) */ + Py_DECREF(result); + } err = SVN_NO_ERROR; @@ -2259,18 +2447,16 @@ static svn_error_t *close_file(void *file_baton, static svn_error_t *close_edit(void *edit_baton, apr_pool_t *pool) { - return close_baton(edit_baton, "close_edit"); + return close_baton(edit_baton, "close_edit", TRUE); } static svn_error_t *abort_edit(void *edit_baton, apr_pool_t *pool) { - return close_baton(edit_baton, "abort_edit"); + return close_baton(edit_baton, "abort_edit", TRUE); } void svn_swig_py_make_editor(const svn_delta_editor_t **editor, - void **edit_baton, - PyObject *py_editor, apr_pool_t *pool) { svn_delta_editor_t *thunk_editor = svn_delta_default_editor(pool); @@ -2291,7 +2477,6 @@ void svn_swig_py_make_editor(const svn_delta_editor_t **editor, thunk_editor->abort_edit = abort_edit; *editor = thunk_editor; - *edit_baton = make_baton(pool, py_editor, NULL); } @@ -2301,14 +2486,19 @@ static svn_error_t *parse_fn3_magic_header_record(int version, void *parse_baton, apr_pool_t *pool) { - item_baton *ib = parse_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parse_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"magic_header_record", + if ((result = PyObject_CallMethod(editor, (char *)"magic_header_record", (char *)"lO&", version, make_ob_pool, pool)) == NULL) { @@ -2330,14 +2520,19 @@ static svn_error_t *parse_fn3_uuid_record(const char *uuid, void *parse_baton, apr_pool_t *pool) { - item_baton *ib = parse_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parse_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"uuid_record", + if ((result = PyObject_CallMethod(editor, (char *)"uuid_record", (char *)SVN_SWIG_BYTES_FMT "O&", uuid, make_ob_pool, pool)) == NULL) { @@ -2360,14 +2555,19 @@ static svn_error_t *parse_fn3_new_revision_record(void **revision_baton, void *parse_baton, apr_pool_t *pool) { - item_baton *ib = parse_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = parse_baton; + PyObject *result = NULL; PyObject *tmp; svn_error_t *err; svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallMethod(ib->editor, (char *)"new_revision_record", + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } + if ((result = PyObject_CallMethod(editor, (char *)"new_revision_record", (char *)"O&O&", svn_swig_py_stringhash_to_dict, headers, make_ob_pool, pool)) == NULL) { @@ -2375,11 +2575,15 @@ static svn_error_t *parse_fn3_new_revision_record(void **revision_baton, goto finished; } - /* make_baton takes our 'result' reference */ - *revision_baton = make_baton(pool, ib->editor, result); + if ((*revision_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2390,26 +2594,35 @@ static svn_error_t *parse_fn3_new_node_record(void **node_baton, void *revision_baton, apr_pool_t *pool) { - item_baton *ib = revision_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = revision_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); - if ((result = PyObject_CallMethod(ib->editor, (char *)"new_node_record", + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } + if ((result = PyObject_CallMethod(editor, (char *)"new_node_record", (char *)"O&OO&", svn_swig_py_stringhash_to_dict, headers, - ib->baton, + baton_item, make_ob_pool, pool)) == NULL) { err = callback_exception_error(); goto finished; } - /* make_baton takes our 'result' reference */ - *node_baton = make_baton(pool, ib->editor, result); + if ((*node_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2419,20 +2632,25 @@ static svn_error_t *parse_fn3_set_revision_property(void *revision_baton, const char *name, const svn_string_t *value) { - item_baton *ib = revision_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = revision_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_revision_property", + if ((result = PyObject_CallMethod(editor, (char *)"set_revision_property", #if IS_PY3 (char *)"Oyy#", #else (char *)"Oss#", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0))) == NULL) @@ -2455,20 +2673,25 @@ static svn_error_t *parse_fn3_set_node_property(void *node_baton, const char *name, const svn_string_t *value) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = node_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_node_property", + if ((result = PyObject_CallMethod(editor, (char *)"set_node_property", #if IS_PY3 (char *)"Oyy#", #else (char *)"Oss#", #endif - ib->baton, name, + baton_item, name, value ? value->data : NULL, (Py_ssize_t) (value ? value->len : 0))) == NULL) @@ -2490,16 +2713,21 @@ static svn_error_t *parse_fn3_set_node_property(void *node_baton, static svn_error_t *parse_fn3_delete_node_property(void *node_baton, const char *name) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = node_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"delete_node_property", + if ((result = PyObject_CallMethod(editor, (char *)"delete_node_property", (char *)"O" SVN_SWIG_BYTES_FMT, - ib->baton, name)) == NULL) + baton_item, name)) == NULL) { err = callback_exception_error(); goto finished; @@ -2517,15 +2745,20 @@ static svn_error_t *parse_fn3_delete_node_property(void *node_baton, static svn_error_t *parse_fn3_remove_node_props(void *node_baton) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL; + PyObject *ib = node_baton; PyObject *result; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton(&editor, &baton_item, ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"remove_node_props", - (char *)"(O)", ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)"remove_node_props", + (char *)"(O)", baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -2544,15 +2777,22 @@ static svn_error_t *parse_fn3_remove_node_props(void *node_baton) static svn_error_t *parse_fn3_set_fulltext(svn_stream_t **stream, void *node_baton) { - item_baton *ib = node_baton; + PyObject *editor = NULL, *baton_item = NULL, *py_pool = NULL; + PyObject *ib = node_baton; PyObject *result = NULL; + apr_pool_t *pool; svn_error_t *err = SVN_NO_ERROR; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton_with_pool(&editor, &baton_item, &py_pool, + ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"set_fulltext", - (char *)"(O)", ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)"set_fulltext", + (char *)"(O)", baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -2565,9 +2805,15 @@ static svn_error_t *parse_fn3_set_fulltext(svn_stream_t **stream, } else { + if (svn_swig_ConvertPtrString(py_pool, (void **)&pool, + "apr_pool_t *") == -1) + { + err = type_conversion_error("apr_pool_t *"); + goto finished; + } /* create a stream from the IO object. it will increment the reference on the 'result'. */ - *stream = svn_swig_py_make_stream(result, ib->pool); + *stream = svn_swig_py_make_stream(result, pool); if (*stream == NULL) { err = callback_exception_error(); @@ -2584,19 +2830,26 @@ static svn_error_t *parse_fn3_set_fulltext(svn_stream_t **stream, } -static svn_error_t *parse_fn3_apply_textdelta(svn_txdelta_window_handler_t *handler, - void **handler_baton, - void *node_baton) +static svn_error_t * +parse_fn3_apply_textdelta(svn_txdelta_window_handler_t *handler, + void **handler_baton, + void *node_baton) { - item_baton *ib = node_baton; - PyObject *result; + PyObject *editor = NULL, *baton_item = NULL, *py_pool = NULL; + PyObject *ib = node_baton; + PyObject *result = NULL; svn_error_t *err; svn_swig_py_acquire_py_lock(); + if ((err = unwrap_item_baton_with_pool(&editor, &baton_item, &py_pool, + ib)) != SVN_NO_ERROR) + { + goto finished; + } /* ### python doesn't have 'const' on the method name and format */ - if ((result = PyObject_CallMethod(ib->editor, (char *)"apply_textdelta", - (char *)"(O)", ib->baton)) == NULL) + if ((result = PyObject_CallMethod(editor, (char *)"apply_textdelta", + (char *)"(O)", baton_item)) == NULL) { err = callback_exception_error(); goto finished; @@ -2607,22 +2860,33 @@ static svn_error_t *parse_fn3_apply_textdelta(svn_txdelta_window_handler_t *hand in Python. */ if (result == Py_None) { - Py_DECREF(result); - *handler = svn_delta_noop_window_handler; *handler_baton = NULL; } else { - /* return the thunk for invoking the handler. the baton takes our - 'result' reference, which is the handler. */ + apr_pool_t *pool; + /* return the thunk for invoking the handler. the baton creates + new reference of our 'result' reference, which is the handler, + so we release it even if no error. */ *handler = window_handler; - *handler_baton = result; + if (svn_swig_ConvertPtrString(py_pool, (void **)&pool, + "apr_pool_t *") == -1) + { + err = type_conversion_error("apr_pool_t *"); + goto finished; + } + if ((*handler_baton = make_baton(pool, ib, result)) == NULL) + { + err = callback_exception_error(); + goto finished; + } } err = SVN_NO_ERROR; finished: + Py_XDECREF(result); svn_swig_py_release_py_lock(); return err; } @@ -2630,13 +2894,13 @@ static svn_error_t *parse_fn3_apply_textdelta(svn_txdelta_window_handler_t *hand static svn_error_t *parse_fn3_close_node(void *node_baton) { - return close_baton(node_baton, "close_node"); + return close_baton(node_baton, "close_node", FALSE); } static svn_error_t *parse_fn3_close_revision(void *revision_baton) { - return close_baton(revision_baton, "close_revision"); + return close_baton(revision_baton, "close_revision", FALSE); } @@ -2656,26 +2920,11 @@ static const svn_repos_parse_fns3_t thunk_parse_fns3_vtable = parse_fn3_close_revision }; -static apr_status_t -svn_swig_py_parse_fns3_destroy(void *parse_baton) -{ - close_baton(parse_baton, "_close_dumpstream"); - return APR_SUCCESS; -} - void svn_swig_py_make_parse_fns3(const svn_repos_parse_fns3_t **parse_fns3, - void **parse_baton, - PyObject *py_parse_fns3, apr_pool_t *pool) { *parse_fns3 = &thunk_parse_fns3_vtable; - *parse_baton = make_baton(pool, py_parse_fns3, NULL); - - /* Dump stream vtable does not provide a method which is called right before - the end of the parsing (similar to close_edit/abort_edit in delta editor). - Thus, register a pool clean-up routine to release this parse baton. */ - apr_pool_cleanup_register(pool, *parse_baton, svn_swig_py_parse_fns3_destroy, - apr_pool_cleanup_null); + return; } 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 7650cec0171e8..31664a63db53a 100644 --- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h +++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h @@ -268,14 +268,10 @@ svn_swig_py_unwrap_struct_ptr(PyObject *source, /* make an editor that "thunks" from C callbacks up to Python */ void svn_swig_py_make_editor(const svn_delta_editor_t **editor, - void **edit_baton, - PyObject *py_editor, apr_pool_t *pool); /* make a parse vtable that "thunks" from C callbacks up to Python */ void svn_swig_py_make_parse_fns3(const svn_repos_parse_fns3_t **parse_fns3, - void **parse_baton, - PyObject *py_parse_fns3, apr_pool_t *pool); apr_file_t *svn_swig_py_make_file(PyObject *py_file, diff --git a/subversion/bindings/swig/python/svn/delta.py b/subversion/bindings/swig/python/svn/delta.py index 0a712cde8b776..5058432df137e 100644 --- a/subversion/bindings/swig/python/svn/delta.py +++ b/subversion/bindings/swig/python/svn/delta.py @@ -77,5 +77,6 @@ def abort_edit(self, pool=None): pass -def make_editor(editor, pool=None): - return svn_swig_py_make_editor(editor, pool) +def make_editor(editor, pool=None, baton=None): + from libsvn.delta import _AncBaton + return svn_swig_py_make_editor(pool), _AncBaton(editor, pool, baton) diff --git a/subversion/bindings/swig/python/svn/fs.py b/subversion/bindings/swig/python/svn/fs.py index 13acc04cc76c0..7d00d1ac58361 100644 --- a/subversion/bindings/swig/python/svn/fs.py +++ b/subversion/bindings/swig/python/svn/fs.py @@ -23,6 +23,7 @@ # under the License. ###################################################################### +import errno from libsvn.fs import * from svn.core import _unprefix_names, Pool, _as_list _unprefix_names(locals(), 'svn_fs_') @@ -130,6 +131,18 @@ def get_files(self): return self.tempfile1, self.tempfile2 def get_pipe(self): + """Perform diff and return a file object from which the output can + be read. + + When DIFFOPTIONS is None (the default), use svn's internal diff. + + With any other DIFFOPTIONS, exec the external diff found on PATH, + passing it DIFFOPTIONS. On Windows, exec diff.exe rather than + diff. If a diff utility is not installed or found on PATH, throws + FileNotFoundError. Caveat: On some systems, including Windows, an + external diff may not be available unless installed and added to + PATH manually. + """ self.get_files() # If diffoptions were provided, then the diff command needs to be @@ -142,8 +155,17 @@ def get_pipe(self): + [self.tempfile1, self.tempfile2] # open the pipe, and return the file object for reading from the child. - p = _subprocess.Popen(cmd, stdout=_subprocess.PIPE, bufsize=-1, - close_fds=_sys.platform != "win32") + try: + p = _subprocess.Popen(cmd, stdout=_subprocess.PIPE, bufsize=-1, + close_fds=_sys.platform != "win32") + # When removing Python 2 support: Change to FileNotFoundError and + # remove check for ENOENT (FileNotFoundError "Corresponds to errno + # ENOENT" according to documentation) + except OSError as err: + if err.errno == errno.ENOENT: + err.strerror = "External diff command not found in PATH" + raise err + return _PopenStdoutWrapper(p) else: diff --git a/subversion/bindings/swig/python/svn/repos.py b/subversion/bindings/swig/python/svn/repos.py index 486a85026f00a..f075acf378042 100644 --- a/subversion/bindings/swig/python/svn/repos.py +++ b/subversion/bindings/swig/python/svn/repos.py @@ -336,5 +336,16 @@ def close_revision(self, revision_baton): pass -def make_parse_fns3(parse_fns3, pool=None): - return svn_swig_py_make_parse_fns3(parse_fns3, pool) +def make_parse_fns3(parse_fns3, pool=None, baton=None): + from libsvn.delta import _AncBaton + + class _ParseBaton(_AncBaton): + # Drive _close_dumpstream method when the instance is deleted. + # For backward compatibility before Subversion 1.15, we call it even if + # the instance would not be used by C API, or the C API would cause + # some error. + def __del__(self): + self.editor._close_dumpstream() + + parse_baton = _ParseBaton(parse_fns3, pool, baton) + return svn_swig_py_make_parse_fns3(pool), parse_baton diff --git a/subversion/bindings/swig/python/tests/client.py b/subversion/bindings/swig/python/tests/client.py index 422dc4f2b1b2e..3f32c3d31cbd0 100644 --- a/subversion/bindings/swig/python/tests/client.py +++ b/subversion/bindings/swig/python/tests/client.py @@ -172,7 +172,9 @@ def test_checkout(self): path = self.temper.alloc_empty_dir('-checkout') - self.assertRaises(ValueError, client.checkout2, + # TypeError is raised since SWIG 4.3.0 + self.assertRaises((ValueError, TypeError), r'Received a NULL pointer', + client.checkout2, self.repos_uri, path, None, None, True, True, self.client_ctx) @@ -233,9 +235,17 @@ def test_get_commit_log3_callback_unicode_error(self): u" message") self.client_ctx.log_msg_baton3 = bogus_log_message_func - with self.assertRaises(UnicodeEncodeError): - commit_info = client.mkdir3((directory,), 1, {b'customprop':b'value'}, - self.client_ctx) + if not utils.IS_PY3 and utils.is_defaultencoding_utf8(): + # 'utf-8' codecs on Python 2 does not raise UnicodeEncodeError + # on surrogate code point U+dc00 - U+dcff, however it causes + # Subversion error on property validation of svn:log + with self.assertRaises(core.SubversionException): + commit_info = client.mkdir3((directory,), 1, {b'customprop':b'value'}, + self.client_ctx) + else: + with self.assertRaises(UnicodeEncodeError): + commit_info = client.mkdir3((directory,), 1, {b'customprop':b'value'}, + self.client_ctx) def test_log3_url(self): """Test svn_client_log3 on a file:// URL""" @@ -302,10 +312,14 @@ def log_entry_receiver_whole(log_entry, pool): self.assertEqual(revprops[b'svn:author'], b"john") with self.assertRaises(KeyError): commit_date = revprops['svn:date'] - with self.assertRaises(UnicodeEncodeError): - client.log5((directory,), start, (rev_range,), 1, True, False, False, - (u'svn:\udc61uthor', b'svn:log'), - log_entry_receiver_whole, self.client_ctx) + if utils.IS_PY3 or not utils.is_defaultencoding_utf8(): + # 'utf-8' codecs on Python 2 does not raise UnicodeEncodeError + # on surrogate code point U+dc00 - U+dcff. So we need to skip + # below in such a case. + with self.assertRaises(UnicodeEncodeError): + client.log5((directory,), start, (rev_range,), 1, True, False, False, + (u'svn:\udc61uthor', b'svn:log'), + log_entry_receiver_whole, self.client_ctx) def test_uuid_from_url(self): """Test svn_client_uuid_from_url on a file:// URL""" @@ -514,7 +528,9 @@ def test_update4(self): path = self.temper.alloc_empty_dir('-update') - self.assertRaises(ValueError, client.checkout2, + # TypeError is raised since SWIG 4.3.0 + self.assertRaises((ValueError, TypeError), r'Received a NULL pointer', + client.checkout2, self.repos_uri, path, None, None, True, True, self.client_ctx) diff --git a/subversion/bindings/swig/python/tests/core.py b/subversion/bindings/swig/python/tests/core.py index cc71ac284922a..26e2963df199f 100644 --- a/subversion/bindings/swig/python/tests/core.py +++ b/subversion/bindings/swig/python/tests/core.py @@ -23,8 +23,6 @@ import tempfile import sys -IS_PY3 = sys.version_info[0] >= 3 - import svn.core, svn.client import utils @@ -222,6 +220,8 @@ def test_stream_read2(self): self.assertEqual(svn.core.svn_stream_read2(stream, 4096), b'') svn.core.svn_stream_close(stream) + @unittest.skipIf(not utils.IS_PY3 and utils.is_defaultencoding_utf8(), + "'utf-8' codecs of Python 2 accepts any unicode strings") def test_stream_write_exception(self): stream = svn.core.svn_stream_empty() with self.assertRaises(TypeError): @@ -238,9 +238,10 @@ def test_stream_write_exception(self): # As default codec of Python 2 is 'ascii', conversion from unicode to bytes # will be success only if all characters of target strings are in the range # of \u0000 ~ \u007f. - @unittest.skipUnless(IS_PY3, "test for Python 3 only") + @unittest.skipUnless(utils.IS_PY3 or utils.is_defaultencoding_utf8(), + "test ony for Python 3 or Python 2 'utf-8' codecs") def test_stream_write_str(self): - o1_str = u'Python\x00\u3071\u3044\u305d\83093\r\n' + o1_str = u'Python\x00\u3071\u3044\u305d\u3093\r\n' o2_str = u'subVersioN\x00\u3055\u3076\u3070\u30fc\u3058\u3087\u3093' o3_str = u'swig\x00\u3059\u3046\u3043\u3050\rend' out_str = o1_str + o2_str + o3_str @@ -332,6 +333,34 @@ def test_stream_readline(self): [b'', 1]) svn.core.svn_stream_close(stream) + def test_svn_rangelist_diff(self): + """ + SWIG incorrectly handles return values when the first %append_output() is + invoked with a list instance. svn.core.svn_rangelist_diff() is in the case. + We test whether the workaround for it is working. + """ + + def from_args(start, end, inheritable): + instance = svn.core.svn_merge_range_t() + instance.start = start + instance.end = end + instance.inheritable = inheritable + return instance + + def to_args(instance): + return [instance.start, instance.end, instance.inheritable] + + def map_list(f, iterator): + return list(map(f, iterator)) + + from_ = [from_args(4, 5, True), from_args(9, 13, True)] + to = [from_args(7, 11, True)] + rv = svn.core.svn_rangelist_diff(from_, to, True) + self.assertIsInstance(rv, (list, tuple)) + deleted, added = rv + self.assertEqual([[7, 9, True]], map_list(to_args, added)) + self.assertEqual([[4, 5, True], [11, 13, True]],map_list(to_args, deleted)) + def suite(): return unittest.defaultTestLoader.loadTestsFromTestCase( diff --git a/subversion/bindings/swig/python/tests/data/repository-deltas.dump b/subversion/bindings/swig/python/tests/data/repository-deltas.dump new file mode 100644 index 0000000000000..aaf3e620c78ff Binary files /dev/null and b/subversion/bindings/swig/python/tests/data/repository-deltas.dump differ diff --git a/subversion/bindings/swig/python/tests/delta.py b/subversion/bindings/swig/python/tests/delta.py index f668c324885cb..b054fcd27643f 100644 --- a/subversion/bindings/swig/python/tests/delta.py +++ b/subversion/bindings/swig/python/tests/delta.py @@ -21,6 +21,7 @@ import unittest, setup_path import os import tempfile +import weakref import svn.delta import svn.core from sys import version_info # For Python version check @@ -117,6 +118,19 @@ def testTxdeltaWindowT(self): # Check that the ops inherit the window's pool self.assertEqual(window.ops[0]._parent_pool, window._parent_pool) + def testMakeEditorLeak(self): + """Issue 4916, check ref-count leak on svn.delta.make_editor()""" + pool = svn.core.Pool() + editor = svn.delta.Editor() + editor_ref = weakref.ref(editor) + e_ptr, e_baton = svn.delta.make_editor(editor, pool) + del e_ptr, e_baton + self.assertNotEqual(editor_ref(), None) + del pool + self.assertNotEqual(editor_ref(), None) + del editor + self.assertEqual(editor_ref(), None) + def suite(): return unittest.defaultTestLoader.loadTestsFromTestCase(DeltaTestCase) diff --git a/subversion/bindings/swig/python/tests/fs.py b/subversion/bindings/swig/python/tests/fs.py index 40e1b0cff0729..4da50493e119d 100644 --- a/subversion/bindings/swig/python/tests/fs.py +++ b/subversion/bindings/swig/python/tests/fs.py @@ -107,6 +107,9 @@ def test_diff_repos_paths_external(self): try: diffout, differr = Popen(["diff"], stdin=PIPE, stderr=PIPE).communicate() + # When removing Python 2 support: Change to FileNotFoundError and + # remove check for ENOENT (FileNotFoundError "Corresponds to errno + # ENOENT" according to documentation) except OSError as err: if err.errno == errno.ENOENT: self.skipTest("'diff' command not present") diff --git a/subversion/bindings/swig/python/tests/mergeinfo.py b/subversion/bindings/swig/python/tests/mergeinfo.py index 873fc52be7fe8..739c0e53875d3 100644 --- a/subversion/bindings/swig/python/tests/mergeinfo.py +++ b/subversion/bindings/swig/python/tests/mergeinfo.py @@ -18,7 +18,7 @@ # under the License. # # -import unittest, os, sys, gc +import unittest, os, sys, weakref, gc from svn import core, repos, fs import utils @@ -125,6 +125,9 @@ def test_mergeinfo_get(self): } self.compare_mergeinfo_catalogs(mergeinfo, expected_mergeinfo) + @unittest.skipIf(utils.HAS_DEFERRED_REFCOUNT, + "Reference counting tests skipped because of deferred " + "reference counting") def test_mergeinfo_leakage__incorrect_range_t_refcounts(self): """Ensure that the ref counts on svn_merge_range_t objects returned by svn_mergeinfo_parse() are correct.""" @@ -138,7 +141,8 @@ def test_mergeinfo_leakage__incorrect_range_t_refcounts(self): # ....and now 3 (incref during iteration of each range object) refcount = sys.getrefcount(r) - # ....and finally, 4 (getrefcount() also increfs) + # ....and finally, 4 (getrefcount() also increfs, unless deferred + # reference counting) expected = 4 # Note: if path and index are not '/trunk' and 0 respectively, then @@ -150,8 +154,49 @@ def test_mergeinfo_leakage__incorrect_range_t_refcounts(self): "cause: incorrect Py_INCREF/Py_DECREF usage in libsvn_swig_py/" "swigutil_py.c." % (expected, refcount, path, i))) + def test_mergeinfo_leakage__incorrect_range_t_weakrefs(self): + """Ensure that the ref counts on svn_merge_range_t objects returned by + svn_mergeinfo_parse() are correct.""" + # When reference counting is working properly, each svn_merge_range_t in + # the returned mergeinfo will have a ref count of 1... + mergeinfo = core.svn_mergeinfo_parse(self.TEXT_MERGEINFO1) + merge_range_refdict = weakref.WeakValueDictionary() + merge_range_indexes = [] + n_merge_range = 0 + for (path, rangelist) in core._as_list(mergeinfo.items()): + # ....and now 2 (incref during iteration of rangelist) + + for (i, r) in enumerate(rangelist): + # ....and now 3 (incref during iteration of each range object) + + idx = (path, i) + merge_range_refdict[idx] = r + merge_range_indexes.append(idx) + n_merge_range += 1 + + # Note: if path and index are not '/trunk' and 0 respectively, then + # only some of the range objects are leaking, which is, as far as + # leaks go, even more impressive. + + del rangelist, r + gc.collect() + # Now (strong) reference count of all svn_merge_range_t should be 1 + # again and those objects should not be removed yet. + for idx in merge_range_indexes: + self.assertIn(idx, merge_range_refdict, ( + "Refarence count error on svn_merge_info_t object for " + "(path: %s, index: %d). It should still exists because " + "mergeinfo holds its reference, but after GC, it already " + "removed." % idx)) del mergeinfo gc.collect() + if merge_range_refdict: + # certainly memory leak, but we want to listing up leaked objects + # before raise an assertion error. + self.assertFalse(merge_range_refdict, + "Memory leak! All svn_merge_range_t object holded " + "by mergeinfo object should be removed, but at least " + "one object still alive.") def test_mergeinfo_leakage__lingering_range_t_objects_after_del(self): """Ensure that there are no svn_merge_range_t objects being tracked by @@ -162,6 +207,9 @@ def test_mergeinfo_leakage__lingering_range_t_objects_after_del(self): objects will be garbage collected and thus, not appear in the list of objects returned by gc.get_objects().""" mergeinfo = core.svn_mergeinfo_parse(self.TEXT_MERGEINFO1) + lingering = get_svn_merge_range_t_objects() + self.assertNotEqual(lingering, list()) + del lingering del mergeinfo gc.collect() lingering = get_svn_merge_range_t_objects() diff --git a/subversion/bindings/swig/python/tests/pool.py b/subversion/bindings/swig/python/tests/pool.py index bd67987ff39ab..a88b72e668ff1 100644 --- a/subversion/bindings/swig/python/tests/pool.py +++ b/subversion/bindings/swig/python/tests/pool.py @@ -19,10 +19,11 @@ # # import unittest, weakref, setup_path -import os, tempfile +import os, tempfile, gc import svn.core, svn.client, libsvn.core from svn.core import * from libsvn.core import application_pool, GenericSWIGWrapper +import utils # Test case for the new automatic pool management infrastructure @@ -208,6 +209,51 @@ def test_compatibility_layer(self): # We can still destroy and create pools at will svn_pool_destroy(svn_pool_create()) + def _test_pools_in_circular_reference(self, finalizer=False): + + class Circular(object): + + def __init__(self, pool): + self.pool = pool + self.loop = None + + if finalizer: + def __del__(self): + self.pool = self.loop = None + + def create_circularl(): + pool = Pool(libsvn.core.application_pool) + subpool1 = Pool(pool) + subpool2 = Pool(pool) + circularly1 = Circular(pool) + circularly2 = Circular(subpool2) + circularly3 = Circular(subpool1) + circularly1.loop = circularly3 + circularly2.loop = circularly1 + circularly3.loop = circularly2 + refs = weakref.WeakValueDictionary() + refs['pool'] = pool + refs['subpool1'] = subpool1 + refs['subpool2'] = subpool2 + return refs + + refs = create_circularl() + self.assertEqual({'pool', 'subpool1', 'subpool2'}, + set(name for name, pool in refs.items() + if pool is not None)) + gc.collect() + self.assertEqual(set(), set(name for name, pool in refs.items() + if pool is not None)) + + def test_pools_in_circular_reference_without_finalizer(self): + self._test_pools_in_circular_reference(finalizer=False) + + @unittest.skipIf(not utils.IS_PY3, + "Python 2 cannot collect garbage which involves circular " + "references with finalizer") + def test_pools_in_circular_reference_with_finalizer(self): + self._test_pools_in_circular_reference(finalizer=True) + def suite(): return unittest.defaultTestLoader.loadTestsFromTestCase(PoolTestCase) diff --git a/subversion/bindings/swig/python/tests/repository.py b/subversion/bindings/swig/python/tests/repository.py index abf5d72ab3c36..d28f1adfdaa32 100644 --- a/subversion/bindings/swig/python/tests/repository.py +++ b/subversion/bindings/swig/python/tests/repository.py @@ -18,11 +18,11 @@ # under the License. # # -import unittest, setup_path, os, sys +import unittest, setup_path, os, sys, weakref from sys import version_info # For Python version check from io import BytesIO from svn import core, repos, fs, delta -from svn.core import SubversionException +from svn.core import SubversionException, Pool import utils class ChangeReceiver(delta.Editor): @@ -40,9 +40,20 @@ def textdelta_handler(textdelta): return textdelta_handler class DumpStreamParser(repos.ParseFns3): - def __init__(self): + def __init__(self, stream=None, pool=None): repos.ParseFns3.__init__(self) + self.stream = stream self.ops = [] + # for leak checking only. If the parse_fns3 object holds some proxy + # object allocated from 'pool' or the 'pool' itself, the 'pool' is not + # destroyed until the parse_fns3 object is removed. + self.pool = pool + def _close_dumpstream(self): + if self.stream: + self.stream.close() + self.stream = None + if self.pool: + self.pool = None def magic_header_record(self, version, pool=None): self.ops.append((b"magic-header", version)) def uuid_record(self, uuid, pool=None): @@ -74,6 +85,93 @@ def set_fulltext(self, node_baton): self.ops.append((b"set-fulltext", node_baton[0], node_baton[1])) return None +class BatonCollector(repos.ChangeCollector): + """A ChangeCollector with collecting batons, too""" + + def __init__(self, fs_ptr, root, pool=None, notify_cb=None): + + def get_expected_baton_refcount(): + """determine expected refcount of batons within a batoun_tuple, + by using dumy object""" + self.open_root(-1, None) + for baton_tuple in self.batons: + rc = sys.getrefcount(baton_tuple[2]) + break + return rc + + repos.ChangeCollector.__init__(self, fs_ptr, root, pool, notify_cb) + self.close_called = False + self.abort_called = False + # temporary values for get_expected_baton_refcount + self.batons = [] + self.expected_baton_refcount = 0 + # determin expected_baton_refcount + self.expected_baton_refcount = get_expected_baton_refcount() + # re-initialize the values after calling get_expected_baton_refcount() + self.batons = [] + + def open_root(self, base_revision, dir_pool=None): + bt = repos.ChangeCollector.open_root(self, base_revision, dir_pool) + self.batons.append((b'dir baton', b'', bt, self.expected_baton_refcount)) + return bt + + def add_directory(self, path, parent_baton, + copyfrom_path, copyfrom_revision, dir_pool=None): + bt = repos.ChangeCollector.add_directory(self, path, parent_baton, + copyfrom_path, + copyfrom_revision, + dir_pool) + self.batons.append((b'dir baton', path, bt, self.expected_baton_refcount)) + return bt + + def open_directory(self, path, parent_baton, base_revision, + dir_pool=None): + bt = repos.ChangeCollector.open_directory(self, path, parent_baton, + base_revision, dir_pool) + self.batons.append((b'dir baton', path, bt, self.expected_baton_refcount)) + return bt + + def add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, file_pool=None): + bt = repos.ChangeCollector.add_file(self, path, parent_baton, + copyfrom_path, copyfrom_revision, + file_pool) + self.batons.append((b'file baton', path, bt, self.expected_baton_refcount)) + return bt + + def open_file(self, path, parent_baton, base_revision, file_pool=None): + bt = repos.ChangeCollector.open_file(self, path, parent_baton, + base_revision, file_pool) + self.batons.append((b'file baton', path, bt, self.expected_baton_refcount)) + return bt + + def close_edit(self, pool=None): + self.close_called = True + return + + def abort_edit(self, pool=None): + self.abort_called = True + return + +class BatonCollectorErrorOnClose(BatonCollector): + """Same as BatonCollector, but raises an Exception when close the + file/dir specfied by error_path""" + def __init__(self, fs_ptr, root, pool=None, notify_cb=None, error_path=b''): + BatonCollector.__init__(self, fs_ptr, root, pool, notify_cb) + self.error_path = error_path + + def close_directory(self, dir_baton): + if dir_baton[0] == self.error_path: + raise SubversionException('A Dummy Exception!', core.SVN_ERR_BASE) + else: + BatonCollector.close_directory(self, dir_baton) + + def close_file(self, file_baton, text_checksum): + if file_baton[0] == self.error_path: + raise SubversionException('A Dummy Exception!', core.SVN_ERR_BASE) + else: + return BatonCollector.close_file(self, file_baton, text_checksum) + def _authz_callback(root, path, pool): "A dummy authz callback which always returns success." @@ -175,13 +273,15 @@ def test_parse_fns3(self): def is_cancelled(): self.cancel_calls += 1 return None + pool = Pool() + subpool = Pool(pool) dump_path = os.path.join(os.path.dirname(sys.argv[0]), "trac/versioncontrol/tests/svnrepos.dump") stream = open(dump_path, 'rb') - dsp = DumpStreamParser() - ptr, baton = repos.make_parse_fns3(dsp) + dsp = DumpStreamParser(stream, subpool) + dsp_ref = weakref.ref(dsp) + ptr, baton = repos.make_parse_fns3(dsp, subpool) repos.parse_dumpstream3(stream, ptr, baton, False, is_cancelled) - stream.close() self.assertEqual(self.cancel_calls, 76) expected_list = [ (b"magic-header", 2), @@ -226,6 +326,13 @@ def is_cancelled(): # the comparison list gets too long. self.assertEqual(dsp.ops[:len(expected_list)], expected_list) + # _close_dumpstream should be invoked after 'baton' is removed. + self.assertEqual(False, stream.closed) + del ptr, baton, subpool, dsp + self.assertEqual(True, stream.closed) + # Issue SVN-4918 + self.assertEqual(None, dsp_ref()) + def test_parse_fns3_invalid_set_fulltext(self): class DumpStreamParserSubclass(DumpStreamParser): def set_fulltext(self, node_baton): @@ -241,6 +348,33 @@ def set_fulltext(self, node_baton): finally: stream.close() + def test_parse_fns3_apply_textdelta_handler_refcount(self): + handler = lambda node_baton: None + handler_ref = weakref.ref(handler) + + class ParseFns3(repos.ParseFns3): + def __init__(self, handler): + self.called = set() + self.handler = handler + def apply_textdelta(self, node_baton): + self.called.add('apply_textdelta') + return self.handler + + dumpfile = os.path.join(os.path.dirname(__file__), 'data', + 'repository-deltas.dump') + pool = Pool() + subpool = Pool(pool) + parser = ParseFns3(handler) + ptr, baton = repos.make_parse_fns3(parser, subpool) + with open(dumpfile, "rb") as stream: + repos.parse_dumpstream3(stream, ptr, baton, False, None) + del ptr, baton, stream + + self.assertIn('apply_textdelta', parser.called) + self.assertNotEqual(None, handler_ref()) + del parser, handler, subpool, ParseFns3 + self.assertEqual(None, handler_ref()) + def test_get_logs(self): """Test scope of get_logs callbacks""" logs = [] @@ -290,6 +424,83 @@ def test_unnamed_editor(self): repos.dir_delta(prev_root, b'', b'', this_root, b'', e_ptr, e_baton, _authz_callback, 1, 1, 0, 0) + def test_delta_editor_leak_with_change_collector(self): + pool = Pool() + subpool = Pool(pool) + root = fs.revision_root(self.fs, self.rev, subpool) + editor = repos.ChangeCollector(self.fs, root, subpool) + editor_ref = weakref.ref(editor) + e_ptr, e_baton = delta.make_editor(editor, subpool) + repos.replay(root, e_ptr, e_baton, subpool) + + fs.close_root(root) + del root + self.assertNotEqual(None, editor_ref()) + + del e_ptr, e_baton, editor + del subpool + self.assertEqual(None, editor_ref()) + + def test_replay_batons_refcounts(self): + """Issue SVN-4917: check ref-count of batons created and used in callbacks""" + root = fs.revision_root(self.fs, self.rev) + editor = BatonCollector(self.fs, root) + e_ptr, e_baton = delta.make_editor(editor) + refcount_at_first = sys.getrefcount(e_ptr) + repos.replay(root, e_ptr, e_baton) + for baton_tuple in editor.batons: + # baton_tuple: 4-tuple(baton_type: bytes, node: bytes, bt: baton, + # expected_refcount_of_bt: int) + self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3], + "leak on baton %s after replay without errors" + % repr(baton_tuple)) + del e_baton + self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first, + "leak on editor baton after replay without errors") + + editor = BatonCollectorErrorOnClose(self.fs, root, + error_path=b'branches/v1x') + e_ptr, e_baton = delta.make_editor(editor) + refcount_at_first = sys.getrefcount(e_ptr) + self.assertRaises(SubversionException, repos.replay, root, e_ptr, e_baton) + batons = editor.batons + # As svn_repos_replay calls neither close_edit callback nor abort_edit + # if an error has occured during processing, references of Python objects + # in decendant batons may live until e_baton is deleted. + del e_baton + for baton_tuple in batons: + self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3], + "leak on baton %s after replay with an error" + % repr(baton_tuple)) + self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first, + "leak on editor baton after replay with an error") + + def test_delta_editor_apply_textdelta_handler_refcount(self): + handler = lambda textdelta: None + handler_ref = weakref.ref(handler) + + class Editor(delta.Editor): + def __init__(self, handler): + self.called = set() + self.handler = handler + def apply_textdelta(self, file_baton, base_checksum, pool=None): + self.called.add('apply_textdelta') + return self.handler + + pool = Pool() + subpool = Pool(pool) + root = fs.revision_root(self.fs, 3) # change of trunk/README.txt + editor = Editor(handler) + e_ptr, e_baton = delta.make_editor(editor, subpool) + repos.replay(root, e_ptr, e_baton, subpool) + del e_ptr, e_baton + + self.assertIn('apply_textdelta', editor.called) + self.assertNotEqual(None, handler_ref()) + del root, editor, handler, Editor + del subpool + self.assertEqual(None, handler_ref()) + def test_retrieve_and_change_rev_prop(self): """Test playing with revprops""" self.assertEqual(repos.fs_revision_prop(self.repos, self.rev, b"svn:log", diff --git a/subversion/bindings/swig/python/tests/typemap.py b/subversion/bindings/swig/python/tests/typemap.py index 7f6e839176cb7..26ee5cd6d3517 100644 --- a/subversion/bindings/swig/python/tests/typemap.py +++ b/subversion/bindings/swig/python/tests/typemap.py @@ -23,6 +23,7 @@ import tempfile import svn.core +import utils class SubversionTypemapTestCase(unittest.TestCase): """Test cases for the SWIG typemaps argments and return values transration""" @@ -47,6 +48,8 @@ def test_char_ptr_in(self): "svn_dirent_join() argument component must be" " bytes or str, not int") + @unittest.skipIf(not utils.IS_PY3 and utils.is_defaultencoding_utf8(), + "'utf-8' codecs of Python 2 accepts any unicode strings") def test_char_ptr_in_unicode_exception(self): """Check %typemap(in) IN_STRING handles unicode encode error correctly""" with self.assertRaises(UnicodeEncodeError): @@ -67,6 +70,8 @@ def test_char_ptr_may_be_null(self): " must be bytes or str or None, not %s" % self.__class__.__name__) + @unittest.skipIf(not utils.IS_PY3 and utils.is_defaultencoding_utf8(), + "'utf-8' codecs of Python 2 accepts any unicode strings") def test_char_ptr_may_be_null_unicode_exception(self): """Check %typemap(in) char *MAY_BE_NULL handles unicode encode error correctly""" cfg = svn.core.svn_config_create2(False, False) @@ -101,6 +106,8 @@ def test_prophash_from_dict_null_value(self): self.assertEqual(svn.core.svn_prop_diffs(target_props, source_props), expected) + @unittest.skipIf(not utils.IS_PY3 and utils.is_defaultencoding_utf8(), + "'utf-8' codecs of Python 2 accepts any unicode strings") def test_make_string_from_ob_unicode_exception(self): """Check make_string_from_ob handles unicode encode error correctly""" source_props = { b'a' : b'foo', @@ -112,6 +119,8 @@ def test_make_string_from_ob_unicode_exception(self): with self.assertRaises(UnicodeEncodeError): svn.core.svn_prop_diffs(target_props, source_props) + @unittest.skipIf(not utils.IS_PY3 and utils.is_defaultencoding_utf8(), + "'utf-8' codecs of Python 2 accepts any unicode strings") def test_make_svn_string_from_ob_unicode_exception(self): """Check make_string_from_ob handles unicode encode error correctly""" source_props = { b'a' : b'foo', diff --git a/subversion/bindings/swig/python/tests/utils.py b/subversion/bindings/swig/python/tests/utils.py index c8b7e9da00d20..3e2999263a5c3 100644 --- a/subversion/bindings/swig/python/tests/utils.py +++ b/subversion/bindings/swig/python/tests/utils.py @@ -18,7 +18,7 @@ # under the License. # # -import os.path, sys, tempfile +import os.path, sys, tempfile, codecs from svn import core, repos from io import BytesIO try: @@ -28,6 +28,8 @@ # Python <3.0 from urllib import pathname2url +IS_PY3 = sys.hexversion >= 0x3000000 + class Temper(object): """Class to simplify allocation and cleanup of dummy Subversion structures, such as repositories and working copies.""" @@ -87,3 +89,19 @@ def file_uri_for_path(path): # it returns both the authority and path parts for no reason, which # means we have to trim the leading slashes to "normalize" the result. return b'file:///' + uri_path.lstrip(b'/') + +def codecs_eq(a, b): + return codecs.lookup(a) == codecs.lookup(b) + +def is_defaultencoding_utf8(): + return codecs_eq(sys.getdefaultencoding(), 'utf-8') + +def get_holded_refcount_by_getrefcount(): + "get refcount holded by sys.getrefcount() if its arg is a local variable" + a = [] + rv = sys.getrefcount(a) - 1 + return rv + +HAS_DEFERRED_REFCOUNT = not get_holded_refcount_by_getrefcount() + +del get_holded_refcount_by_getrefcount diff --git a/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c b/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c index a25ec5a6ef86a..58145da3a685a 100644 --- a/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c +++ b/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb.c @@ -695,6 +695,12 @@ rb_set_pool(VALUE self, VALUE pool) return Qnil; } +static VALUE +rb_set_pool_callback(RB_BLOCK_CALL_FUNC_ARGLIST(self, pool)) +{ + return rb_set_pool(self, pool); +} + static VALUE rb_pool_new(VALUE parent) { @@ -760,9 +766,10 @@ struct rb_set_pool_for_hash_arg { }; static int -rb_set_pool_for_hash_callback(VALUE key, VALUE value, - struct rb_set_pool_for_hash_arg *arg) +rb_set_pool_for_hash_callback(VALUE key, VALUE value, VALUE hash_arg) { + struct rb_set_pool_for_hash_arg *arg; + arg = (struct rb_set_pool_for_hash_arg *) hash_arg; if (svn_swig_rb_set_pool(value, arg->pool)) arg->set = TRUE; return ST_CONTINUE; @@ -806,7 +813,7 @@ svn_swig_rb_set_pool_for_no_swig_type(VALUE target, VALUE pool) target = rb_ary_new3(1, target); } - rb_iterate(rb_each, target, rb_set_pool, pool); + rb_iterate(rb_each, target, rb_set_pool_callback, pool); } void @@ -1028,9 +1035,10 @@ typedef struct prop_hash_each_arg_t { } prop_hash_each_arg_t; static int -svn_swig_rb_to_apr_array_row_prop_callback(VALUE key, VALUE value, - prop_hash_each_arg_t *arg) +svn_swig_rb_to_apr_array_row_prop_callback(VALUE key, VALUE value, VALUE + prop_arg) { + prop_hash_each_arg_t *arg = (prop_hash_each_arg_t *) prop_arg; svn_prop_t *prop; prop = apr_array_push(arg->array); @@ -1070,7 +1078,8 @@ svn_swig_rb_to_apr_array_row_prop(VALUE array_or_hash, apr_pool_t *pool) result = apr_array_make(pool, 0, sizeof(svn_prop_t)); arg.array = result; arg.pool = pool; - rb_hash_foreach(array_or_hash, svn_swig_rb_to_apr_array_row_prop_callback, + rb_hash_foreach(array_or_hash, + svn_swig_rb_to_apr_array_row_prop_callback, (VALUE)&arg); return result; } else { @@ -1081,9 +1090,9 @@ svn_swig_rb_to_apr_array_row_prop(VALUE array_or_hash, apr_pool_t *pool) } static int -svn_swig_rb_to_apr_array_prop_callback(VALUE key, VALUE value, - prop_hash_each_arg_t *arg) +svn_swig_rb_to_apr_array_prop_callback(VALUE key, VALUE value, VALUE prop_arg) { + prop_hash_each_arg_t *arg = (prop_hash_each_arg_t *) prop_arg; svn_prop_t *prop; prop = apr_palloc(arg->pool, sizeof(svn_prop_t)); @@ -1125,7 +1134,8 @@ svn_swig_rb_to_apr_array_prop(VALUE array_or_hash, apr_pool_t *pool) result = apr_array_make(pool, 0, sizeof(svn_prop_t *)); arg.array = result; arg.pool = pool; - rb_hash_foreach(array_or_hash, svn_swig_rb_to_apr_array_prop_callback, + rb_hash_foreach(array_or_hash, + svn_swig_rb_to_apr_array_prop_callback, (VALUE)&arg); return result; } else { @@ -1523,8 +1533,9 @@ svn_swig_rb_apr_revnum_key_hash_to_hash_string(apr_hash_t *hash) /* Ruby Hash -> apr_hash_t */ static int -r2c_hash_i(VALUE key, VALUE value, hash_to_apr_hash_data_t *data) +r2c_hash_i(VALUE key, VALUE value, VALUE data_arg) { + hash_to_apr_hash_data_t *data = (hash_to_apr_hash_data_t *) data_arg; if (key != Qundef) { void *val = data->func(value, data->ctx, data->pool); svn_hash_sets(data->apr_hash, apr_pstrdup(data->pool, StringValuePtr(key)), @@ -1570,7 +1581,9 @@ svn_swig_rb_hash_to_apr_hash_svn_string(VALUE hash, apr_pool_t *pool) apr_hash_t * svn_swig_rb_hash_to_apr_hash_swig_type(VALUE hash, const char *typename, apr_pool_t *pool) { - return r2c_hash(hash, r2c_swig_type, (void *)typename, pool); + /* Note: casting to r2c_cunc for r2c_swig_type may unsafe, because + it contains the cast from "const void *" to "void *" */ + return r2c_hash(hash, (r2c_func)r2c_swig_type, (void *)typename, pool); } apr_hash_t * @@ -1605,7 +1618,7 @@ typedef struct callback_handle_error_baton_t { } callback_handle_error_baton_t; static VALUE -callback(VALUE baton, ...) +callback(VALUE baton) { callback_baton_t *cbb = (callback_baton_t *)baton; VALUE result; @@ -1617,7 +1630,7 @@ callback(VALUE baton, ...) } static VALUE -callback_rescue(VALUE baton, ...) +callback_rescue(VALUE baton, VALUE unused) { callback_rescue_baton_t *rescue_baton = (callback_rescue_baton_t*)baton; @@ -1634,7 +1647,7 @@ callback_rescue(VALUE baton, ...) } static VALUE -callback_ensure(VALUE pool, ...) +callback_ensure(VALUE pool) { svn_swig_rb_pop_pool(pool); @@ -1655,7 +1668,7 @@ invoke_callback(VALUE baton, VALUE pool) } static VALUE -callback_handle_error(VALUE baton, ...) +callback_handle_error(VALUE baton) { callback_handle_error_baton_t *handle_error_baton; handle_error_baton = (callback_handle_error_baton_t *)baton; @@ -1715,7 +1728,7 @@ make_baton(apr_pool_t *pool, VALUE editor, VALUE baton) } static VALUE -add_baton_if_delta_editor(VALUE target, VALUE baton) +add_baton_if_delta_editor(RB_BLOCK_CALL_FUNC_ARGLIST(target, baton)) { if (RTEST(rb_obj_is_kind_of(target, svn_swig_rb_svn_delta_editor()))) { add_baton(target, baton); diff --git a/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb__pre_ruby.h b/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb__pre_ruby.h index bc8eb6e0a1df5..97bfeac128e07 100644 --- a/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb__pre_ruby.h +++ b/subversion/bindings/swig/ruby/libsvn_swig_ruby/swigutil_rb__pre_ruby.h @@ -57,7 +57,9 @@ typedef int gid_t; #if !defined(__cplusplus) && !defined(inline) #define inline __inline #endif -typedef long ssize_t; +#ifndef HAVE_SSIZE_T +typedef apr_ssize_t ssize_t; +#endif /* Don't define iovec when including APR */ #define APR_IOVEC_DEFINED @@ -92,6 +94,10 @@ typedef unsigned __int64 uint64_t; #ifdef _MSC_VER #pragma warning(disable: 4702) /* warning C4702: unreachable code */ +#if SWIG_VERSION < 0x040002 +/* Prevent C4113 "X differs in parameter lists from Y" errors */ +# pragma warning(default : 4113) +#endif #endif #endif /* defined(SVN_SWIG_RUBY__CUSTOM_RUBY_CONFIG) && defined(_MSC_VER) */ diff --git a/subversion/bindings/swig/ruby/svn/client.rb b/subversion/bindings/swig/ruby/svn/client.rb index 50a0385592eb1..30d328bcd895f 100644 --- a/subversion/bindings/swig/ruby/svn/client.rb +++ b/subversion/bindings/swig/ruby/svn/client.rb @@ -637,25 +637,25 @@ def switch(path, uri, peg_rev=nil, rev=nil, depth=nil, ignore_externals, allow_unver_obstruction, self) end - def set_log_msg_func(callback=Proc.new) + def set_log_msg_func(&callback) callback_wrapper = Proc.new do |items| items = items.collect do |item| item_wrapper = CommitItemWrapper.new(item) end callback.call(items) end - set_log_msg_func2(callback_wrapper) + set_log_msg_func2(&callback_wrapper) end - def set_log_msg_func2(callback=Proc.new) + def set_log_msg_func2(&callback) @log_msg_baton = Client.set_log_msg_func3(self, callback) end - def set_notify_func(callback=Proc.new) + def set_notify_func(&callback) @notify_baton = Client.set_notify_func2(self, callback) end - def set_cancel_func(callback=Proc.new) + def set_cancel_func(&callback) @cancel_baton = Client.set_cancel_func(self, callback) end @@ -707,9 +707,9 @@ def remove_from_changelists(changelists_names, paths, depth=nil) private def init_callbacks - set_log_msg_func(nil) - set_notify_func(nil) - set_cancel_func(nil) + set_log_msg_func + set_notify_func + set_cancel_func end %w(log_msg notify cancel).each do |type| private "#{type}_func", "#{type}_baton" diff --git a/subversion/bindings/swig/ruby/svn/core.rb b/subversion/bindings/swig/ruby/svn/core.rb index 15ebe139b6a2d..ff32926066cc0 100644 --- a/subversion/bindings/swig/ruby/svn/core.rb +++ b/subversion/bindings/swig/ruby/svn/core.rb @@ -249,31 +249,31 @@ def add_windows_ssl_server_trust_provider end end - def add_simple_prompt_provider(retry_limit, prompt=Proc.new) + def add_simple_prompt_provider(retry_limit, &prompt) args = [retry_limit] klass = AuthCredSimple add_prompt_provider("simple", args, prompt, klass) end - def add_username_prompt_provider(retry_limit, prompt=Proc.new) + def add_username_prompt_provider(retry_limit, &prompt) args = [retry_limit] klass = AuthCredUsername add_prompt_provider("username", args, prompt, klass) end - def add_ssl_server_trust_prompt_provider(prompt=Proc.new) + def add_ssl_server_trust_prompt_provider(&prompt) args = [] klass = AuthCredSSLServerTrust add_prompt_provider("ssl_server_trust", args, prompt, klass) end - def add_ssl_client_cert_prompt_provider(retry_limit, prompt=Proc.new) + def add_ssl_client_cert_prompt_provider(retry_limit, &prompt) args = [retry_limit] klass = AuthCredSSLClientCert add_prompt_provider("ssl_client_cert", args, prompt, klass) end - def add_ssl_client_cert_pw_prompt_provider(retry_limit, prompt=Proc.new) + def add_ssl_client_cert_pw_prompt_provider(retry_limit, &prompt) args = [retry_limit] klass = AuthCredSSLClientCertPw add_prompt_provider("ssl_client_cert_pw", args, prompt, klass) @@ -812,7 +812,7 @@ def initialize(*ranges) def diff(to, consider_inheritance=false) result = Core.rangelist_diff(self, to, consider_inheritance) deleted = result.pop - added = result + added = result.pop [added, deleted].collect do |result| self.class.new(*result) end diff --git a/subversion/bindings/swig/ruby/svn/util.rb b/subversion/bindings/swig/ruby/svn/util.rb index d409b984c2761..5f250be4d5d46 100644 --- a/subversion/bindings/swig/ruby/svn/util.rb +++ b/subversion/bindings/swig/ruby/svn/util.rb @@ -17,10 +17,10 @@ # under the License. # ==================================================================== -if /cygwin|mingw|mswin32|bccwin32/.match(RUBY_PLATFORM) +if /cygwin|mingw|mswin|bccwin32/.match(RUBY_PLATFORM) $LOAD_PATH.each do |load_path| svn_ext_path = File.join(load_path, "svn", "ext") - if File.exists?(svn_ext_path) + if File.exist?(svn_ext_path) svn_ext_path_win = File.expand_path(svn_ext_path) svn_ext_path_win = svn_ext_path.gsub(File::SEPARATOR, File::ALT_SEPARATOR) unless ENV["PATH"].split(";").find {|path| path == svn_ext_path_win} @@ -122,7 +122,7 @@ def filename_to_temp_file(filename) end def reset_message_directory - if /cygwin|mingw|mswin32|bccwin32/.match(RUBY_PLATFORM) + if /cygwin|mingw|mswin|bccwin32/.match(RUBY_PLATFORM) top_directory = File.join(File.dirname(__FILE__), "..", "..") top_directory = File.expand_path(top_directory) locale_directory = File.join(top_directory, "share", "locale") @@ -147,7 +147,7 @@ def validate_options(options, optional_keys, required_keys=[]) end def windows? - /cygwin|mingw|mswin32|bccwin32/.match(RUBY_PLATFORM) + /cygwin|mingw|mswin|bccwin32/.match(RUBY_PLATFORM) end end end diff --git a/subversion/bindings/swig/ruby/test/test_fs.rb b/subversion/bindings/swig/ruby/test/test_fs.rb index 8808055eec0a9..fa8f409c5f64f 100644 --- a/subversion/bindings/swig/ruby/test/test_fs.rb +++ b/subversion/bindings/swig/ruby/test/test_fs.rb @@ -219,18 +219,14 @@ def test_transaction end start_time = Time.now + sleep 0.032r if Svn::Util::windows? txn1 = @fs.transaction assert_equal([Svn::Core::PROP_REVISION_DATE], txn1.proplist.keys) assert_instance_of(Time, txn1.proplist[Svn::Core::PROP_REVISION_DATE]) date = txn1.prop(Svn::Core::PROP_REVISION_DATE) - # Subversion's clock is more precise than Ruby's on - # Windows. So this test can fail intermittently because - # the begin and end of the range are the same (to 3 - # decimal places), but the time from Subversion has 6 - # decimal places so it looks like it's not in the range. - # So we just add a smidgen to the end of the Range. - assert_operator(start_time..(Time.now + 0.001), :include?, date) + sleep 0.032r if Svn::Util::windows? + assert_operator(start_time..Time.now, :include?, date) txn1.set_prop(Svn::Core::PROP_REVISION_DATE, nil) assert_equal([], txn1.proplist.keys) assert_equal(youngest_rev, txn1.base_revision) @@ -420,10 +416,12 @@ def test_prop ctx.mkdir(["#{@wc_path}/new_dir"]) start_time = Time.now + sleep 0.032r if Svn::Util::windows? info = ctx.commit([@wc_path]) assert_equal(@author, info.author) assert_equal(@fs.youngest_rev, info.revision) + sleep 0.032r if Svn::Util::windows? assert_operator(start_time..(Time.now), :include?, info.date) assert_equal(@author, @fs.prop(Svn::Core::PROP_REVISION_AUTHOR)) diff --git a/subversion/bindings/swig/ruby/test/test_repos.rb b/subversion/bindings/swig/ruby/test/test_repos.rb index 1c0c366e627a5..5d0ba49679095 100644 --- a/subversion/bindings/swig/ruby/test/test_repos.rb +++ b/subversion/bindings/swig/ruby/test/test_repos.rb @@ -279,6 +279,7 @@ def assert_transaction assert_equal(prev_rev, @repos.youngest_rev) assert_equal(prev_rev, @repos.dated_revision(past_date)) + sleep 0.032r if Svn::Util::windows? prev_rev = @repos.youngest_rev @repos.transaction_for_commit(@author, log) do |txn| end diff --git a/subversion/bindings/swig/ruby/test/test_wc.rb b/subversion/bindings/swig/ruby/test/test_wc.rb index 763cbf6611c1b..6548d83ee978b 100644 --- a/subversion/bindings/swig/ruby/test/test_wc.rb +++ b/subversion/bindings/swig/ruby/test/test_wc.rb @@ -342,11 +342,11 @@ def callbacks.handle_error(path, err) def test_adm_ensure adm_dir = Dir.glob(File.join(@wc_path, "{.,_}svn")).first - assert(File.exists?(adm_dir)) + assert(File.exist?(adm_dir)) FileUtils.rm_rf(adm_dir) - assert(!File.exists?(adm_dir)) + assert(!File.exist?(adm_dir)) Svn::Wc.ensure_adm(@wc_path, @fs.uuid, @repos_uri, @repos_uri, 0) - assert(File.exists?(adm_dir)) + assert(File.exist?(adm_dir)) end def test_merge @@ -474,19 +474,19 @@ def test_delete ctx.add(path) ctx.ci(@wc_path).revision - assert(File.exists?(path)) + assert(File.exist?(path)) Svn::Wc::AdmAccess.open(nil, @wc_path, true, 5) do |access| access.delete(path) end - assert(!File.exists?(path)) + assert(!File.exist?(path)) ctx.revert(path) - assert(File.exists?(path)) + assert(File.exist?(path)) Svn::Wc::AdmAccess.open(nil, @wc_path, true, 5) do |access| access.delete(path, nil, nil, true) end - assert(File.exists?(path)) + assert(File.exist?(path)) end end @@ -808,9 +808,9 @@ def test_update_editor ctx.add(path2) rev2 = ctx.commit(@wc_path).revision - assert(File.exists?(path2)) + assert(File.exist?(path2)) assert_equal(0, ctx.up(@wc_path, 0)) - assert(!File.exists?(path2)) + assert(!File.exist?(path2)) Svn::Wc::AdmAccess.open(nil, @wc_path) do |access| editor = access.update_editor('', 0) assert_equal(0, editor.target_revision) @@ -848,9 +848,9 @@ def test_update_editor_options ctx.add(path2) rev2 = ctx.commit(@wc_path).revision - assert(File.exists?(path2)) + assert(File.exist?(path2)) assert_equal(0, ctx.up(@wc_path, 0)) - assert(!File.exists?(path2)) + assert(!File.exist?(path2)) notification_count = 0 Svn::Wc::AdmAccess.open(nil, @wc_path) do |access| notify_func = Proc.new {|n| notification_count += 1} @@ -946,9 +946,9 @@ def test_switch_editor ctx.add(dir2_path) rev2 = ctx.commit(@wc_path).revision - assert(File.exists?(path1)) + assert(File.exist?(path1)) assert_equal(rev2, ctx.switch(@wc_path, dir2_uri)) - assert(File.exists?(File.join(@wc_path, file2))) + assert(File.exist?(File.join(@wc_path, file2))) Svn::Wc::AdmAccess.open_anchor(@wc_path) do |access, dir_access, target| editor = dir_access.switch_editor('', dir1_uri, rev2) assert_equal(rev2, editor.target_revision) diff --git a/subversion/bindings/swig/ruby/test/windows_util.rb b/subversion/bindings/swig/ruby/test/windows_util.rb index 726527a983805..6681a904a5e45 100644 --- a/subversion/bindings/swig/ruby/test/windows_util.rb +++ b/subversion/bindings/swig/ruby/test/windows_util.rb @@ -38,30 +38,25 @@ def setup_svnserve @svnserve_port = @svnserve_ports.last @repos_svnserve_uri = "svn://#{@svnserve_host}:#{@svnserve_port}" - @@service_created ||= begin - @@service_created = true + top_directory = File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..") + build_type = ENV["BUILD_TYPE"] || "Release" + svnserve_path = File.join(top_directory, build_type, 'subversion', 'svnserve', 'svnserve.exe') + svnserve_path = Svnserve.escape_value(svnserve_path) - top_directory = File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..") - build_type = ENV["BUILD_TYPE"] || "Release" - svnserve_path = File.join(top_directory, build_type, 'subversion', 'svnserve', 'svnserve.exe') - svnserve_path = Svnserve.escape_value(svnserve_path) + root = @full_repos_path.tr(File::SEPARATOR, File::ALT_SEPARATOR) + FileUtils.mkdir_p(root) - root = @full_repos_path.tr(File::SEPARATOR, File::ALT_SEPARATOR) - FileUtils.mkdir_p(root) + IO.popen("#{svnserve_path} -d -r #{Svnserve.escape_value(root)} --listen-host #{@svnserve_host} --listen-port #{@svnserve_port} --pid-file #{@svnserve_pid_file}") - IO.popen("#{svnserve_path} -d -r #{Svnserve.escape_value(root)} --listen-host #{@svnserve_host} --listen-port #{@svnserve_port} --pid-file #{@svnserve_pid_file}") - user = ENV["USERNAME"] || Etc.getlogin + # Give svnserve a bit of time to start + sleep 1 - # Give svnserve a bit of time to start - sleep 1 - end true end def teardown_svnserve - # TODO: - # Load @svnserve_pid_file - # Kill process + pid = File.read(@svnserve_pid_file).to_i + Process.kill(:KILL, pid) end def add_pre_revprop_change_hook @@ -115,7 +110,7 @@ def gen_make_opts lines = [] gen_make_opts = File.join(@@top_dir, "gen-make.opts") lines = - File.read(gen_make_opts).lines.to_a if File.exists?(gen_make_opts) + File.read(gen_make_opts).lines.to_a if File.exist?(gen_make_opts) config = Hash.new do |hash, key| if /^--with-(.*)$/ =~ key hash[key] = File.join(@@top_dir, $1) diff --git a/subversion/bindings/swig/svn_client.i b/subversion/bindings/swig/svn_client.i index 7f4dead9ff0fc..2ab84bc70659d 100644 --- a/subversion/bindings/swig/svn_client.i +++ b/subversion/bindings/swig/svn_client.i @@ -21,15 +21,16 @@ * svn_client.i: SWIG interface file for svn_client.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") client +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) client #elif defined(SWIGPERL) %module "SVN::_Client" #elif defined(SWIGRUBY) %module "svn::ext::client" #endif -%include svn_global.swg %import core.i %import svn_delta.i %import svn_wc.i diff --git a/subversion/bindings/swig/svn_delta.i b/subversion/bindings/swig/svn_delta.i index f69b76e0c1e81..cd589218dc2ef 100644 --- a/subversion/bindings/swig/svn_delta.i +++ b/subversion/bindings/swig/svn_delta.i @@ -21,15 +21,16 @@ * svn_delta.i: SWIG interface file for svn_delta.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") delta +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) delta #elif defined(SWIGPERL) %module "SVN::_Delta" #elif defined(SWIGRUBY) %module "svn::ext::delta" #endif -%include svn_global.swg %import core.i #ifdef SWIGRUBY @@ -67,8 +68,6 @@ ### There must be a cleaner way to implement this? ### Maybe follow Ruby by wrapping it where passing an editor? */ void svn_swig_py_make_editor(const svn_delta_editor_t **editor, - void **edit_baton, - PyObject *py_editor, apr_pool_t *pool); #endif @@ -206,6 +205,50 @@ void _ops_get(int *num_ops, const svn_txdelta_op_t **ops) #ifdef SWIGPYTHON %pythoncode %{ +# Baton container class for editor/parse_fns3 batons and their decendants. +class _ItemBaton: + def __init__(self, editor, pool, baton=None): + import libsvn.core + self.pool = pool if pool else libsvn.core.svn_pool_create() + self.baton = baton + self.editor = editor + + def get_ancestor(self): + raise NotImplementedError + + def make_decendant(self, pool, baton=None): + return _DecBaton(self, pool, baton) + + +class _DecBaton(_ItemBaton): + def __init__(self, parent, pool, baton=None): + import weakref + _ItemBaton.__init__(self, parent.editor, pool, baton) + self._anc = weakref.ref(parent.get_ancestor()) + self._anc().hold_baton(self) + + def get_ancestor(self): + return self._anc() + + def release_self(self): + self._anc().release_baton(self) + + +class _AncBaton(_ItemBaton): + def __init__(self, editor, pool, baton=None): + _ItemBaton.__init__(self, editor, pool, baton) + self._dec = {} # hold decendant batons. + + def get_ancestor(self): + return self + + def hold_baton(self, baton): + self._dec[id(baton)] = baton + + def release_baton(self, baton): + del self._dec[id(baton)] + + # 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 @@ -231,7 +274,7 @@ svn_swig_rb_delta_editor_get_target_revision(VALUE editor) if (NIL_P(rb_target_address)) return Qnil; - target_address = (svn_revnum_t *)(NUM2LONG(rb_target_address)); + target_address = (svn_revnum_t *)(NUM2SWIG(rb_target_address)); if (!target_address) return Qnil; diff --git a/subversion/bindings/swig/svn_diff.i b/subversion/bindings/swig/svn_diff.i index 81439cbee60d2..a52973b8a7c6c 100644 --- a/subversion/bindings/swig/svn_diff.i +++ b/subversion/bindings/swig/svn_diff.i @@ -21,15 +21,16 @@ * svn_diff.i: SWIG interface file for svn_diff.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") diff +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) diff #elif defined(SWIGPERL) %module "SVN::_Diff" #elif defined(SWIGRUBY) %module "svn::ext::diff" #endif -%include svn_global.swg %import core.i /* ----------------------------------------------------------------------- diff --git a/subversion/bindings/swig/svn_fs.i b/subversion/bindings/swig/svn_fs.i index ae1d847b88199..0c1e0b9810e4e 100644 --- a/subversion/bindings/swig/svn_fs.i +++ b/subversion/bindings/swig/svn_fs.i @@ -21,15 +21,16 @@ * svn_fs.i: SWIG interface file for svn_fs.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") fs +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) fs #elif defined(SWIGPERL) %module "SVN::_Fs" #elif defined(SWIGRUBY) %module "svn::ext::fs" #endif -%include svn_global.swg %import core.i %import svn_delta.i diff --git a/subversion/bindings/swig/svn_ra.i b/subversion/bindings/swig/svn_ra.i index e1925f8a47c6c..80284c4cc52da 100644 --- a/subversion/bindings/swig/svn_ra.i +++ b/subversion/bindings/swig/svn_ra.i @@ -21,15 +21,16 @@ * svn_ra.i: SWIG interface file for svn_ra.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") ra +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) ra #elif defined(SWIGPERL) %module "SVN::_Ra" #elif defined(SWIGRUBY) %module "svn::ext::ra" #endif -%include svn_global.swg %import core.i %import svn_delta.i diff --git a/subversion/bindings/swig/svn_repos.i b/subversion/bindings/swig/svn_repos.i index bab95c859bdc0..4b621a3947e89 100644 --- a/subversion/bindings/swig/svn_repos.i +++ b/subversion/bindings/swig/svn_repos.i @@ -21,15 +21,16 @@ * svn_repos.i: SWIG interface file for svn_repos.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") repos +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) repos #elif defined(SWIGPERL) %module "SVN::_Repos" #elif defined(SWIGRUBY) %module "svn::ext::repos" #endif -%include svn_global.swg %import core.i %import svn_delta.i %import svn_fs.i @@ -151,8 +152,6 @@ svn_error_t *svn_repos_dump_fs2(svn_repos_t *repos, #ifdef SWIGPYTHON /* Make swig wrap this function for us, to allow making a vtable in python */ void svn_swig_py_make_parse_fns3(const svn_repos_parse_fns3_t **parse_fns3, - void **parse_baton, - PyObject *py_parse_fns3, apr_pool_t *pool); #endif diff --git a/subversion/bindings/swig/svn_wc.i b/subversion/bindings/swig/svn_wc.i index 2efe84705fea8..2648f2e16126c 100644 --- a/subversion/bindings/swig/svn_wc.i +++ b/subversion/bindings/swig/svn_wc.i @@ -21,15 +21,16 @@ * svn_wc.i: SWIG interface file for svn_wc.h */ +%include svn_global.swg + #if defined(SWIGPYTHON) -%module(package="libsvn") wc +%module(package="libsvn", moduleimport=SVN_PYTHON_MODULEIMPORT) wc #elif defined(SWIGPERL) %module "SVN::_Wc" #elif defined(SWIGRUBY) %module "svn::ext::wc" #endif -%include svn_global.swg %import core.i %import svn_delta.i %import svn_ra.i @@ -231,7 +232,7 @@ %typemap(argout) svn_revnum_t *target_revision { - %append_output(LONG2NUM((long)$1)); + %append_output(SWIG2NUM($1)); } #endif @@ -241,6 +242,8 @@ { $result = $1 ? svn_swig_rb_svn_error_to_rb_error($1) : Qnil; } + +%typemap(ret) svn_error_t *err ""; #endif diff --git a/subversion/include/private/svn_cmdline_private.h b/subversion/include/private/svn_cmdline_private.h index ac5fb7b079240..aa8bb7bcca2d7 100644 --- a/subversion/include/private/svn_cmdline_private.h +++ b/subversion/include/private/svn_cmdline_private.h @@ -278,6 +278,34 @@ svn_cmdline__stdin_readline(const char **result, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +#if defined(WIN32) +/* Normalizes Windows-specific command line arguments, such as those passed + to wmain(), to the environment-specific code page. */ +svn_error_t * +svn_cmdline__win32_get_cstring_argv(const char **cstring_argv_p[], + int argc, + const wchar_t *argv[], + apr_pool_t *result_pool); +#endif + +/* Default platform-agnostic handler that normalizes command line arguments + to the environment-specific code page. */ +svn_error_t * +svn_cmdline__default_get_cstring_argv(const char **cstring_argv_p[], + int argc, + const char *argv[], + apr_pool_t *result_pool); + +#if defined(WIN32) && defined(_MSC_VER) +typedef wchar_t svn_cmdline__argv_char_t; +#define SVN_CMDLINE__MAIN wmain +#define svn_cmdline__get_cstring_argv svn_cmdline__win32_get_cstring_argv +#else +typedef char svn_cmdline__argv_char_t; +#define SVN_CMDLINE__MAIN main +#define svn_cmdline__get_cstring_argv svn_cmdline__default_get_cstring_argv +#endif + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/include/private/svn_dep_compat.h b/subversion/include/private/svn_dep_compat.h index 7e66038261358..9a2e49a19cbaa 100644 --- a/subversion/include/private/svn_dep_compat.h +++ b/subversion/include/private/svn_dep_compat.h @@ -29,6 +29,7 @@ #define SVN_DEP_COMPAT_H #include +#include #ifdef __cplusplus extern "C" { @@ -193,6 +194,16 @@ extern "C" { ((major*1000000 + minor*1000 + patch) <= SVN_SQLITE_MIN_VERSION_NUMBER) #endif /* SQLITE_VERSION_AT_LEAST */ +/** + * Support for 'apr_escape_shell() which was introduced in APR 1.5. + */ +#if !APR_VERSION_AT_LEAST(1,5,0) +/* from apr_escape.h */ +#define APR_ESCAPE_STRING (-1) +APR_DECLARE(apr_status_t) apr_escape_shell(char *escaped, const char *str, + apr_ssize_t slen, apr_size_t *len); +#endif + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/include/private/svn_fs_fs_private.h b/subversion/include/private/svn_fs_fs_private.h index de57558194d43..2ca61e4cf8c18 100644 --- a/subversion/include/private/svn_fs_fs_private.h +++ b/subversion/include/private/svn_fs_fs_private.h @@ -353,6 +353,17 @@ typedef struct svn_fs_fs__ioctl_revision_size_output_t /* See svn_fs_fs__revision_size(). */ SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_REVISION_SIZE, SVN_FS_TYPE_FSFS, 1003); +typedef struct svn_fs_fs__ioctl_build_rep_cache_input_t +{ + svn_revnum_t start_rev; + svn_revnum_t end_rev; + svn_fs_progress_notify_func_t progress_func; + void *progress_baton; +} svn_fs_fs__ioctl_build_rep_cache_input_t; + +/* See svn_fs_fs__build_rep_cache(). */ +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_BUILD_REP_CACHE, SVN_FS_TYPE_FSFS, 1004); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/include/private/svn_repos_private.h b/subversion/include/private/svn_repos_private.h index 1fd34e8053c7f..1d5fc9cd6ea74 100644 --- a/subversion/include/private/svn_repos_private.h +++ b/subversion/include/private/svn_repos_private.h @@ -390,6 +390,14 @@ svn_repos__get_dump_editor(const svn_delta_editor_t **editor, const char *update_anchor_relpath, apr_pool_t *pool); +/* Validate that the given PATH is a valid pathname that can be stored in + * a Subversion repository, according to the name constraints used by the + * svn_repos_* layer. + */ +svn_error_t * +svn_repos__validate_new_path(const char *path, + apr_pool_t *scratch_pool); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/include/private/svn_sorts_private.h b/subversion/include/private/svn_sorts_private.h index d0fddc0c6488c..d11ebbe1a992b 100644 --- a/subversion/include/private/svn_sorts_private.h +++ b/subversion/include/private/svn_sorts_private.h @@ -80,7 +80,7 @@ svn_sort__array(apr_array_header_t *array, int (*comparison_func)(const void *, const void *)); -/* Return the lowest index at which the element @a *key should be inserted into +/** Return the lowest index at which the element @a *key should be inserted into * the array @a array, according to the ordering defined by @a compare_func. * The array must already be sorted in the ordering defined by @a compare_func. * @a compare_func is defined as for the C stdlib function bsearch(); the @@ -93,7 +93,7 @@ svn_sort__bsearch_lower_bound(const apr_array_header_t *array, const void *key, int (*compare_func)(const void *, const void *)); -/* Find the lowest index at which the element @a *key should be inserted into +/** Find the lowest index at which the element @a *key should be inserted into * the array @a array, according to the ordering defined by @a compare_func. * The array must already be sorted in the ordering defined by @a compare_func. * @a compare_func is defined as for the C stdlib function bsearch(); the @@ -116,7 +116,7 @@ svn_sort__array_lookup(const apr_array_header_t *array, int (*compare_func)(const void *, const void *)); -/* Insert a shallow copy of @a *new_element into the array @a array at the index +/** Insert a shallow copy of @a *new_element into the array @a array at the index * @a insert_index, growing the array and shuffling existing elements along to * make room. * @@ -131,7 +131,7 @@ svn_sort__array_insert2(apr_array_header_t *array, int insert_index); -/* Remove @a elements_to_delete elements starting at @a delete_index from the +/** Remove @a elements_to_delete elements starting at @a delete_index from the * array @a arr. * * Raise an error if the indexes to delete extends outside the array bounds @@ -144,7 +144,7 @@ svn_sort__array_delete2(apr_array_header_t *arr, int delete_index, int elements_to_delete); -/* Reverse the order of elements in @a array, in place. +/** Reverse the order of elements in @a array, in place. * * @note Private. For use by Subversion's own code only. */ diff --git a/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h index 162b315417826..c00c0f5b2f851 100644 --- a/subversion/include/svn_error_codes.h +++ b/subversion/include/svn_error_codes.h @@ -893,6 +893,16 @@ SVN_ERROR_START SVN_ERR_FS_CATEGORY_START + 68, "Unrecognized filesystem I/O control code") + /** @since New in 1.14. */ + SVN_ERRDEF(SVN_ERR_FS_REP_SHARING_NOT_ALLOWED, + SVN_ERR_FS_CATEGORY_START + 69, + "Rep-sharing is not allowed.") + + /** @since New in 1.14. */ + SVN_ERRDEF(SVN_ERR_FS_REP_SHARING_NOT_SUPPORTED, + SVN_ERR_FS_CATEGORY_START + 70, + "Rep-sharing is not supported.") + /* repos errors */ SVN_ERRDEF(SVN_ERR_REPOS_LOCKED, diff --git a/subversion/include/svn_ra.h b/subversion/include/svn_ra.h index 4c71520dd1ffb..d534ab70caa13 100644 --- a/subversion/include/svn_ra.h +++ b/subversion/include/svn_ra.h @@ -2244,7 +2244,7 @@ svn_ra_has_capability(svn_ra_session_t *session, /** * The capability of a server to automatically remove transaction - * properties prefixed with SVN_PROP_EPHEMERAL_PREFIX. + * properties prefixed with #SVN_PROP_TXN_PREFIX. * * @since New in 1.8. */ diff --git a/subversion/include/svn_types.h b/subversion/include/svn_types.h index 418d6ac9cc84b..2513355156659 100644 --- a/subversion/include/svn_types.h +++ b/subversion/include/svn_types.h @@ -124,16 +124,12 @@ extern "C" { * if unaligned access is supported for integers. * * @since New in 1.7. + * @deprecated Provided for backward compatibility with the 1.14 API. */ -#ifndef SVN_UNALIGNED_ACCESS_IS_OK -# if defined(_M_IX86) || defined(i386) \ - || defined(_M_X64) || defined(__x86_64) \ - || defined(__powerpc__) || defined(__ppc__) -# define SVN_UNALIGNED_ACCESS_IS_OK 1 -# else -# define SVN_UNALIGNED_ACCESS_IS_OK 0 -# endif +#ifdef SVN_UNALIGNED_ACCESS_IS_OK +# undef SVN_UNALIGNED_ACCESS_IS_OK #endif +#define SVN_UNALIGNED_ACCESS_IS_OK 0 @@ -248,6 +244,35 @@ typedef struct svn_version_t svn_version_t; /** @} */ + +/** @defgroup apr_hash_utilities APR Hash Table Helpers + * These functions enable the caller to dereference an APR hash table index + * without type casts or temporary variables. + * + * These functions are provided by APR itself from version 1.5. + * Definitions are provided here for when using older versions of APR. + * @{ + */ + +#if !APR_VERSION_AT_LEAST(1, 5, 0) + +/** Return the key of the hash table entry indexed by @a hi. */ +const void * +apr_hash_this_key(apr_hash_index_t *hi); + +/** Return the key length of the hash table entry indexed by @a hi. */ +apr_ssize_t +apr_hash_this_key_len(apr_hash_index_t *hi); + +/** Return the value of the hash table entry indexed by @a hi. */ +void * +apr_hash_this_val(apr_hash_index_t *hi); + +#endif + +/** @} */ + + /** On Windows, APR_STATUS_IS_ENOTDIR includes several kinds of * invalid-pathname error but not ERROR_INVALID_NAME, so we include it. diff --git a/subversion/include/svn_version.h b/subversion/include/svn_version.h index 667e350c58bd6..5cc9d28190dad 100644 --- a/subversion/include/svn_version.h +++ b/subversion/include/svn_version.h @@ -70,7 +70,7 @@ extern "C" { * * @since New in 1.1. */ -#define SVN_VER_PATCH 0 +#define SVN_VER_PATCH 6 /** @deprecated Provided for backward compatibility with the 1.0 API. */ diff --git a/subversion/libsvn_auth_kwallet/kwallet.cpp b/subversion/libsvn_auth_kwallet/kwallet.cpp index f59e04778b3c1..c64caabe14bd2 100644 --- a/subversion/libsvn_auth_kwallet/kwallet.cpp +++ b/subversion/libsvn_auth_kwallet/kwallet.cpp @@ -227,10 +227,10 @@ kwallet_password_get(svn_boolean_t *done, KLocalizedString::setApplicationDomain("subversion"); /* translation domain */ /* componentName appears in KDE GUI prompts */ - KAboutData aboutData(QStringLiteral("subversion"), /* componentName */ + KAboutData aboutData(QString("subversion"), /* componentName */ i18n(get_application_name(parameters, pool)), /* displayName */ - QStringLiteral(SVN_VER_NUMBER)); + QString(SVN_VER_NUMBER)); KAboutData::setApplicationData(aboutData); #else KCmdLineArgs::init(q_argc, q_argv, @@ -309,10 +309,10 @@ kwallet_password_set(svn_boolean_t *done, KLocalizedString::setApplicationDomain("subversion"); /* translation domain */ /* componentName appears in KDE GUI prompts */ - KAboutData aboutData(QStringLiteral("subversion"), /* componentName */ + KAboutData aboutData(QString("subversion"), /* componentName */ i18n(get_application_name(parameters, pool)), /* displayName */ - QStringLiteral(SVN_VER_NUMBER)); + QString(SVN_VER_NUMBER)); KAboutData::setApplicationData(aboutData); #else KCmdLineArgs::init(q_argc, q_argv, diff --git a/subversion/libsvn_client/conflicts.c b/subversion/libsvn_client/conflicts.c index 9a58703c4238f..50f53453e2ffe 100644 --- a/subversion/libsvn_client/conflicts.c +++ b/subversion/libsvn_client/conflicts.c @@ -2847,12 +2847,26 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, /* Make sure we're going to search the related node in a revision where * it exists. The younger incoming node might have been deleted in HEAD. */ if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM) - SVN_ERR(find_related_node( - &related_repos_relpath, &related_peg_rev, - related_repos_relpath, related_peg_rev, - (old_rev < new_rev ? old_repos_relpath : new_repos_relpath), - (old_rev < new_rev ? old_rev : new_rev), - conflict, ctx, scratch_pool, scratch_pool)); + { + const char *older_related_repos_relpath; + svn_revnum_t older_related_peg_rev; + SVN_ERR(find_related_node( + &older_related_repos_relpath, &older_related_peg_rev, + related_repos_relpath, related_peg_rev, + (old_rev < new_rev ? old_repos_relpath : new_repos_relpath), + (old_rev < new_rev ? old_rev : new_rev), + conflict, ctx, scratch_pool, scratch_pool)); + if (older_related_repos_relpath != NULL && + older_related_peg_rev != SVN_INVALID_REVNUM) + { + related_repos_relpath = older_related_repos_relpath; + related_peg_rev = older_related_peg_rev; + } + } + + /* Bail if we are unable to find the related node. */ + if (related_repos_relpath == NULL || related_peg_rev == SVN_INVALID_REVNUM) + return SVN_NO_ERROR; /* Set END_REV to our best guess of the nearest YCA revision. */ url = svn_path_url_add_component2(repos_root_url, related_repos_relpath, @@ -3014,12 +3028,12 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, deleted_basename, conflict->pool); details->moves = moves; + details->wc_move_targets = apr_hash_make(conflict->pool); if (details->moves != NULL) { apr_pool_t *iterpool; int i; - details->wc_move_targets = apr_hash_make(conflict->pool); iterpool = svn_pool_create(scratch_pool); for (i = 0; i < details->moves->nelts; i++) { @@ -8583,13 +8597,20 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, * Merge from the local move's target location to the * incoming move's target location. */ struct conflict_tree_local_missing_details *local_details; - apr_array_header_t *moves; local_details = conflict->tree_conflict_local_details; - moves = svn_hash_gets(local_details->wc_move_targets, - local_details->move_target_repos_relpath); - merge_source_abspath = - APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *); + if (local_details->wc_move_targets && + local_details->move_target_repos_relpath) + { + apr_array_header_t *moves; + moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + merge_source_abspath = + APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, + const char *); + } + else + merge_source_abspath = victim_abspath; } else merge_source_abspath = victim_abspath; @@ -9769,7 +9790,7 @@ resolve_local_move_dir_merge(svn_client_conflict_option_t *option, NULL, conflict, scratch_pool, scratch_pool)); - if (details->wc_move_targets) + if (details->wc_move_targets && details->move_target_repos_relpath) { apr_array_header_t *moves; @@ -10718,7 +10739,8 @@ describe_incoming_move_merge_conflict_option( struct conflict_tree_incoming_delete_details *details; details = conflict->tree_conflict_incoming_details; - if (details->wc_move_targets) + if (details->wc_move_targets && + details->move_target_repos_relpath) { apr_array_header_t *moves; @@ -12251,7 +12273,7 @@ svn_client_conflict_option_get_moved_to_abspath_candidates2( *possible_moved_to_abspaths = apr_array_make(result_pool, 1, sizeof (const char *)); - if (details->wc_move_targets) + if (details->wc_move_targets && details->move_target_repos_relpath) { apr_array_header_t *move_target_wc_abspaths; move_target_wc_abspaths = @@ -12445,7 +12467,7 @@ svn_client_conflict_option_set_moved_to_abspath2( svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling), scratch_pool)); } - else if (details->wc_move_targets) + else if (details->wc_move_targets && details->move_target_repos_relpath) { apr_array_header_t *move_target_wc_abspaths; move_target_wc_abspaths = diff --git a/subversion/libsvn_client/diff.c b/subversion/libsvn_client/diff.c index f019294f05c01..ae077cac04896 100644 --- a/subversion/libsvn_client/diff.c +++ b/subversion/libsvn_client/diff.c @@ -205,7 +205,9 @@ adjust_paths_for_diff_labels(const char **index_path, else if (! strcmp(relative_to_dir, new_path)) new_path = "."; else - return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir); + return MAKE_ERR_BAD_RELATIVE_PATH( + svn_dirent_local_style(new_path, scratch_pool), + svn_dirent_local_style(relative_to_dir, scratch_pool)); } { diff --git a/subversion/libsvn_client/externals.c b/subversion/libsvn_client/externals.c index 7ff1ed25d80d0..cc53954896c13 100644 --- a/subversion/libsvn_client/externals.c +++ b/subversion/libsvn_client/externals.c @@ -917,7 +917,7 @@ handle_external_item_change(svn_client_ctx_t *ctx, SVN_ERR(switch_file_external(local_abspath, new_loc, - new_url, + new_loc->url, &new_item->peg_revision, &new_item->revision, parent_dir_abspath, diff --git a/subversion/libsvn_client/merge.c b/subversion/libsvn_client/merge.c index 9d9a1c300a105..e535647d8649a 100644 --- a/subversion/libsvn_client/merge.c +++ b/subversion/libsvn_client/merge.c @@ -264,7 +264,7 @@ typedef struct merge_cmd_baton_t { /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global comment) or a similar list for single-file-merges */ - const apr_array_header_t *children_with_mergeinfo; + apr_array_header_t *children_with_mergeinfo; svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */ @@ -1545,6 +1545,25 @@ record_update_delete(merge_cmd_baton_t *merge_b, svn_node_kind_to_word(kind)); } + /* Note in children_with_mergeinfo that all paths in this subtree are + * being deleted, to avoid trying to set mergeinfo on them later. */ + if (merge_b->children_with_mergeinfo) + { + int i; + + for (i = 0; i < merge_b->children_with_mergeinfo->nelts; i++) + { + svn_client__merge_path_t *child + = APR_ARRAY_IDX(merge_b->children_with_mergeinfo, i, + svn_client__merge_path_t *); + + if (svn_dirent_is_ancestor(local_abspath, child->abspath)) + { + SVN_ERR(svn_sort__array_delete2(merge_b->children_with_mergeinfo, i--, 1)); + } + } + } + return SVN_NO_ERROR; } @@ -5595,7 +5614,7 @@ svn_client__make_merge_conflict_error(svn_client__conflict_report_t *report, with paths (svn_client__merge_path_t *) arranged in depth first order, which have mergeinfo set on them or meet one of the other criteria defined in get_mergeinfo_paths(). Remove any paths absent from disk - or scheduled for deletion from CHILDREN_WITH_MERGEINFO which are equal to + from CHILDREN_WITH_MERGEINFO which are equal to or are descendants of TARGET_WCPATH by setting those children to NULL. */ static svn_error_t * remove_absent_children(const char *target_wcpath, @@ -5609,7 +5628,7 @@ remove_absent_children(const char *target_wcpath, { svn_client__merge_path_t *child = APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *); - if ((child->absent || child->scheduled_for_deletion) + if (child->absent && svn_dirent_is_ancestor(target_wcpath, child->abspath)) { SVN_ERR(svn_sort__array_delete2(children_with_mergeinfo, i--, 1)); @@ -7896,18 +7915,23 @@ process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b, apr_pool_t *pool) { apr_pool_t *iterpool; - apr_hash_index_t *hi; + apr_array_header_t *a; + int i; if (!merge_b->paths_with_new_mergeinfo || merge_b->dry_run) return SVN_NO_ERROR; /* Iterate over each path with explicit mergeinfo added by the merge. */ + /* Iterate over the paths in a parent-to-child order so that inherited + * mergeinfo is propagated consistently from each parent path to its + * children. (Issue #4862) */ + a = svn_sort__hash(merge_b->paths_with_new_mergeinfo, + svn_sort_compare_items_as_paths, pool); iterpool = svn_pool_create(pool); - for (hi = apr_hash_first(pool, merge_b->paths_with_new_mergeinfo); - hi; - hi = apr_hash_next(hi)) + for (i = 0; i < a->nelts; i++) { - const char *abspath_with_new_mergeinfo = apr_hash_this_key(hi); + svn_sort__item_t *item = &APR_ARRAY_IDX(a, i, svn_sort__item_t); + const char *abspath_with_new_mergeinfo = item->key; svn_mergeinfo_t path_inherited_mergeinfo; svn_mergeinfo_t path_explicit_mergeinfo; svn_client__merge_path_t *new_child; diff --git a/subversion/libsvn_client/mergeinfo.h b/subversion/libsvn_client/mergeinfo.h index 1d6d524f5dd21..99e1bf9f80c7d 100644 --- a/subversion/libsvn_client/mergeinfo.h +++ b/subversion/libsvn_client/mergeinfo.h @@ -74,8 +74,6 @@ typedef struct svn_client__merge_path_t prior to a merge. May be NULL. */ svn_boolean_t inherited_mergeinfo; /* Whether PRE_MERGE_MERGEINFO was explicit or inherited. */ - svn_boolean_t scheduled_for_deletion; /* ABSPATH is scheduled for - deletion. */ svn_boolean_t immediate_child_dir; /* ABSPATH is an immediate child directory of the merge target, has no explicit mergeinfo prior diff --git a/subversion/libsvn_client/mtcc.c b/subversion/libsvn_client/mtcc.c index 48ddcccbc1648..a7c86a49a472d 100644 --- a/subversion/libsvn_client/mtcc.c +++ b/subversion/libsvn_client/mtcc.c @@ -453,7 +453,8 @@ mtcc_verify_create(svn_client__mtcc_t *mtcc, if (op) return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, - _("Path '%s' already exists"), + _("Path '%s' already exists, or was created " + "by an earlier operation"), new_relpath); SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE, diff --git a/subversion/libsvn_diff/diff_file.c b/subversion/libsvn_diff/diff_file.c index 6d72159cfbb04..6549cbeb0f21b 100644 --- a/subversion/libsvn_diff/diff_file.c +++ b/subversion/libsvn_diff/diff_file.c @@ -355,23 +355,6 @@ is_one_at_eof(struct file_info file[], apr_size_t file_len) return FALSE; } -/* 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 -static svn_boolean_t contains_eol(apr_uintptr_t chunk) -{ - apr_uintptr_t r_test = chunk ^ SVN__R_MASK; - apr_uintptr_t n_test = chunk ^ SVN__N_MASK; - - 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 & SVN__BIT_7_SET) != SVN__BIT_7_SET; -} -#endif - /* Find the prefix which is identical between all elements of the FILE array. * Return the number of prefix lines in PREFIX_LINES. REACHED_ONE_EOF will be * set to TRUE if one of the FILEs reached its end while scanning prefix, @@ -396,10 +379,6 @@ find_identical_prefix(svn_boolean_t *reached_one_eof, apr_off_t *prefix_lines, is_match = is_match && *file[0].curp == *file[i].curp; while (is_match) { -#if SVN_UNALIGNED_ACCESS_IS_OK - apr_ssize_t max_delta, delta; -#endif /* SVN_UNALIGNED_ACCESS_IS_OK */ - /* ### TODO: see if we can take advantage of diff options like ignore_eol_style or ignore_space. */ /* check for eol, and count */ @@ -419,53 +398,6 @@ find_identical_prefix(svn_boolean_t *reached_one_eof, apr_off_t *prefix_lines, INCREMENT_POINTERS(file, file_len, pool); -#if SVN_UNALIGNED_ACCESS_IS_OK - - /* Try to advance as far as possible with machine-word granularity. - * Determine how far we may advance with chunky ops without reaching - * endp for any of the files. - * Signedness is important here if curp gets close to endp. - */ - max_delta = file[0].endp - file[0].curp - sizeof(apr_uintptr_t); - for (i = 1; i < file_len; i++) - { - delta = file[i].endp - file[i].curp - sizeof(apr_uintptr_t); - if (delta < max_delta) - max_delta = delta; - } - - is_match = TRUE; - for (delta = 0; delta < max_delta; delta += sizeof(apr_uintptr_t)) - { - apr_uintptr_t chunk = *(const apr_uintptr_t *)(file[0].curp + delta); - if (contains_eol(chunk)) - break; - - for (i = 1; i < file_len; i++) - if (chunk != *(const apr_uintptr_t *)(file[i].curp + delta)) - { - is_match = FALSE; - break; - } - - if (! is_match) - break; - } - - if (delta /* > 0*/) - { - /* We either found a mismatch or an EOL at or shortly behind curp+delta - * or we cannot proceed with chunky ops without exceeding endp. - * In any way, everything up to curp + delta is equal and not an EOL. - */ - for (i = 0; i < file_len; i++) - file[i].curp += delta; - - /* Skipped data without EOL markers, so last char was not a CR. */ - had_cr = FALSE; - } -#endif - *reached_one_eof = is_one_at_eof(file, file_len); if (*reached_one_eof) break; @@ -611,11 +543,6 @@ find_identical_suffix(apr_off_t *suffix_lines, struct file_info file[], while (is_match) { svn_boolean_t reached_prefix; -#if SVN_UNALIGNED_ACCESS_IS_OK - /* Initialize the minimum pointer positions. */ - const char *min_curp[4]; - svn_boolean_t can_read_word; -#endif /* SVN_UNALIGNED_ACCESS_IS_OK */ /* ### TODO: see if we can take advantage of diff options like ignore_eol_style or ignore_space. */ @@ -636,60 +563,6 @@ find_identical_suffix(apr_off_t *suffix_lines, struct file_info file[], DECREMENT_POINTERS(file_for_suffix, file_len, pool); -#if SVN_UNALIGNED_ACCESS_IS_OK - for (i = 0; i < file_len; i++) - min_curp[i] = file_for_suffix[i].buffer; - - /* If we are in the same chunk that contains the last part of the common - prefix, use the min_curp[0] pointer to make sure we don't get a - suffix that overlaps the already determined common prefix. */ - if (file_for_suffix[0].chunk == suffix_min_chunk0) - min_curp[0] += suffix_min_offset0; - - /* Scan quickly by reading with machine-word granularity. */ - for (i = 0, can_read_word = TRUE; can_read_word && i < file_len; i++) - can_read_word = ((file_for_suffix[i].curp + 1 - sizeof(apr_uintptr_t)) - > min_curp[i]); - - while (can_read_word) - { - apr_uintptr_t chunk; - - /* For each file curp is positioned at the current byte, but we - want to examine the current byte and the ones before the current - location as one machine word. */ - - chunk = *(const apr_uintptr_t *)(file_for_suffix[0].curp + 1 - - sizeof(apr_uintptr_t)); - if (contains_eol(chunk)) - break; - - for (i = 1, is_match = TRUE; is_match && i < file_len; i++) - is_match = (chunk - == *(const apr_uintptr_t *) - (file_for_suffix[i].curp + 1 - - sizeof(apr_uintptr_t))); - - if (! is_match) - break; - - for (i = 0; i < file_len; i++) - { - file_for_suffix[i].curp -= sizeof(apr_uintptr_t); - can_read_word = can_read_word - && ( (file_for_suffix[i].curp + 1 - - sizeof(apr_uintptr_t)) - > min_curp[i]); - } - - /* We skipped some bytes, so there are no closing EOLs */ - had_nl = FALSE; - } - - /* The > min_curp[i] check leaves at least one final byte for checking - in the non block optimized case below. */ -#endif - reached_prefix = file_for_suffix[0].chunk == suffix_min_chunk0 && (file_for_suffix[0].curp - file_for_suffix[0].buffer) == suffix_min_offset0; diff --git a/subversion/libsvn_diff/parse-diff.c b/subversion/libsvn_diff/parse-diff.c index 4fc5bfd46d73a..efc3fce96a8ff 100644 --- a/subversion/libsvn_diff/parse-diff.c +++ b/subversion/libsvn_diff/parse-diff.c @@ -1006,7 +1006,7 @@ parse_pretty_mergeinfo_line(svn_boolean_t *found_mergeinfo, } (*number_of_reverse_merges)--; } - else if (number_of_forward_merges > 0) /* forward merges */ + else if (*number_of_forward_merges > 0) /* forward merges */ { if (patch->reverse) { diff --git a/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c index 5eb475026d377..5174da2e49410 100644 --- a/subversion/libsvn_fs_fs/fs.c +++ b/subversion/libsvn_fs_fs/fs.c @@ -312,6 +312,22 @@ fs_ioctl(svn_fs_t *fs, svn_fs_ioctl_code_t ctlcode, *output_p = output; return SVN_NO_ERROR; } + else if (ctlcode.code == SVN_FS_FS__IOCTL_BUILD_REP_CACHE.code) + { + svn_fs_fs__ioctl_build_rep_cache_input_t *input = input_void; + + SVN_ERR(svn_fs_fs__build_rep_cache(fs, + input->start_rev, + input->end_rev, + input->progress_func, + input->progress_baton, + cancel_func, + cancel_baton, + scratch_pool)); + + *output_p = NULL; + return SVN_NO_ERROR; + } } return svn_error_create(SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE, NULL, NULL); diff --git a/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c index 5cb1362d84515..76b053e232296 100644 --- a/subversion/libsvn_fs_fs/fs_fs.c +++ b/subversion/libsvn_fs_fs/fs_fs.c @@ -37,6 +37,7 @@ #include "cached_data.h" #include "id.h" #include "index.h" +#include "low_level.h" #include "rep-cache.h" #include "revprops.h" #include "transaction.h" @@ -2343,3 +2344,172 @@ svn_fs_fs__info_config_files(apr_array_header_t **files, result_pool); return SVN_NO_ERROR; } + +static svn_error_t * +ensure_representation_sha1(svn_fs_t *fs, + representation_t *rep, + apr_pool_t *pool) +{ + if (!rep->has_sha1) + { + svn_stream_t *contents; + svn_checksum_t *checksum; + + SVN_ERR(svn_fs_fs__get_contents(&contents, fs, rep, FALSE, pool)); + SVN_ERR(svn_stream_contents_checksum(&checksum, contents, + svn_checksum_sha1, pool, pool)); + + memcpy(rep->sha1_digest, checksum->digest, APR_SHA1_DIGESTSIZE); + rep->has_sha1 = TRUE; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +reindex_node(svn_fs_t *fs, + const svn_fs_id_t *id, + svn_revnum_t rev, + svn_fs_fs__revision_file_t *rev_file, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + node_revision_t *noderev; + apr_off_t offset; + + if (svn_fs_fs__id_rev(id) != rev) + { + return SVN_NO_ERROR; + } + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL, + svn_fs_fs__id_item(id), pool)); + + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool)); + SVN_ERR(svn_fs_fs__read_noderev(&noderev, rev_file->stream, + pool, pool)); + + /* Make sure EXPANDED_SIZE has the correct value for every rep. */ + SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->data_rep, pool)); + SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->prop_rep, pool)); + + /* First reindex sub-directory to match write_final_rev() behavior. */ + if (noderev->kind == svn_node_dir) + { + apr_array_header_t *entries; + + SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool, pool)); + + if (entries->nelts > 0) + { + int i; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(pool); + for (i = 0; i < entries->nelts; i++) + { + const svn_fs_dirent_t *dirent; + + svn_pool_clear(iterpool); + + dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *); + + SVN_ERR(reindex_node(fs, dirent->id, rev, rev_file, + cancel_func, cancel_baton, iterpool)); + } + svn_pool_destroy(iterpool); + } + } + + if (noderev->data_rep && noderev->data_rep->revision == rev && + noderev->kind == svn_node_file) + { + SVN_ERR(ensure_representation_sha1(fs, noderev->data_rep, pool)); + SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->data_rep, pool)); + } + + if (noderev->prop_rep && noderev->prop_rep->revision == rev) + { + SVN_ERR(ensure_representation_sha1(fs, noderev->prop_rep, pool)); + SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->prop_rep, pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__build_rep_cache(svn_fs_t *fs, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_fs_progress_notify_func_t progress_func, + void *progress_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + fs_fs_data_t *ffd = fs->fsap_data; + apr_pool_t *iterpool; + svn_revnum_t rev; + + if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT) + { + return svn_error_createf(SVN_ERR_FS_REP_SHARING_NOT_SUPPORTED, NULL, + _("FSFS format (%d) too old for rep-sharing; " + "please upgrade the filesystem."), + ffd->format); + } + + if (!ffd->rep_sharing_allowed) + { + return svn_error_create(SVN_ERR_FS_REP_SHARING_NOT_ALLOWED, NULL, + _("Filesystem does not allow rep-sharing.")); + } + + /* Do not build rep-cache for revision zero to match + * svn_fs_fs__create() behavior. */ + if (start_rev == SVN_INVALID_REVNUM) + start_rev = 1; + + if (end_rev == SVN_INVALID_REVNUM) + SVN_ERR(svn_fs_fs__youngest_rev(&end_rev, fs, pool)); + + /* Do nothing for empty FS. */ + if (start_rev > end_rev) + { + return SVN_NO_ERROR; + } + + if (!ffd->rep_cache_db) + SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); + + iterpool = svn_pool_create(pool); + for (rev = start_rev; rev <= end_rev; rev++) + { + svn_fs_id_t *root_id; + svn_fs_fs__revision_file_t *file; + svn_error_t *err; + + svn_pool_clear(iterpool); + + if (progress_func) + progress_func(rev, progress_baton, iterpool); + + SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&file, fs, rev, + iterpool, iterpool)); + SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, iterpool, iterpool)); + + SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db)); + err = reindex_node(fs, root_id, rev, file, cancel_func, cancel_baton, iterpool); + SVN_ERR(svn_sqlite__finish_transaction(ffd->rep_cache_db, err)); + + SVN_ERR(svn_fs_fs__close_revision_file(file)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h index f3e356bda50c8..b233be1b2ea15 100644 --- a/subversion/libsvn_fs_fs/fs_fs.h +++ b/subversion/libsvn_fs_fs/fs_fs.h @@ -356,4 +356,23 @@ svn_fs_fs__revision_size(apr_off_t *rev_size, svn_revnum_t revision, apr_pool_t *scratch_pool); +/* Add missing entries to the rep-cache on the filesystem FS. Process data + * in revisions START_REV through END_REV inclusive. If START_REV is + * SVN_INVALID_REVNUM, start at revision 1; if END_REV is SVN_INVALID_REVNUM, + * end at the head revision. If the rep-cache does not exist, then create it. + * + * Indicate progress via the optional PROGRESS_FUNC callback using + * PROGRESS_BATON. The optional CANCEL_FUNC will periodically be called with + * CANCEL_BATON to allow cancellation. Use POOL for temporary allocations. + */ +svn_error_t * +svn_fs_fs__build_rep_cache(svn_fs_t *fs, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_fs_progress_notify_func_t progress_func, + void *progress_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool); + #endif diff --git a/subversion/libsvn_fs_fs/index.c b/subversion/libsvn_fs_fs/index.c index b7ba79f963137..168022e63eb3c 100644 --- a/subversion/libsvn_fs_fs/index.c +++ b/subversion/libsvn_fs_fs/index.c @@ -827,7 +827,7 @@ svn_fs_fs__l2p_index_append(svn_checksum_t **checksum, &eof, local_pool)); /* handle new revision */ - if ((entry > 0 && proto_entry.offset == 0) || eof) + if (eof || (entry > 0 && proto_entry.offset == 0)) { /* dump entries, grouped into pages */ diff --git a/subversion/libsvn_fs_fs/rep-cache-db.sql b/subversion/libsvn_fs_fs/rep-cache-db.sql index cd89f35c63ee3..a700a9dcd0261 100644 --- a/subversion/libsvn_fs_fs/rep-cache-db.sql +++ b/subversion/libsvn_fs_fs/rep-cache-db.sql @@ -61,7 +61,7 @@ WHERE hash = ?1 -- STMT_SET_REP /* Works for both V1 and V2 schemas. */ -INSERT OR FAIL INTO rep_cache (hash, revision, offset, size, expanded_size) +INSERT OR IGNORE INTO rep_cache (hash, revision, offset, size, expanded_size) VALUES (?1, ?2, ?3, ?4, ?5) -- STMT_GET_REPS_FOR_RANGE diff --git a/subversion/libsvn_fs_fs/rep-cache.c b/subversion/libsvn_fs_fs/rep-cache.c index b0b81fad9ac38..c51cde3954b4e 100644 --- a/subversion/libsvn_fs_fs/rep-cache.c +++ b/subversion/libsvn_fs_fs/rep-cache.c @@ -328,7 +328,6 @@ svn_fs_fs__set_rep_reference(svn_fs_t *fs, { fs_fs_data_t *ffd = fs->fsap_data; svn_sqlite__stmt_t *stmt; - svn_error_t *err; svn_checksum_t checksum; checksum.kind = svn_checksum_sha1; checksum.digest = rep->sha1_digest; @@ -351,28 +350,7 @@ svn_fs_fs__set_rep_reference(svn_fs_t *fs, (apr_int64_t) rep->size, (apr_int64_t) rep->expanded_size)); - err = svn_sqlite__insert(NULL, stmt); - if (err) - { - representation_t *old_rep; - - if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT) - return svn_error_trace(err); - - svn_error_clear(err); - - /* Constraint failed so the mapping for SHA1_CHECKSUM->REP - should exist. If so that's cool -- just do nothing. If not, - that's a red flag! */ - SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, &checksum, pool)); - - if (!old_rep) - { - /* Something really odd at this point, we failed to insert the - checksum AND failed to read an existing checksum. Do we need - to flag this? */ - } - } + SVN_ERR(svn_sqlite__insert(NULL, stmt)); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c index 1fafcfc1b2e86..e26788d45d553 100644 --- a/subversion/libsvn_fs_fs/tree.c +++ b/subversion/libsvn_fs_fs/tree.c @@ -220,11 +220,6 @@ hash_func(svn_revnum_t revision, apr_size_t i; apr_uint32_t hash_value = (apr_uint32_t)revision; -#if SVN_UNALIGNED_ACCESS_IS_OK - /* "randomizing" / distributing factor used in our hash function */ - const apr_uint32_t factor = 0xd1f3da69; -#endif - /* Calculate the hash value (HASH_VALUE has been initialized to REVISION). @@ -233,35 +228,8 @@ hash_func(svn_revnum_t revision, make as much of *PATH influence the result as possible to get an "even" spread across the hash buckets (maximizes our cache retention rate and thus the hit rates). - - When chunked access is possible (independent of the PATH pointer's - value!), we read 4 bytes at once and multiply the hash value with a - FACTOR that mirror / pattern / shift all 4 input bytes to various bits - of the result. The final result will be taken from the MSBs. - - When chunked access is not possible (not supported by CPU or odd bytes - at the end of *PATH), we use the simple traditional "* 33" hash - function that works very well with texts / paths and that e.g. APR uses. - - Please note that the bytewise and the chunked calculation are *NOT* - interchangeable as they will yield different results for the same input. - For any given machine and *PATH, we must use a fixed combination of the - two functions. - */ - i = 0; -#if SVN_UNALIGNED_ACCESS_IS_OK - /* We relax the dependency chain between iterations by processing - two chunks from the input per hash_value self-multiplication. - The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency - per 2 chunks instead of 1 chunk. */ - for (; i + 8 <= path_len; i += 8) - hash_value = hash_value * factor * factor - + ( *(const apr_uint32_t*)(path + i) * factor - + *(const apr_uint32_t*)(path + i + 4)); -#endif - - for (; i < path_len; ++i) + for (i = 0; i < path_len; ++i) /* Help GCC to minimize the HASH_VALUE update latency by splitting the MUL 33 of the naive implementation: h = h * 33 + path[i]. This shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD. @@ -4644,7 +4612,7 @@ make_txn_root(svn_fs_root_t **root_p, svn_fs_fs__dag_deserialize, APR_HASH_KEY_STRING, 32, 20, FALSE, - apr_pstrcat(pool, txn, ":TXN", + apr_pstrcat(pool, root->txn, ":TXN", SVN_VA_NULL), root->pool)); diff --git a/subversion/libsvn_fs_fs/verify.c b/subversion/libsvn_fs_fs/verify.c index 9d70d6cfbc8dc..0c26a8adaeb84 100644 --- a/subversion/libsvn_fs_fs/verify.c +++ b/subversion/libsvn_fs_fs/verify.c @@ -691,17 +691,20 @@ compare_p2l_to_rev(svn_fs_t *fs, * during later stages of the verification process. */ if ( (entry->type == SVN_FS_FS__ITEM_TYPE_CHANGES) != (entry->item.number == SVN_FS_FS__ITEM_INDEX_CHANGES)) - return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, - NULL, - _("p2l index entry for changes in" - " revision r%ld is item" - " %"APR_UINT64_T_FMT - " of type %u at offset %s"), - entry->item.revision, - entry->item.number, - (unsigned int)entry->type, - apr_off_t_toa(pool, offset)); - + { + const char *num_str = apr_psprintf(pool, + "%" APR_UINT64_T_FMT, + entry->item.number); + return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION, + NULL, + _("p2l index entry for changes in" + " revision r%ld is item %s" + " of type %u at offset %s"), + entry->item.revision, + num_str, + (unsigned int)entry->type, + apr_off_t_toa(pool, offset)); + } /* Check contents. */ if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED) { diff --git a/subversion/libsvn_fs_x/dag_cache.c b/subversion/libsvn_fs_x/dag_cache.c index 34a0117fd5994..fd486f114d823 100644 --- a/subversion/libsvn_fs_x/dag_cache.c +++ b/subversion/libsvn_fs_x/dag_cache.c @@ -312,11 +312,6 @@ cache_lookup(svn_fs_x__dag_cache_t *cache, apr_size_t path_len = path->len; apr_uint32_t hash_value = (apr_uint32_t)(apr_uint64_t)change_set; -#if SVN_UNALIGNED_ACCESS_IS_OK - /* "randomizing" / distributing factor used in our hash function */ - const apr_uint32_t factor = 0xd1f3da69; -#endif - /* optimistic lookup: hit the same bucket again? */ cache_entry_t *result = &cache->buckets[cache->last_hit]; if ( (result->change_set == change_set) @@ -332,20 +327,7 @@ cache_lookup(svn_fs_x__dag_cache_t *cache, /* need to do a full lookup. Calculate the hash value (HASH_VALUE has been initialized to REVISION). */ - i = 0; -#if SVN_UNALIGNED_ACCESS_IS_OK - /* We relax the dependency chain between iterations by processing - two chunks from the input per hash_value self-multiplication. - The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency - per 2 chunks instead of 1 chunk. - */ - for (; i + 8 <= path_len; i += 8) - hash_value = hash_value * factor * factor - + ( *(const apr_uint32_t*)(path->data + i) * factor - + *(const apr_uint32_t*)(path->data + i + 4)); -#endif - - for (; i < path_len; ++i) + for (i = 0; i < path_len; ++i) /* Help GCC to minimize the HASH_VALUE update latency by splitting the MUL 33 of the naive implementation: h = h * 33 + path[i]. This shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD. diff --git a/subversion/libsvn_fs_x/string_table.c b/subversion/libsvn_fs_x/string_table.c index e6a1cffdd2d94..1f7f660cef571 100644 --- a/subversion/libsvn_fs_x/string_table.c +++ b/subversion/libsvn_fs_x/string_table.c @@ -473,21 +473,6 @@ svn_fs_x__string_table_create(const string_table_builder_t *builder, return result; } -/* Masks used by table_copy_string. copy_mask[I] is used if the target - content to be preserved starts at byte I within the current chunk. - This is used to work around alignment issues. - */ -#if SVN_UNALIGNED_ACCESS_IS_OK -static const char *copy_masks[8] = { "\xff\xff\xff\xff\xff\xff\xff\xff", - "\x00\xff\xff\xff\xff\xff\xff\xff", - "\x00\x00\xff\xff\xff\xff\xff\xff", - "\x00\x00\x00\xff\xff\xff\xff\xff", - "\x00\x00\x00\x00\xff\xff\xff\xff", - "\x00\x00\x00\x00\x00\xff\xff\xff", - "\x00\x00\x00\x00\x00\x00\xff\xff", - "\x00\x00\x00\x00\x00\x00\x00\xff" }; -#endif - static void table_copy_string(char *buffer, apr_size_t len, @@ -499,40 +484,10 @@ table_copy_string(char *buffer, { assert(header->head_length <= len); { -#if SVN_UNALIGNED_ACCESS_IS_OK - /* the sections that we copy tend to be short but we can copy - *all* of it chunky because we made sure that source and target - buffer have some extra padding to prevent segfaults. */ - apr_uint64_t mask; - apr_size_t to_copy = len - header->head_length; - apr_size_t copied = 0; - - const char *source = table->data + header->tail_start; - char *target = buffer + header->head_length; - len = header->head_length; - - /* copy whole chunks */ - while (to_copy >= copied + sizeof(apr_uint64_t)) - { - *(apr_uint64_t *)(target + copied) - = *(const apr_uint64_t *)(source + copied); - copied += sizeof(apr_uint64_t); - } - - /* copy the remainder assuming that we have up to 8 extra bytes - of addressable buffer on the source and target sides. - Now, we simply copy 8 bytes and use a mask to filter & merge - old with new data. */ - mask = *(const apr_uint64_t *)copy_masks[to_copy - copied]; - *(apr_uint64_t *)(target + copied) - = (*(apr_uint64_t *)(target + copied) & mask) - | (*(const apr_uint64_t *)(source + copied) & ~mask); -#else memcpy(buffer + header->head_length, table->data + header->tail_start, len - header->head_length); len = header->head_length; -#endif } header = &table->short_strings[header->head_string]; diff --git a/subversion/libsvn_repos/authz.c b/subversion/libsvn_repos/authz.c index 9f8dbc5a08f75..a69441f5d052f 100644 --- a/subversion/libsvn_repos/authz.c +++ b/subversion/libsvn_repos/authz.c @@ -130,6 +130,30 @@ static svn_object_pool__t *authz_pool = NULL; static svn_object_pool__t *filtered_pool = NULL; static svn_atomic_t authz_pool_initialized = FALSE; +/* + * Ensure that we will initialize authz again if the pool which + * our authz caches depend on is cleared. + * + * HTTPD may run pre/post config hooks multiple times and clear + * its global configuration pool which our authz pools depend on. + * This happens in a non-threaded context during HTTPD's intialization + * and HTTPD's main loop, so it is safe to reset static variables here. + * (And any applications which cleared this pool while SVN threads + * were running would crash no matter what.) + * + * See issue #4880, "Use-after-free of object-pools in + * subversion/libsvn_repos/authz.c when used as httpd module" + */ +static apr_status_t +deinit_authz(void *data) +{ + /* The two object pools run their own cleanup handlers. */ + authz_pool = NULL; + filtered_pool = NULL; + authz_pool_initialized = FALSE; + return APR_SUCCESS; +} + /* Implements svn_atomic__err_init_func_t. */ static svn_error_t * synchronized_authz_initialize(void *baton, apr_pool_t *pool) @@ -143,6 +167,7 @@ synchronized_authz_initialize(void *baton, apr_pool_t *pool) SVN_ERR(svn_object_pool__create(&authz_pool, multi_threaded, pool)); SVN_ERR(svn_object_pool__create(&filtered_pool, multi_threaded, pool)); + apr_pool_cleanup_register(pool, NULL, deinit_authz, apr_pool_cleanup_null); return SVN_NO_ERROR; } @@ -889,9 +914,7 @@ create_user_authz(authz_full_t *authz, /* Use a separate sub-pool to keep memory usage tight. */ apr_pool_t *subpool = svn_pool_create(scratch_pool); - /* Find all ACLs for REPOSITORY. - * Note that repo-specific rules replace global rules, - * even if they don't apply to the current user. */ + /* Find all ACLs for REPOSITORY. */ apr_array_header_t *acls = apr_array_make(subpool, authz->acls->nelts, sizeof(authz_acl_t *)); for (i = 0; i < authz->acls->nelts; ++i) @@ -908,15 +931,36 @@ create_user_authz(authz_full_t *authz, = APR_ARRAY_IDX(acls, acls->nelts - 1, const authz_acl_t *); if (svn_authz__compare_paths(&prev_acl->rule, &acl->rule) == 0) { + svn_boolean_t global_acl_applies; + svn_boolean_t repos_acl_applies; + + /* Previous ACL is a global rule. */ SVN_ERR_ASSERT_NO_RETURN(!strcmp(prev_acl->rule.repos, AUTHZ_ANY_REPOSITORY)); + /* Current ACL is a per-repository rule. */ SVN_ERR_ASSERT_NO_RETURN(strcmp(acl->rule.repos, AUTHZ_ANY_REPOSITORY)); - apr_array_pop(acls); + + global_acl_applies = + svn_authz__get_acl_access(NULL, prev_acl, user, repository); + repos_acl_applies = + svn_authz__get_acl_access(NULL, acl, user, repository); + + /* Prefer rules which apply to both this user and this path + * over rules which apply only to the path. In cases where + * both rules apply to user and path, always prefer the + * repository-specific rule. */ + if (!global_acl_applies || repos_acl_applies) + { + apr_array_pop(acls); + APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl; + } } + else + APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl; } - - APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl; + else + APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl; } } diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c index 515600d4f94f5..aad37eea69b2d 100644 --- a/subversion/libsvn_repos/commit.c +++ b/subversion/libsvn_repos/commit.c @@ -308,8 +308,7 @@ add_file_or_directory(const char *path, svn_boolean_t was_copied = FALSE; const char *full_path, *canonicalized_path; - /* Reject paths which contain control characters (related to issue #4340). */ - SVN_ERR(svn_path_check_valid(path, pool)); + SVN_ERR(svn_repos__validate_new_path(path, pool)); SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, pool, pool)); diff --git a/subversion/libsvn_repos/config_file.c b/subversion/libsvn_repos/config_file.c index 4bec766e3dd5d..31b9441ca0da3 100644 --- a/subversion/libsvn_repos/config_file.c +++ b/subversion/libsvn_repos/config_file.c @@ -237,6 +237,10 @@ get_repos_config(svn_stream_t **stream, { /* Search for a repository in the full path. */ repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool); + if (repos_root_dirent == NULL) + return svn_error_trace(handle_missing_file(stream, checksum, access, + url, must_exist, + svn_node_none)); /* Attempt to open a repository at repos_root_dirent. */ SVN_ERR(svn_repos_open3(&access->repos, repos_root_dirent, NULL, diff --git a/subversion/libsvn_repos/log.c b/subversion/libsvn_repos/log.c index d9a1fb1085e16..41ca8aed27174 100644 --- a/subversion/libsvn_repos/log.c +++ b/subversion/libsvn_repos/log.c @@ -337,42 +337,36 @@ detect_changed(svn_repos_revision_access_level_t *access_level, if ( (change->change_kind == svn_fs_path_change_add) || (change->change_kind == svn_fs_path_change_replace)) { - const char *copyfrom_path = change->copyfrom_path; - svn_revnum_t copyfrom_rev = change->copyfrom_rev; - /* the following is a potentially expensive operation since on FSFS we will follow the DAG from ROOT to PATH and that requires actually reading the directories along the way. */ if (!change->copyfrom_known) { - SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, + SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, &change->copyfrom_path, root, path, iterpool)); change->copyfrom_known = TRUE; } - if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) + if (change->copyfrom_path && SVN_IS_VALID_REVNUM(change->copyfrom_rev)) { - svn_boolean_t readable = TRUE; - if (callbacks->authz_read_func) { svn_fs_root_t *copyfrom_root; + svn_boolean_t readable; SVN_ERR(svn_fs_revision_root(©from_root, fs, - copyfrom_rev, iterpool)); + change->copyfrom_rev, iterpool)); SVN_ERR(callbacks->authz_read_func(&readable, copyfrom_root, - copyfrom_path, + change->copyfrom_path, callbacks->authz_read_baton, iterpool)); if (! readable) - found_unreadable = TRUE; - } - - if (readable) - { - change->copyfrom_path = copyfrom_path; - change->copyfrom_rev = copyfrom_rev; + { + found_unreadable = TRUE; + change->copyfrom_path = NULL; + change->copyfrom_rev = SVN_INVALID_REVNUM; + } } } } diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c index 2189de823d201..119f04bd2fe60 100644 --- a/subversion/libsvn_repos/repos.c +++ b/subversion/libsvn_repos/repos.c @@ -2092,3 +2092,13 @@ svn_repos__fs_type(const char **fs_type, svn_dirent_join(repos_path, SVN_REPOS__DB_DIR, pool), pool); } + +svn_error_t * +svn_repos__validate_new_path(const char *path, + apr_pool_t *scratch_pool) +{ + /* Reject paths which contain control characters (related to issue #4340). */ + SVN_ERR(svn_path_check_valid(path, scratch_pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/apr_escape.c b/subversion/libsvn_subr/apr_escape.c new file mode 100644 index 0000000000000..caa25d9fc5734 --- /dev/null +++ b/subversion/libsvn_subr/apr_escape.c @@ -0,0 +1,135 @@ +/* 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 code in this file is copied from APR (initially from APR 1.7.0) + * to provide compatibility for building against APR < 1.5.0. + */ + +#include "private/svn_dep_compat.h" + +#if !APR_VERSION_AT_LEAST(1,5,0) + +#include +#include + +/* from apr_escape_test_char.h */ +#define T_ESCAPE_SHELL_CMD (1) +#define T_ESCAPE_PATH_SEGMENT (2) +#define T_OS_ESCAPE_PATH (4) +#define T_ESCAPE_ECHO (8) +#define T_ESCAPE_URLENCODED (16) +#define T_ESCAPE_XML (32) +#define T_ESCAPE_LDAP_DN (64) +#define T_ESCAPE_LDAP_FILTER (128) + +static const unsigned char test_char_table[256] = { + 224,222,222,222,222,222,222,222,222,222,223,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,6,16,127,22,17,22,49,17, + 145,145,129,80,80,0,0,18,0,0,0,0,0,0,0,0,0,0,16,87, + 119,16,119,23,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,23,223,23,23,0,23,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,23,23,23,17,222,222,222,222,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222, + 222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222 +}; + +/* from apr_encode_private.h */ +#if APR_CHARSET_EBCDIC +#error This Subversion compatibility code for APR<1.5 does not support EBCDIC. +#else /* APR_CHARSET_EBCDIC */ +#define ENCODE_TO_ASCII(ch) (ch) +#define ENCODE_TO_NATIVE(ch) (ch) +#endif /* !APR_CHARSET_EBCDIC */ + +/* we assume the folks using this ensure 0 <= c < 256... which means + * you need a cast to (unsigned char) first, you can't just plug a + * char in here and get it to work, because if char is signed then it + * will first be sign extended. + */ +#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f)) + +APR_DECLARE(apr_status_t) apr_escape_shell(char *escaped, const char *str, + apr_ssize_t slen, apr_size_t *len) +{ + unsigned char *d; + const unsigned char *s; + apr_size_t size = 1; + int found = 0; + + d = (unsigned char *) escaped; + s = (const unsigned char *) str; + + if (s) { + if (d) { + for (; *s && slen; ++s, slen--) { +#if defined(OS2) || defined(WIN32) + /* + * Newlines to Win32/OS2 CreateProcess() are ill advised. + * Convert them to spaces since they are effectively white + * space to most applications + */ + if (*s == '\r' || *s == '\n') { + if (d) { + *d++ = ' '; + found = 1; + } + continue; + } +#endif + if (TEST_CHAR(*s, T_ESCAPE_SHELL_CMD)) { + *d++ = '\\'; + size++; + found = 1; + } + *d++ = *s; + size++; + } + *d = '\0'; + } + else { + for (; *s && slen; ++s, slen--) { + if (TEST_CHAR(*s, T_ESCAPE_SHELL_CMD)) { + size++; + found = 1; + } + size++; + } + } + } + + if (len) { + *len = size; + } + if (!found) { + return APR_NOTFOUND; + } + + return APR_SUCCESS; +} + +#else /* APR_VERSION_AT_LEAST(1,5,0) */ + +/* Silence OSX ranlib warnings about object files with no symbols. */ +#include +extern const apr_uint32_t svn__fake__apr_escape; +const apr_uint32_t svn__fake__apr_escape = 0xdeadbeef; + +#endif /* !APR_VERSION_AT_LEAST(1,5,0) */ diff --git a/subversion/libsvn_subr/cmdline.c b/subversion/libsvn_subr/cmdline.c index d1aad71b234ab..dab4fbeef76eb 100644 --- a/subversion/libsvn_subr/cmdline.c +++ b/subversion/libsvn_subr/cmdline.c @@ -39,9 +39,15 @@ #include /* for STDIN_FILENO */ #include /* for apr_strerror */ +#include +#if APR_VERSION_AT_LEAST(1,5,0) #include +#else +#include "private/svn_dep_compat.h" +#endif #include /* for apr_initialize/apr_terminate */ #include /* for apr_snprintf */ +#include /* for apr_env_get */ #include #include @@ -1235,38 +1241,84 @@ svn_cmdline__be_interactive(svn_boolean_t non_interactive, /* Helper for the edit_externally functions. Set *EDITOR to some path to an - editor binary. Sources to search include: the EDITOR_CMD argument - (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG + editor binary, in native C string on Unix/Linux platforms and in UTF-8 + string on Windows platform. Sources to search include: the EDITOR_CMD + argument (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG is not NULL), $VISUAL, $EDITOR. Return SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */ static svn_error_t * find_editor_binary(const char **editor, const char *editor_cmd, - apr_hash_t *config) + apr_hash_t *config, + apr_pool_t *pool) { const char *e; + const char *e_cfg; struct svn_config_t *cfg; + apr_status_t status; /* Use the editor specified on the command line via --editor-cmd, if any. */ +#ifdef WIN32 + /* On Windows, editor_cmd is transcoded to the system active code page + because we use main() as a entry point without APR's (or our own) wrapper + in command line tools. */ + if (editor_cmd) + { + SVN_ERR(svn_utf_cstring_to_utf8(&e, editor_cmd, pool)); + } + else + { + e = NULL; + } +#else e = editor_cmd; +#endif /* Otherwise look for the Subversion-specific environment variable. */ if (! e) - e = getenv("SVN_EDITOR"); + { + status = apr_env_get((char **)&e, "SVN_EDITOR", pool); + if (status || ! *e) + { + e = NULL; + } + } /* If not found then fall back on the config file. */ if (! e) { cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; - svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS, + svn_config_get(cfg, &e_cfg, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_EDITOR_CMD, NULL); +#ifdef WIN32 + if (e_cfg) + { + /* On Windows, we assume that config values are set in system active + code page, so we need transcode it here. */ + SVN_ERR(svn_utf_cstring_to_utf8(&e, e_cfg, pool)); + } +#else + e = e_cfg; +#endif } /* If not found yet then try general purpose environment variables. */ if (! e) - e = getenv("VISUAL"); + { + status = apr_env_get((char**)&e, "VISUAL", pool); + if (status || ! *e) + { + e = NULL; + } + } if (! e) - e = getenv("EDITOR"); + { + status = apr_env_get((char**)&e, "EDITOR", pool); + if (status || ! *e) + { + e = NULL; + } + } #ifdef SVN_CLIENT_EDITOR /* If still not found then fall back on the hard-coded default. */ @@ -1400,13 +1452,17 @@ svn_cmdline__edit_file_externally(const char *path, apr_pool_t *pool) { const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr; + const char *file_name_local; +#ifdef WIN32 + const WCHAR *wcmd; +#endif char *old_cwd; int sys_err; apr_status_t apr_err; svn_dirent_split(&base_dir, &file_name, path, pool); - SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool)); apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool); if (apr_err) @@ -1423,10 +1479,18 @@ svn_cmdline__edit_file_externally(const char *path, return svn_error_wrap_apr (apr_err, _("Can't change working directory to '%s'"), base_dir); + SVN_ERR(svn_path_cstring_from_utf8(&file_name_local, + escape_path(pool, file_name), pool)); /* editor is explicitly documented as being interpreted by the user's shell, and as such should already be quoted/escaped as needed. */ - cmd = apr_psprintf(pool, "%s %s", editor, escape_path(pool, file_name)); +#ifndef WIN32 + cmd = apr_psprintf(pool, "%s %s", editor, file_name_local); sys_err = system(cmd); +#else + cmd = apr_psprintf(pool, "\"%s %s\"", editor, file_name_local); + SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool)); + sys_err = _wsystem(wcmd); +#endif apr_err = apr_filepath_set(old_cwd, pool); if (apr_err) @@ -1435,10 +1499,16 @@ svn_cmdline__edit_file_externally(const char *path, stderr, TRUE /* fatal */, "svn: "); if (sys_err) - /* Extracting any meaning from sys_err is platform specific, so just - use the raw value. */ - return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, - _("system('%s') returned %d"), cmd, sys_err); + { + const char *cmd_utf8; + + /* Extracting any meaning from sys_err is platform specific, so just + use the raw value. */ + SVN_ERR(svn_path_cstring_to_utf8(&cmd_utf8, cmd, pool)); + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, + _("system('%s') returned %d"), + cmd_utf8, sys_err); + } return SVN_NO_ERROR; } @@ -1458,6 +1528,9 @@ svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, { const char *editor; const char *cmd; +#ifdef WIN32 + const WCHAR *wcmd; +#endif apr_file_t *tmp_file; const char *tmpfile_name; const char *tmpfile_native; @@ -1471,7 +1544,7 @@ svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, int sys_err; svn_boolean_t remove_file = TRUE; - SVN_ERR(find_editor_binary(&editor, editor_cmd, config)); + SVN_ERR(find_editor_binary(&editor, editor_cmd, config, pool)); /* Convert file contents from UTF-8/LF if desired. */ if (as_text) @@ -1581,13 +1654,18 @@ svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, goto cleanup; /* Prepare the editor command line. */ - err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool); + err = svn_path_cstring_from_utf8(&tmpfile_native, + escape_path(pool, tmpfile_name), pool); if (err) goto cleanup; /* editor is explicitly documented as being interpreted by the user's shell, and as such should already be quoted/escaped as needed. */ - cmd = apr_psprintf(pool, "%s %s", editor, escape_path(pool, tmpfile_native)); +#ifndef WIN32 + cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native); +#else + cmd = apr_psprintf(pool, "\"%s %s\"", editor, tmpfile_native); +#endif /* If the caller wants us to leave the file around, return the path of the file we'll use, and make a note not to destroy it. */ @@ -1598,13 +1676,22 @@ svn_cmdline__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */, } /* Now, run the editor command line. */ +#ifndef WIN32 sys_err = system(cmd); +#else + SVN_ERR(svn_utf__win32_utf8_to_utf16(&wcmd, cmd, NULL, pool)); + sys_err = _wsystem(wcmd); +#endif if (sys_err != 0) { + const char *cmd_utf8; + /* Extracting any meaning from sys_err is platform specific, so just use the raw value. */ + SVN_ERR(svn_path_cstring_to_utf8(&cmd_utf8, cmd, pool)); err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, - _("system('%s') returned %d"), cmd, sys_err); + _("system('%s') returned %d"), + cmd_utf8, sys_err); goto cleanup; } @@ -1811,3 +1898,73 @@ svn_cmdline__cancellation_exit(void) #endif } } + +#if defined(WIN32) + +svn_error_t * +svn_cmdline__win32_get_cstring_argv(const char **cstring_argv_p[], + int argc, + const wchar_t *argv[], + apr_pool_t *result_pool) +{ + apr_array_header_t *cstring_argv; + int i; + + cstring_argv = apr_array_make(result_pool, argc + 1, sizeof(const char *)); + + for (i = 0; i < argc; i++) + { + const wchar_t *arg = argv[i]; + char *cstring_arg; + int rv; + BOOL used_default_char; + + /* Passing -1 for the string length guarantees that the returned length + will account for a terminating null character. */ + rv = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, arg, -1, + NULL, 0, NULL, &used_default_char); + if (rv <= 0) + { + return svn_error_wrap_apr(apr_get_os_error(), + _("Conversion from UTF-16 failed")); + } + else if (used_default_char) + { + return svn_error_wrap_apr(APR_FROM_OS_ERROR(ERROR_NO_UNICODE_TRANSLATION), + _("Conversion from UTF-16 failed")); + } + + cstring_arg = apr_palloc(result_pool, rv); + rv = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, arg, -1, + cstring_arg, rv, NULL, &used_default_char); + if (rv <= 0) + { + return svn_error_wrap_apr(apr_get_os_error(), + _("Conversion from UTF-16 failed")); + } + else if (used_default_char) + { + return svn_error_wrap_apr(APR_FROM_OS_ERROR(ERROR_NO_UNICODE_TRANSLATION), + _("Conversion from UTF-16 failed")); + } + + APR_ARRAY_PUSH(cstring_argv, const char *) = cstring_arg; + } + + APR_ARRAY_PUSH(cstring_argv, const char *) = NULL; + + *cstring_argv_p = (const char **)cstring_argv->elts; + return SVN_NO_ERROR; +} + +#endif + +svn_error_t * +svn_cmdline__default_get_cstring_argv(const char **cstring_argv_p[], + int argc, + const char *argv[], + apr_pool_t *result_pool) +{ + *cstring_argv_p = argv; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_subr/config_file.c b/subversion/libsvn_subr/config_file.c index 9c0a83a170f8b..bb12a761a2e79 100644 --- a/subversion/libsvn_subr/config_file.c +++ b/subversion/libsvn_subr/config_file.c @@ -807,6 +807,7 @@ svn_config__parse_stream(svn_stream_t *stream, SVN_ERR(open_section(ctx, &stop)); if (stop) return SVN_NO_ERROR; + ++(ctx->line); break; case '#': /* Comment */ @@ -1442,11 +1443,12 @@ svn_config_ensure(const char *config_dir, apr_pool_t *pool) "### is defined. The command (or environment variable) may contain" NL "### arguments, using standard shell quoting for arguments with" NL "### spaces. The command will be invoked as:" NL - "### svnserve -t" NL - "### (If the URL includes a username, then the hostname will be" NL - "### passed to the tunnel agent as @.) If the" NL - "### built-in ssh scheme were not predefined, it could be defined" NL - "### as:" NL + "### svnserve -t" NL + "### where is the hostname part of the URL. If the URL" NL + "### specified a username and/or a port, those are included in the" NL + "### argument in the usual way: @:."NL + "### If the built-in ssh scheme were not predefined, it could be" NL + "### defined as:" NL "# ssh = $SVN_SSH ssh -q --" NL "### If you wanted to define a new 'rsh' scheme, to be used with" NL "### 'svn+rsh:' URLs, you could do so as follows:" NL diff --git a/subversion/libsvn_subr/eol.c b/subversion/libsvn_subr/eol.c index d04afd71c2e39..2bc1b4fbe2b14 100644 --- a/subversion/libsvn_subr/eol.c +++ b/subversion/libsvn_subr/eol.c @@ -33,34 +33,6 @@ char * svn_eol__find_eol_start(char *buf, apr_size_t len) { -#if SVN_UNALIGNED_ACCESS_IS_OK - - /* Scan the input one machine word at a time. */ - for (; len > sizeof(apr_uintptr_t) - ; buf += sizeof(apr_uintptr_t), len -= sizeof(apr_uintptr_t)) - { - /* This is a variant of the well-known strlen test: */ - apr_uintptr_t chunk = *(const apr_uintptr_t *)buf; - - /* 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 SVN__R_TEST can only be < 0x80, iff it has been \0 before - * (i.e. \r in *BUF). Ditto 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 & SVN__BIT_7_SET) != SVN__BIT_7_SET) - break; - } - -#endif - - /* The remaining odd bytes will be examined the naive way: */ for (; len > 0; ++buf, --len) { if (*buf == '\n' || *buf == '\r') diff --git a/subversion/libsvn_subr/hash.c b/subversion/libsvn_subr/hash.c index 28874f42cc159..10a305ba6d86b 100644 --- a/subversion/libsvn_subr/hash.c +++ b/subversion/libsvn_subr/hash.c @@ -636,16 +636,6 @@ hashfunc_compatible(const char *char_key, apr_ssize_t *klen) if (*klen == APR_HASH_KEY_STRING) *klen = strlen(char_key); -#if SVN_UNALIGNED_ACCESS_IS_OK - for (p = key, i = *klen; i >= 4; i-=4, p+=4) - { - apr_uint32_t chunk = *(const apr_uint32_t *)p; - - /* the ">> 17" part gives upper bits in the chunk a chance to make - some impact as well */ - hash = hash * 33 * 33 * 33 * 33 + chunk + (chunk >> 17); - } -#else for (p = key, i = *klen; i >= 4; i-=4, p+=4) { hash = hash * 33 * 33 * 33 * 33 @@ -654,7 +644,7 @@ hashfunc_compatible(const char *char_key, apr_ssize_t *klen) + p[2] * 33 + p[3]; } -#endif + for (; i; i--, p++) hash = hash * 33 + *p; diff --git a/subversion/libsvn_subr/io.c b/subversion/libsvn_subr/io.c index b351c00751add..d788b3dcad2e5 100644 --- a/subversion/libsvn_subr/io.c +++ b/subversion/libsvn_subr/io.c @@ -2703,7 +2703,6 @@ svn_io__file_lock_autocreate(const char *lock_file, svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, apr_pool_t *pool) { - apr_os_file_t filehand; const char *fname; apr_status_t apr_err; @@ -2713,49 +2712,21 @@ svn_error_t *svn_io_file_flush_to_disk(apr_file_t *file, if (apr_err) return svn_error_wrap_apr(apr_err, _("Can't get file name")); - /* ### In apr 1.4+ we could delegate most of this function to - apr_file_sync(). The only major difference is that this doesn't - contain the retry loop for EINTR on linux. */ - - /* First make sure that any user-space buffered data is flushed. */ - SVN_ERR(svn_io_file_flush(file, pool)); - - apr_os_file_get(&filehand, file); - - /* Call the operating system specific function to actually force the - data to disk. */ - { -#ifdef WIN32 - - if (! FlushFileBuffers(filehand)) - return svn_error_wrap_apr(apr_get_os_error(), - _("Can't flush file '%s' to disk"), - try_utf8_from_internal_style(fname, pool)); - -#else - int rv; - - do { -#ifdef F_FULLFSYNC - rv = fcntl(filehand, F_FULLFSYNC, 0); -#else - rv = fsync(filehand); -#endif - } while (rv == -1 && APR_STATUS_IS_EINTR(apr_get_os_error())); + do { + apr_err = apr_file_datasync(file); + } while(APR_STATUS_IS_EINTR(apr_err)); - /* If the file is in a memory filesystem, fsync() may return - EINVAL. Presumably the user knows the risks, and we can just - ignore the error. */ - if (rv == -1 && APR_STATUS_IS_EINVAL(apr_get_os_error())) - return SVN_NO_ERROR; + /* If the file is in a memory filesystem, fsync() may return + EINVAL. Presumably the user knows the risks, and we can just + ignore the error. */ + if (APR_STATUS_IS_EINVAL(apr_err)) + return SVN_NO_ERROR; - if (rv == -1) - return svn_error_wrap_apr(apr_get_os_error(), - _("Can't flush file '%s' to disk"), - try_utf8_from_internal_style(fname, pool)); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Can't flush file '%s' to disk"), + try_utf8_from_internal_style(fname, pool)); -#endif - } return SVN_NO_ERROR; } diff --git a/subversion/libsvn_subr/iter.c b/subversion/libsvn_subr/iter.c index 6d04846e83782..1a303b5119458 100644 --- a/subversion/libsvn_subr/iter.c +++ b/subversion/libsvn_subr/iter.c @@ -143,3 +143,29 @@ svn_iter__break(void) { return &internal_break_error; } + +#if !APR_VERSION_AT_LEAST(1, 5, 0) +const void *apr_hash_this_key(apr_hash_index_t *hi) +{ + const void *key; + + apr_hash_this((apr_hash_index_t *)hi, &key, NULL, NULL); + return key; +} + +apr_ssize_t apr_hash_this_key_len(apr_hash_index_t *hi) +{ + apr_ssize_t klen; + + apr_hash_this((apr_hash_index_t *)hi, NULL, &klen, NULL); + return klen; +} + +void *apr_hash_this_val(apr_hash_index_t *hi) +{ + void *val; + + apr_hash_this((apr_hash_index_t *)hi, NULL, NULL, &val); + return val; +} +#endif diff --git a/subversion/libsvn_subr/opt.c b/subversion/libsvn_subr/opt.c index 1a9beb646f89a..2832553d1f415 100644 --- a/subversion/libsvn_subr/opt.c +++ b/subversion/libsvn_subr/opt.c @@ -326,7 +326,7 @@ print_command_info3(const svn_opt_subcommand_desc3_t *cmd, } } - if (!verbose) + if (!verbose && global_options && *global_options) SVN_ERR(svn_cmdline_fputs(_("\n(Use '-v' to show global and experimental options.)\n"), stream, pool)); if (have_options) diff --git a/subversion/libsvn_subr/string.c b/subversion/libsvn_subr/string.c index 243e4b555c712..8cd36b3d897a7 100644 --- a/subversion/libsvn_subr/string.c +++ b/subversion/libsvn_subr/string.c @@ -1508,20 +1508,6 @@ svn_cstring__match_length(const char *a, { apr_size_t pos = 0; -#if SVN_UNALIGNED_ACCESS_IS_OK - - /* Chunky processing is so much faster ... - * - * We can't make this work on architectures that require aligned access - * because A and B will probably have different alignment. So, skipping - * the first few chars until alignment is reached is not an option. - */ - for (; max_len - pos >= sizeof(apr_size_t); pos += sizeof(apr_size_t)) - if (*(const apr_size_t*)(a + pos) != *(const apr_size_t*)(b + pos)) - break; - -#endif - for (; pos < max_len; ++pos) if (a[pos] != b[pos]) break; @@ -1536,22 +1522,6 @@ svn_cstring__reverse_match_length(const char *a, { apr_size_t pos = 0; -#if SVN_UNALIGNED_ACCESS_IS_OK - - /* Chunky processing is so much faster ... - * - * We can't make this work on architectures that require aligned access - * because A and B will probably have different alignment. So, skipping - * the first few chars until alignment is reached is not an option. - */ - for (pos = sizeof(apr_size_t); pos <= max_len; pos += sizeof(apr_size_t)) - if (*(const apr_size_t*)(a - pos) != *(const apr_size_t*)(b - pos)) - break; - - pos -= sizeof(apr_size_t); - -#endif - /* If we find a mismatch at -pos, pos-1 characters matched. */ while (++pos <= max_len) diff --git a/subversion/libsvn_subr/utf_validate.c b/subversion/libsvn_subr/utf_validate.c index 0aab81c9172e4..a2cd62c018274 100644 --- a/subversion/libsvn_subr/utf_validate.c +++ b/subversion/libsvn_subr/utf_validate.c @@ -258,17 +258,6 @@ static const char machine [9][14] = { static const char * first_non_fsm_start_char(const char *data, apr_size_t max_len) { -#if SVN_UNALIGNED_ACCESS_IS_OK - - /* 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; - -#endif - - /* The remaining odd bytes will be examined the naive way: */ for (; max_len > 0; ++data, --max_len) if ((unsigned char)*data >= 0x80) break; diff --git a/subversion/libsvn_subr/version.c b/subversion/libsvn_subr/version.c index 1c50ad0e7a5dd..2bf4fb9100f46 100644 --- a/subversion/libsvn_subr/version.c +++ b/subversion/libsvn_subr/version.c @@ -143,7 +143,7 @@ svn_version_extended(svn_boolean_t verbose, info->build_time = __TIME__; info->build_host = SVN_BUILD_HOST; info->copyright = apr_pstrdup - (pool, _("Copyright (C) 2020 The Apache Software Foundation.\n" + (pool, _("Copyright (C) 2024 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 " diff --git a/subversion/libsvn_wc/deprecated.c b/subversion/libsvn_wc/deprecated.c index 379564bd6da8d..490ed7210ad69 100644 --- a/subversion/libsvn_wc/deprecated.c +++ b/subversion/libsvn_wc/deprecated.c @@ -2799,6 +2799,7 @@ svn_wc__status2_from_3(svn_wc_status2_t **status, } (*status)->entry = entry; + (*status)->locked = old_status->locked; (*status)->copied = old_status->copied; (*status)->repos_lock = svn_lock_dup(old_status->repos_lock, result_pool); diff --git a/subversion/libsvn_wc/wc-metadata.sql b/subversion/libsvn_wc/wc-metadata.sql index e34cf1422e60f..5dd7294f3694d 100644 --- a/subversion/libsvn_wc/wc-metadata.sql +++ b/subversion/libsvn_wc/wc-metadata.sql @@ -636,7 +636,7 @@ 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"; +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 */ diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c index c76a8b1b94477..52ca17ef19c2d 100644 --- a/subversion/libsvn_wc/wc_db.c +++ b/subversion/libsvn_wc/wc_db.c @@ -16644,7 +16644,7 @@ svn_wc__db_find_working_nodes_with_basename(apr_array_header_t **local_abspaths, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRESENT_HIGHEST_WORKING_NODES_BY_BASENAME_AND_KIND)); SVN_ERR(svn_sqlite__bindf(stmt, "ist", wcroot->wc_id, basename, - kind_map, kind)); + kind_map_none, kind)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); *local_abspaths = apr_array_make(result_pool, 1, sizeof(const char *)); @@ -16688,7 +16688,7 @@ svn_wc__db_find_copies_of_repos_path(apr_array_header_t **local_abspaths, SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_COPIES_OF_REPOS_RELPATH)); SVN_ERR(svn_sqlite__bindf(stmt, "ist", wcroot->wc_id, repos_relpath, - kind_map, kind)); + kind_map_none, kind)); SVN_ERR(svn_sqlite__step(&have_row, stmt)); *local_abspaths = apr_array_make(result_pool, 1, sizeof(const char *)); diff --git a/subversion/mod_dav_svn/lock.c b/subversion/mod_dav_svn/lock.c index 7e9c94b64d02d..d2a6aa9021e27 100644 --- a/subversion/mod_dav_svn/lock.c +++ b/subversion/mod_dav_svn/lock.c @@ -36,6 +36,7 @@ #include "svn_pools.h" #include "svn_props.h" #include "private/svn_log.h" +#include "private/svn_repos_private.h" #include "dav_svn.h" @@ -717,6 +718,12 @@ append_locks(dav_lockdb *lockdb, /* Commit a 0-byte file: */ + if ((serr = svn_repos__validate_new_path(resource->info->repos_path, + resource->pool))) + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + resource->pool); + if ((serr = dav_svn__get_youngest_rev(&rev, repos, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not determine youngest revision", diff --git a/subversion/mod_dav_svn/repos.c b/subversion/mod_dav_svn/repos.c index 8cbd5e70e69d7..c411a173b43d4 100644 --- a/subversion/mod_dav_svn/repos.c +++ b/subversion/mod_dav_svn/repos.c @@ -2928,6 +2928,16 @@ open_stream(const dav_resource *resource, if (kind == svn_node_none) /* No existing file. */ { + serr = svn_repos__validate_new_path(resource->info->repos_path, + resource->pool); + + if (serr != NULL) + { + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + resource->pool); + } + serr = svn_fs_make_file(resource->info->root.root, resource->info->repos_path, resource->pool); @@ -4120,6 +4130,14 @@ create_collection(dav_resource *resource) return err; } + if ((serr = svn_repos__validate_new_path(resource->info->repos_path, + resource->pool)) != NULL) + { + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + resource->pool); + } + if ((serr = svn_fs_make_dir(resource->info->root.root, resource->info->repos_path, resource->pool)) != NULL) @@ -4194,6 +4212,12 @@ copy_resource(const dav_resource *src, return err; } + serr = svn_repos__validate_new_path(dst->info->repos_path, dst->pool); + if (serr) + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + dst->pool); + src_repos_path = svn_repos_path(src->info->repos->repos, src->pool); dst_repos_path = svn_repos_path(dst->info->repos->repos, dst->pool); @@ -4430,6 +4454,12 @@ move_resource(dav_resource *src, if (err) return err; + serr = svn_repos__validate_new_path(dst->info->repos_path, dst->pool); + if (serr) + return dav_svn__convert_err(serr, HTTP_BAD_REQUEST, + "Request specifies an invalid path.", + dst->pool); + /* Copy the src to the dst. */ serr = svn_fs_copy(src->info->root.root, /* the root object of src rev*/ src->info->repos_path, /* the relative path of src */ diff --git a/subversion/po/de.po b/subversion/po/de.po index 06aeaeff391c5..c4530e155b81b 100644 --- a/subversion/po/de.po +++ b/subversion/po/de.po @@ -6562,7 +6562,7 @@ msgstr "P2L-Indexeintrag für Revision r%ld ist nicht zusammenhängend zwischen #: ../libsvn_fs_fs/verify.c:696 #, fuzzy, c-format -msgid "p2l index entry for changes in revision r%ld is item %ld of type %d at offset %s" +msgid "p2l index entry for changes in revision r%ld is item %s of type %u at offset %s" msgstr "P2L-Indexeintrag für Revision r%ld ist nicht zusammenhängend zwischen Offsets %s und %s" #: ../libsvn_fs_util/fs-util.c:164 diff --git a/subversion/po/sv.po b/subversion/po/sv.po index 0e25c0b17d187..53505114f7fb4 100644 --- a/subversion/po/sv.po +++ b/subversion/po/sv.po @@ -6854,8 +6854,8 @@ msgstr "p2l-indexpost för revision r%ld på position %s innehåller ogiltig obj #: ../libsvn_fs_fs/verify.c:696 #, c-format -msgid "p2l index entry for changes in revision r%ld is item %ld of type %d at offset %s" -msgstr "p2l-indexpost för ändringar i revision r%ld är objekt %ld av typ %d på position %s" +msgid "p2l index entry for changes in revision r%ld is item %s of type %u at offset %s" +msgstr "p2l-indexpost för ändringar i revision r%ld är objekt %s av typ %u på position %s" #: ../libsvn_fs_util/fs-util.c:164 msgid "Filesystem object has not been opened yet" diff --git a/subversion/svn/cl-conflicts.c b/subversion/svn/cl-conflicts.c index 0dc0abf291141..24a4607b62b70 100644 --- a/subversion/svn/cl-conflicts.c +++ b/subversion/svn/cl-conflicts.c @@ -452,7 +452,7 @@ append_tree_conflict_info_xml(svn_stringbuf_t *str, repos_root_url, repos_relpath, peg_rev, node_kind, pool)); - SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(&repos_relpath, + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(&repos_relpath, &peg_rev, &node_kind, conflict, @@ -529,7 +529,7 @@ svn_cl__append_conflict_info_xml(svn_stringbuf_t *str, repos_root_url, repos_relpath, peg_rev, node_kind, scratch_pool)); - SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( &repos_relpath, &peg_rev, &node_kind, conflict, scratch_pool, scratch_pool)); if (repos_root_url && repos_relpath) @@ -576,7 +576,7 @@ svn_cl__append_conflict_info_xml(svn_stringbuf_t *str, repos_root_url, repos_relpath, peg_rev, node_kind, scratch_pool)); - SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( &repos_relpath, &peg_rev, &node_kind, conflict, scratch_pool, scratch_pool)); if (repos_root_url && repos_relpath) diff --git a/subversion/svn/filesize.c b/subversion/svn/filesize.c index ba1c35626b611..c6e2bc40fd8da 100644 --- a/subversion/svn/filesize.c +++ b/subversion/svn/filesize.c @@ -88,15 +88,24 @@ format_size(double human_readable_size, + 1 nul terminator --- = 5 characters of space needed in the buffer. */ - char buffer[8]; + char buffer[64]; + + assert(absolute_human_readable_size < 1000); + + /* When the adjusted size has only one significant digit left of + the decimal point, show tenths of a unit, too. Except when + the absolute size is actually a single-digit number, because + files can't have fractional byte sizes. */ + if (absolute_human_readable_size >= 10) + sprintf(buffer, "%.0f", human_readable_size); + else + { + double integral; + const double frac = modf(absolute_human_readable_size, &integral); + const int decimals = (index > 0 && (integral < 9 || frac <= .949999999)); + sprintf(buffer, "%.*f", decimals, human_readable_size); + } - assert(absolute_human_readable_size < 1000.0); - - /* When the adjusted size has only one significant digit left of the - decimal point, show tenths of a unit, too. */ - sprintf(buffer, "%.*f", - absolute_human_readable_size < 10.0 ? 1 : 0, - human_readable_size); return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL); } @@ -138,8 +147,9 @@ get_base2_unit_file_size(svn_filesize_t size, assert(index < order_size - 1); ++index; } + human_readable_size = (index == 0 ? (double)size - : (size >> 3 * index) / 128.0 / index); + : (size >> (10 * index - 10)) / 1024.0); return format_size(human_readable_size, long_units, order, index, result_pool); @@ -160,7 +170,7 @@ get_base10_unit_file_size(svn_filesize_t size, {APR_INT64_C( 999999999999), " TB", "T"}, /* tera */ {APR_INT64_C( 999999999999999), " EB", "E"}, /* exa */ {APR_INT64_C(999999999999999999), " PB", "P"} /* peta */ - /* 18446744073709551615 is the maximum value. */ + /* 9223372036854775807 is the maximum value. */ }; static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); @@ -170,20 +180,29 @@ get_base10_unit_file_size(svn_filesize_t size, /* Adjust the size to the given order of magnitude. - This is division by (order[index].mask + 1), which is the base-1000 - magnitude of the size. For large file sizes, we split the operation - into an integer and a floating-point division, so that we don't + This is division by (order[index].mask + 1), which is the + base-1000 magnitude of the size. We split the operation into an + integer and a floating-point division, so that we don't overflow the mantissa. */ if (index == 0) human_readable_size = (double)size; - else if (index <= 3) - human_readable_size = (double)size / (order[index].mask + 1); else { - /* [ Keep integer division here! ] */ - const double divisor = (double)((order[index].mask + 1) / 1000000); - human_readable_size = (size / 1000000) / divisor; - /* [ And here! ] */ + const svn_filesize_t divisor = (order[index - 1].mask + 1); + /* [Keep integer arithmetic here!] */ + human_readable_size = (size / divisor) / 1000.0; + } + + /* Adjust index and number for rounding. */ + if (human_readable_size >= 999.5) + { + /* This assertion should never fail, because we only have one + decimal digit in the petabyte range and so the number of + petabytes can't be large enough to cause the program flow + to enter this conditional block. */ + assert(index < order_size - 1); + human_readable_size /= 1000.0; + ++index; } return format_size(human_readable_size, diff --git a/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c index fe2d1bd3fba19..4a6a7e1863ad6 100644 --- a/subversion/svn/propget-cmd.c +++ b/subversion/svn/propget-cmd.c @@ -272,14 +272,18 @@ print_properties(svn_stream_t *out, 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 = apr_hash_this_val(apr_hash_first(pool, - iprop->prop_hash)); + svn_prop_inherited_item_t *iprop; + svn_string_t *propval; + + svn_pool_clear(iterpool); + + iprop = APR_ARRAY_IDX(inherited_props, i, + svn_prop_inherited_item_t *); + propval = apr_hash_this_val(apr_hash_first(iterpool, + iprop->prop_hash)); + SVN_ERR(print_single_prop(propval, target_abspath_or_url, iprop->path_or_url, path_prefix, out, pname, diff --git a/subversion/svn/proplist-cmd.c b/subversion/svn/proplist-cmd.c index 80e0364f3da83..583cbf5a50ea0 100644 --- a/subversion/svn/proplist-cmd.c +++ b/subversion/svn/proplist-cmd.c @@ -75,6 +75,8 @@ proplist_receiver_xml(void *baton, svn_prop_inherited_item_t *iprop = APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); + svn_pool_clear(iterpool); + sb = NULL; if (svn_path_is_url(iprop->path_or_url)) diff --git a/subversion/svn/svn.c b/subversion/svn/svn.c index 3c3ac6d5068e1..16cc92119cb9d 100644 --- a/subversion/svn/svn.c +++ b/subversion/svn/svn.c @@ -2019,7 +2019,10 @@ add_commands(const svn_opt_subcommand_desc3_t *cmds_add, * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; int opt_id; @@ -2045,12 +2048,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) apr_hash_t *cfg_hash; svn_membuf_t buf; svn_boolean_t read_pass_from_stdin = FALSE; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -2194,6 +2200,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) while (*s == 'r') s++; changeno_end = strtol(s, &end, 10); + + if (changeno_end < 0) + { + return svn_error_createf( + SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Negative number in range (%s)" + " not supported with -c"), + change_str); + } } if (end == change_str || *end != '\0') { @@ -2202,12 +2217,20 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) "given to -c"), change_str); } - if (changeno == 0) + if (changeno == 0 || changeno_end == 0) { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("There is no change 0")); } + /* The revision number cannot contain a double minus */ + if (changeno < 0 && is_negative) + { + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Non-numeric change argument " + "(%s) given to -c"), change_str); + } + if (is_negative) changeno = -changeno; @@ -3307,7 +3330,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnadmin/svnadmin.c b/subversion/svnadmin/svnadmin.c index 7adc635fd293e..cc14ee9dfa194 100644 --- a/subversion/svnadmin/svnadmin.c +++ b/subversion/svnadmin/svnadmin.c @@ -97,6 +97,7 @@ check_lib_versions(void) /** Subcommands. **/ static svn_opt_subcommand_t + subcommand_build_repcache, subcommand_crashtest, subcommand_create, subcommand_delrevprop, @@ -306,6 +307,16 @@ static const apr_getopt_option_t options_table[] = */ static const svn_opt_subcommand_desc3_t cmd_table[] = { + {"build-repcache", subcommand_build_repcache, {0}, {N_( + "usage: svnadmin build-repcache REPOS_PATH [-r LOWER[:UPPER]]\n" + "\n"), N_( + "Add missing entries to the representation cache for the repository\n" + "at REPOS_PATH. Process data in revisions LOWER through UPPER.\n" + "If no revision arguments are given, process all revisions. If only\n" + "LOWER revision argument is given, process only that single revision.\n" + )}, + {'r', 'q', 'M'} }, + {"crashtest", subcommand_crashtest, {0}, {N_( "usage: svnadmin crashtest REPOS_PATH\n" "\n"), N_( @@ -341,7 +352,8 @@ static const svn_opt_subcommand_desc3_t cmd_table[] = "2. Delete the property NAME on transaction TXN.\n" )}, {'r', 't', svnadmin__use_pre_revprop_change_hook, - svnadmin__use_post_revprop_change_hook} }, + svnadmin__use_post_revprop_change_hook}, + { {'r', "specify revision number ARG"} } }, {"deltify", subcommand_deltify, {0}, {N_( "usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n" @@ -530,7 +542,8 @@ static const svn_opt_subcommand_desc3_t cmd_table[] = "The size includes revision properties and excludes FSFS indexes.\n" )}, {'r', 'q', 'M'}, - { {'q', "print only the size and a newline"} } }, + { {'r', "specify revision number ARG"}, + {'q', "print only the size and a newline"} }, }, {"rmlocks", subcommand_rmlocks, {0}, {N_( "usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n" @@ -559,7 +572,8 @@ static const svn_opt_subcommand_desc3_t cmd_table[] = "NOTE: Revision properties are not versioned, so this command will\n" "overwrite the previous log message.\n" )}, - {'r', svnadmin__bypass_hooks} }, + {'r', svnadmin__bypass_hooks}, + { {'r', "specify revision number ARG"} }, }, {"setrevprop", subcommand_setrevprop, {0}, {N_( "usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n" @@ -577,7 +591,8 @@ static const svn_opt_subcommand_desc3_t cmd_table[] = "2. Set the property NAME on transaction TXN to the contents of FILE.\n" )}, {'r', 't', svnadmin__use_pre_revprop_change_hook, - svnadmin__use_post_revprop_change_hook} }, + svnadmin__use_post_revprop_change_hook}, + { {'r', "specify revision number ARG"} }, }, {"setuuid", subcommand_setuuid, {0}, {N_( "usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n" @@ -2915,10 +2930,116 @@ subcommand_rev_size(apr_getopt_t *os, void *baton, apr_pool_t *pool) SVN_ERR(revision_size(&rev_size, svn_repos_fs(repos), revision, pool)); if (opt_state->quiet) - SVN_ERR(svn_cmdline_printf(pool, "%"APR_OFF_T_FMT"\n", rev_size)); + { + SVN_ERR(svn_cmdline_printf(pool, "%"APR_OFF_T_FMT"\n", rev_size)); + } else - SVN_ERR(svn_cmdline_printf(pool, _("%12"APR_OFF_T_FMT" bytes in revision %ld\n"), - rev_size, revision)); + { + const char *rev_size_str = apr_psprintf(pool, + "%12" APR_INT64_T_FMT, rev_size); + SVN_ERR(svn_cmdline_printf(pool, _("%s bytes in revision %ld\n"), + rev_size_str, revision)); + } + return SVN_NO_ERROR; +} + +static void +build_rep_cache_progress_func(svn_revnum_t revision, + void *baton, + apr_pool_t *pool) +{ + svn_error_clear(svn_cmdline_printf(pool, + _("* Processed revision %ld.\n"), + revision)); +} + +static svn_error_t * +build_rep_cache(svn_fs_t *fs, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + struct svnadmin_opt_state *opt_state, + apr_pool_t *pool) +{ + svn_fs_fs__ioctl_build_rep_cache_input_t input = {0}; + svn_error_t *err; + + input.start_rev = start_rev; + input.end_rev = end_rev; + + if (opt_state->quiet) + { + input.progress_func = NULL; + input.progress_baton = NULL; + } + else + { + input.progress_func = build_rep_cache_progress_func; + input.progress_baton = NULL; + } + + err = svn_fs_ioctl(fs, SVN_FS_FS__IOCTL_BUILD_REP_CACHE, + &input, NULL, + check_cancel, NULL, pool, pool); + if (err && err->apr_err == SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE) + { + return svn_error_quick_wrapf(err, + _("Building rep-cache is not implemented " + "for the filesystem type found in '%s'"), + svn_fs_path(fs, pool)); + } + else if (err && err->apr_err == SVN_ERR_FS_REP_SHARING_NOT_ALLOWED) + { + svn_error_clear(err); + SVN_ERR(svn_cmdline_printf(pool, + _("svnadmin: Warning - this repository has rep-sharing disabled." + " Building rep-cache has no effect.\n"))); + return SVN_NO_ERROR; + } + else + { + return err; + } +} + +/* This implements `svn_opt_subcommand_t'. */ +static svn_error_t * +subcommand_build_repcache(apr_getopt_t *os, void *baton, apr_pool_t *pool) +{ + struct svnadmin_opt_state *opt_state = baton; + svn_repos_t *repos; + svn_fs_t *fs; + svn_revnum_t youngest; + svn_revnum_t lower; + svn_revnum_t upper; + + /* Expect no more arguments. */ + SVN_ERR(parse_args(NULL, os, 0, 0, pool)); + + SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool)); + fs = svn_repos_fs(repos); + SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); + + SVN_ERR(get_revnum(&lower, &opt_state->start_revision, + youngest, repos, pool)); + SVN_ERR(get_revnum(&upper, &opt_state->end_revision, + youngest, repos, pool)); + + if (SVN_IS_VALID_REVNUM(lower) && SVN_IS_VALID_REVNUM(upper)) + { + if (lower > upper) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("First revision cannot be higher than second")); + } + else if (SVN_IS_VALID_REVNUM(lower)) + { + upper = lower; + } + else + { + upper = youngest; + } + + SVN_ERR(build_rep_cache(fs, lower, upper, opt_state, pool)); return SVN_NO_ERROR; } @@ -2932,7 +3053,10 @@ subcommand_rev_size(apr_getopt_t *os, void *baton, apr_pool_t *pool) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; apr_status_t apr_err; @@ -2944,12 +3068,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) apr_array_header_t *received_opts; int i; svn_boolean_t dash_F_arg = FALSE; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); @@ -3329,7 +3456,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnbench/svnbench.c b/subversion/svnbench/svnbench.c index 33e7b69a20b1b..967bdf7ac8535 100644 --- a/subversion/svnbench/svnbench.c +++ b/subversion/svnbench/svnbench.c @@ -386,7 +386,10 @@ add_search_pattern_group(svn_cl__opt_state_t *opt_state, * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; int opt_id; @@ -405,6 +408,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) ra_progress_baton_t ra_progress_baton = {0}; svn_membuf_t buf; svn_boolean_t read_pass_from_stdin = FALSE; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); @@ -414,6 +418,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -1039,7 +1045,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svndumpfilter/svndumpfilter.c b/subversion/svndumpfilter/svndumpfilter.c index a948b3a4ceaa0..272303be3c87c 100644 --- a/subversion/svndumpfilter/svndumpfilter.c +++ b/subversion/svndumpfilter/svndumpfilter.c @@ -1291,7 +1291,10 @@ subcommand_include(apr_getopt_t *os, void *baton, apr_pool_t *pool) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; apr_status_t apr_err; @@ -1302,10 +1305,13 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) int opt_id; apr_array_header_t *received_opts; int i; + const char **argv; /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Initialize the FS library. */ @@ -1564,7 +1570,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnfsfs/svnfsfs.c b/subversion/svnfsfs/svnfsfs.c index 6fcb792cc5863..1ff49fa9d2567 100644 --- a/subversion/svnfsfs/svnfsfs.c +++ b/subversion/svnfsfs/svnfsfs.c @@ -228,7 +228,10 @@ subcommand__help(apr_getopt_t *os, void *baton, apr_pool_t *pool) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; apr_status_t apr_err; @@ -239,12 +242,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) int opt_id; apr_array_header_t *received_opts; int i; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); @@ -473,7 +479,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnlook/svnlook.c b/subversion/svnlook/svnlook.c index d6f9506ba56d9..135bc0819e8c5 100644 --- a/subversion/svnlook/svnlook.c +++ b/subversion/svnlook/svnlook.c @@ -2466,7 +2466,10 @@ subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; apr_status_t apr_err; @@ -2477,12 +2480,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) int opt_id; apr_array_header_t *received_opts; int i; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); @@ -2849,7 +2855,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnmucc/svnmucc.c b/subversion/svnmucc/svnmucc.c index ce4a1ce99d336..3cf5dc38ea46a 100644 --- a/subversion/svnmucc/svnmucc.c +++ b/subversion/svnmucc/svnmucc.c @@ -286,7 +286,9 @@ help(FILE *stream, apr_pool_t *pool) " mv SRC-URL DST-URL : move SRC-URL to DST-URL\n" " rm URL : delete URL\n" " put SRC-FILE URL : add or modify file URL with contents copied from\n" - " SRC-FILE (use \"-\" to read from standard input)\n" + " SRC-FILE (to read from standard input, use \"--\"\n" + " to stop option processing followed by \"-\" to\n" + " indicate standard input)\n" " propset NAME VALUE URL : set property NAME on URL to VALUE\n" " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n" " propdel NAME URL : delete property NAME from URL\n" @@ -465,7 +467,10 @@ log_message_func(const char **log_msg, * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { apr_array_header_t *actions = apr_array_make(pool, 1, sizeof(struct action *)); @@ -531,10 +536,13 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) struct log_message_baton lmb; int i; svn_boolean_t read_pass_from_stdin = FALSE; + const char **argv; /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + /* Initialize the RA library. */ SVN_ERR(svn_ra_initialize(pool)); @@ -978,7 +986,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnrdump/svnrdump.c b/subversion/svnrdump/svnrdump.c index 500a5f9ea3bf1..aa88b4f0afe54 100644 --- a/subversion/svnrdump/svnrdump.c +++ b/subversion/svnrdump/svnrdump.c @@ -784,7 +784,10 @@ validate_and_resolve_revisions(opt_baton_t *opt_baton, * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err = SVN_NO_ERROR; const svn_opt_subcommand_desc3_t *subcommand = NULL; @@ -806,6 +809,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) apr_array_header_t *received_opts; int i; svn_boolean_t read_pass_from_stdin = FALSE; + const char **argv; + + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); opt_baton = apr_pcalloc(pool, sizeof(*opt_baton)); opt_baton->start_revision.kind = svn_opt_revision_unspecified; @@ -1155,7 +1161,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnserve/svnserve.c b/subversion/svnserve/svnserve.c index a92c3eb1007bc..aebc0404b2663 100644 --- a/subversion/svnserve/svnserve.c +++ b/subversion/svnserve/svnserve.c @@ -703,7 +703,10 @@ check_lib_versions(void) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { enum run_mode run_mode = run_mode_unspecified; svn_boolean_t foreground = FALSE; @@ -742,6 +745,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) svn_node_kind_t kind; apr_size_t min_thread_count = THREADPOOL_MIN_SIZE; apr_size_t max_thread_count = THREADPOOL_MAX_SIZE; + const char **argv; + #ifdef SVN_HAVE_SASL SVN_ERR(cyrus_init(pool)); #endif @@ -749,6 +754,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); @@ -1395,7 +1402,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnsync/svnsync.c b/subversion/svnsync/svnsync.c index 7c1c0efbf7cfd..12b1c989e1d90 100644 --- a/subversion/svnsync/svnsync.c +++ b/subversion/svnsync/svnsync.c @@ -1963,7 +1963,10 @@ help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { const svn_opt_subcommand_desc3_t *subcommand = NULL; apr_array_header_t *received_opts; @@ -1978,10 +1981,13 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) apr_array_header_t *config_options = NULL; const char *source_prop_encoding = NULL; svn_boolean_t force_interactive = FALSE; + const char **argv; /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + SVN_ERR(svn_ra_initialize(pool)); /* Initialize the option baton. */ @@ -2402,7 +2408,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/svnversion/svnversion.c b/subversion/svnversion/svnversion.c index da65800467fda..111db531fd4de 100644 --- a/subversion/svnversion/svnversion.c +++ b/subversion/svnversion/svnversion.c @@ -124,7 +124,10 @@ check_lib_versions(void) * program. Obviously we don't want to have to run svn when building svn. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { const char *wc_path, *trail_url; const char *local_abspath; @@ -146,10 +149,13 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) N_("no progress (only errors) to stderr")}, {0, 0, 0, 0} }; + const char **argv; /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -289,7 +295,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/subversion/tests/client/filesize-test.c b/subversion/tests/client/filesize-test.c new file mode 100644 index 0000000000000..1e6a977e308f7 --- /dev/null +++ b/subversion/tests/client/filesize-test.c @@ -0,0 +1,131 @@ +/* filesize-test.c --- tests for svn_cl__format_file_size + * + * ==================================================================== + * 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/filesize.c" + +#include "../svn_test.h" + +typedef struct test_data_t +{ + svn_filesize_t size; + const char* result; +} test_data_t; + + +static svn_error_t * +test_base2_file_size(apr_pool_t *pool) +{ + static const test_data_t data[] = + { + {APR_INT64_C( 1), "1 B"}, + {APR_INT64_C( 9), "9 B"}, + {APR_INT64_C( 13), "13 B"}, + {APR_INT64_C( 999), "999 B"}, + {APR_INT64_C( 1000), "1.0 KiB"}, + {APR_INT64_C( 1024), "1.0 KiB"}, + {APR_INT64_C( 3000), "2.9 KiB"}, + {APR_INT64_C( 1000000), "977 KiB"}, + {APR_INT64_C( 1048576), "1.0 MiB"}, + {APR_INT64_C( 1000000000), "954 MiB"}, + {APR_INT64_C( 1000000000000), "931 GiB"}, + {APR_INT64_C( 1000000000000000), "909 TiB"}, + {APR_INT64_C(1000000000000000000), "888 EiB"}, + {APR_INT64_C(9223372036854775807), "8.0 PiB"}, + }; + static const apr_size_t data_size = sizeof(data) / sizeof(data[0]); + + apr_size_t index; + for (index = 0; index < data_size; ++index) + { + const char *result; + SVN_ERR(svn_cl__format_file_size(&result, data[index].size, + SVN_CL__SIZE_UNIT_BASE_2, + TRUE, pool)); + SVN_TEST_STRING_ASSERT(result, data[index].result); + /* fprintf(stderr, "%s\t%" APR_INT64_T_FMT "\n", result, data[index].size); */ + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_base10_file_size(apr_pool_t *pool) +{ + static const test_data_t data[] = + { + {APR_INT64_C( 1), "1 B"}, + {APR_INT64_C( 9), "9 B"}, + {APR_INT64_C( 13), "13 B"}, + {APR_INT64_C( 999), "999 B"}, + {APR_INT64_C( 1000), "1.0 kB"}, + {APR_INT64_C( 3000), "3.0 kB"}, + {APR_INT64_C( 999499), "999 kB"}, + {APR_INT64_C( 999501), "1.0 MB"}, + {APR_INT64_C( 1000000), "1.0 MB"}, + {APR_INT64_C( 9900000), "9.9 MB"}, + {APR_INT64_C( 9950001), "10 MB"}, + {APR_INT64_C( 99400001), "99 MB"}, + {APR_INT64_C( 99500001), "100 MB"}, + {APR_INT64_C( 999444444), "999 MB"}, + {APR_INT64_C( 999999999), "1.0 GB"}, + {APR_INT64_C( 1000000000), "1.0 GB"}, + {APR_INT64_C( 1100000000), "1.1 GB"}, + {APR_INT64_C( 1000000000000), "1.0 TB"}, + {APR_INT64_C( 1000000000000000), "1.0 EB"}, + {APR_INT64_C( 999000000000000000), "999 EB"}, + {APR_INT64_C( 999500000000000000), "1.0 PB"}, + {APR_INT64_C(1000000000000000000), "1.0 PB"}, + {APR_INT64_C(1090000000000000000), "1.1 PB"}, + {APR_INT64_C(9223372036854775807), "9.2 PB"}, + }; + static const apr_size_t data_size = sizeof(data) / sizeof(data[0]); + + apr_size_t index; + for (index = 0; index < data_size; ++index) + { + const char *result; + SVN_ERR(svn_cl__format_file_size(&result, data[index].size, + SVN_CL__SIZE_UNIT_BASE_10, + TRUE, pool)); + SVN_TEST_STRING_ASSERT(result, data[index].result); + /* fprintf(stderr, "%s\t%" APR_INT64_T_FMT "\n", result, data[index].size); */ + } + + return SVN_NO_ERROR; +} + + +/* The test table. */ + +static int max_threads = 3; + +static struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_PASS2(test_base2_file_size, + "base-2 human-friendly file size"), + SVN_TEST_PASS2(test_base10_file_size, + "base-10 human-friendly file size"), + SVN_TEST_NULL + }; + +SVN_TEST_MAIN diff --git a/subversion/tests/cmdline/authz_tests.py b/subversion/tests/cmdline/authz_tests.py index 760cb3663d02f..92e8a5e1935c9 100755 --- a/subversion/tests/cmdline/authz_tests.py +++ b/subversion/tests/cmdline/authz_tests.py @@ -1731,6 +1731,60 @@ def empty_group(sbox): '--username', svntest.main.wc_author, sbox.repo_url) +@Skip(svntest.main.is_ra_type_file) +def log_inaccessible_copyfrom(sbox): + "log doesn't leak inaccessible copyfrom paths" + + sbox.build(empty=True) + sbox.simple_add_text('secret', 'private') + sbox.simple_commit(message='log message for r1') + sbox.simple_copy('private', 'public') + sbox.simple_commit(message='log message for r2') + + svntest.actions.enable_revprop_changes(sbox.repo_dir) + # Remove svn:date and svn:author for predictable output. + svntest.actions.run_and_verify_svn(None, [], 'propdel', '--revprop', + '-r2', 'svn:date', sbox.repo_url) + svntest.actions.run_and_verify_svn(None, [], 'propdel', '--revprop', + '-r2', 'svn:author', sbox.repo_url) + + write_restrictive_svnserve_conf(sbox.repo_dir) + + # First test with blanket access. + write_authz_file(sbox, + {"/" : "* = rw"}) + expected_output = svntest.verify.ExpectedOutput([ + "------------------------------------------------------------------------\n", + "r2 | (no author) | (no date) | 1 line\n", + "Changed paths:\n", + " A /public (from /private:1)\n", + "\n", + "log message for r2\n", + "------------------------------------------------------------------------\n", + ]) + svntest.actions.run_and_verify_svn(expected_output, [], + 'log', '-r2', '-v', + sbox.repo_url) + + # Now test with an inaccessible copy source (/private). + write_authz_file(sbox, + {"/" : "* = rw"}, + {"/private" : "* ="}) + expected_output = svntest.verify.ExpectedOutput([ + "------------------------------------------------------------------------\n", + "r2 | (no author) | (no date) | 1 line\n", + "Changed paths:\n", + # The copy is shown as a plain add with no copyfrom info. + " A /public\n", + "\n", + # No log message, as the revision is only partially visible. + "\n", + "------------------------------------------------------------------------\n", + ]) + svntest.actions.run_and_verify_svn(expected_output, [], + 'log', '-r2', '-v', + sbox.repo_url) + ######################################################################## # Run the tests @@ -1771,6 +1825,7 @@ def empty_group(sbox): inverted_group_membership, group_member_empty_string, empty_group, + log_inaccessible_copyfrom, ] serial_only = True diff --git a/subversion/tests/cmdline/basic_tests.py b/subversion/tests/cmdline/basic_tests.py index 30bb62b346edd..164f1d309e937 100755 --- a/subversion/tests/cmdline/basic_tests.py +++ b/subversion/tests/cmdline/basic_tests.py @@ -696,8 +696,8 @@ def basic_conflict(sbox): # "Extra" files that we expect to result from the conflicts. # These are expressed as list of regexps. What a cool system! :-) - extra_files = ['mu.*\.r1', 'mu.*\.r2', 'mu.*\.mine', - 'rho.*\.r1', 'rho.*\.r2', 'rho.*\.mine',] + extra_files = [r'mu.*\.r1', r'mu.*\.r2', r'mu.*\.mine', + r'rho.*\.r1', r'rho.*\.r2', r'rho.*\.mine',] # Do the update and check the results in three ways. # All "extra" files are passed to detect_conflict_files(). @@ -2258,11 +2258,11 @@ def automatic_conflict_resolution(sbox): # "Extra" files that we expect to result from the conflicts. # These are expressed as list of regexps. What a cool system! :-) - extra_files = ['mu.*\.r1', 'mu.*\.r2', 'mu.*\.mine', - 'lambda.*\.r1', 'lambda.*\.r2', 'lambda.*\.mine', - 'omega.*\.r1', 'omega.*\.r2', 'omega.*\.mine', - 'rho.*\.r1', 'rho.*\.r2', 'rho.*\.mine', - 'tau.*\.r1', 'tau.*\.r2', 'tau.*\.mine', + extra_files = [r'mu.*\.r1', r'mu.*\.r2', r'mu.*\.mine', + r'lambda.*\.r1', r'lambda.*\.r2', r'lambda.*\.mine', + r'omega.*\.r1', r'omega.*\.r2', r'omega.*\.mine', + r'rho.*\.r1', r'rho.*\.r2', r'rho.*\.mine', + r'tau.*\.r1', r'tau.*\.r2', r'tau.*\.mine', ] # Do the update and check the results in three ways. @@ -2338,7 +2338,7 @@ def automatic_conflict_resolution(sbox): ""])) # Set the expected extra files for the test - extra_files = ['omega.*\.r1', 'omega.*\.r2', 'omega.*\.mine',] + extra_files = [r'omega.*\.r1', r'omega.*\.r2', r'omega.*\.mine',] # Set the expected status for the test expected_status = svntest.actions.get_virginal_state(wc_backup, 2) @@ -3243,6 +3243,61 @@ def filtered_ls_top_level_path(sbox): [], [], 'ls', f_path, '--search=*/*', *extra_opts) +@SkipUnless(svntest.main.is_os_windows) +def argv_with_best_fit_chars(sbox): + """argv with best fit chars""" + + import ctypes + from ctypes import windll, wintypes + + CP_ACP = 0 + kernel32 = windll.kernel32 + WideCharToMultiByte = kernel32.WideCharToMultiByte + WideCharToMultiByte.argtypes = [ + wintypes.UINT, wintypes.DWORD, wintypes.LPCWSTR, ctypes.c_int, + wintypes.LPSTR, ctypes.c_int, wintypes.LPCSTR, wintypes.LPBOOL, + ] + WideCharToMultiByte.restype = ctypes.c_int + codepage = kernel32.GetACP() + + def regexlines(*patterns): + return svntest.verify.RegexListOutput(list(patterns), match_all=True) + + def iter_bestfit_chars(): + chars = {b'"': 0, b'\\': 0, b' ': 0} + for c in range(0x80, 0x10000): + wcs = ctypes.create_unicode_buffer(chr(c)) + mbcs = ctypes.create_string_buffer(8) + rc = WideCharToMultiByte(CP_ACP, 0, wcs, len(wcs), mbcs, len(mbcs), None, + None) + if rc == 0: + continue + mbcs = mbcs.value + if chars.get(mbcs) != 0: + continue + chars[mbcs] = c + yield chr(c), mbcs + + count = 0 + # E721113: Conversion from UTF-16 failed: No mapping for the Unicode + # character exists in the target multi-byte code page. + expected_stderr = 'svn: E721113: ' + for wc, mbcs in iter_bestfit_chars(): + count += 1 + logger.info('Code page %r - U+%04x -> 0x%s', codepage, ord(wc), mbcs.hex()) + if mbcs == b'"': + svntest.actions.run_and_verify_svn2(None, expected_stderr, 1, 'help', + 'foo{0} {0}bar'.format(wc)) + elif mbcs == b'\\': + svntest.actions.run_and_verify_svn2(None, expected_stderr, 1, 'help', + 'foo{0}" {0}"bar'.format(wc)) + elif mbcs == b' ': + svntest.actions.run_and_verify_svn2(None, expected_stderr, 1, 'help', + 'foo{0}bar'.format(wc)) + if count == 0: + raise svntest.Skip('No best fit characters in code page %r' % codepage) + + ######################################################################## # Run the tests @@ -3318,6 +3373,7 @@ def filtered_ls_top_level_path(sbox): null_update_last_changed_revision, null_prop_update_last_changed_revision, filtered_ls_top_level_path, + argv_with_best_fit_chars, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/blame_tests.py b/subversion/tests/cmdline/blame_tests.py index 8620dd4195825..36467778f8ca9 100755 --- a/subversion/tests/cmdline/blame_tests.py +++ b/subversion/tests/cmdline/blame_tests.py @@ -958,8 +958,7 @@ def blame_youngest_to_oldest(sbox): sbox.simple_commit() #r3 # Delete a line. - with open(iota_moved, 'w') as f: - f.write(line) + svntest.main.file_write(iota_moved, line) sbox.simple_commit() #r4 expected_output = [ diff --git a/subversion/tests/cmdline/changelist_tests.py b/subversion/tests/cmdline/changelist_tests.py index 91cedc8d46a04..9f230ed26eb6c 100755 --- a/subversion/tests/cmdline/changelist_tests.py +++ b/subversion/tests/cmdline/changelist_tests.py @@ -130,9 +130,9 @@ def clname_from_lastchar_cb(full_path): # Regular expressions for 'svn changelist' output. -_re_cl_rem_pattern = "^D \[(.*)\] (.*)" -_re_cl_skip = re.compile("Skipped '(.*)'") -_re_cl_add = re.compile("^A \[(.*)\] (.*)") +_re_cl_rem_pattern = r"^D \[(.*)\] (.*)" +_re_cl_skip = re.compile(r"Skipped '(.*)'") +_re_cl_add = re.compile(r"^A \[(.*)\] (.*)") _re_cl_rem = re.compile(_re_cl_rem_pattern) def verify_changelist_output(output, expected_adds=None, diff --git a/subversion/tests/cmdline/checkout_tests.py b/subversion/tests/cmdline/checkout_tests.py index 49165e78fe245..ecf0cf4bfac5e 100755 --- a/subversion/tests/cmdline/checkout_tests.py +++ b/subversion/tests/cmdline/checkout_tests.py @@ -882,8 +882,8 @@ def co_with_obstructing_local_adds(sbox): }) # "Extra" files that we expect to result from the conflicts. - extra_files = ['eta\.r0', 'eta\.r2', 'eta\.mine', - 'kappa\.r0', 'kappa\.r2', 'kappa\.mine'] + extra_files = [r'eta\.r0', r'eta\.r2', r'eta\.mine', + r'kappa\.r0', r'kappa\.r2', r'kappa\.mine'] # Perform the checkout and check the results in three ways. # We use --force here because run_and_verify_checkout() will delete diff --git a/subversion/tests/cmdline/copy_tests.py b/subversion/tests/cmdline/copy_tests.py index efc4deee2d33e..b55c1051d4595 100755 --- a/subversion/tests/cmdline/copy_tests.py +++ b/subversion/tests/cmdline/copy_tests.py @@ -1370,7 +1370,7 @@ def repos_to_wc_copy_eol_keywords(sbox): if re.match(b'[^\\r]\\n', raw_contents): raise svntest.Failure - if not re.match(b'.*\$LastChangedRevision:\s*\d+\s*\$', line_contents[3]): + if not re.match(b'.*\\$LastChangedRevision:\\s*\\d+\\s*\\$', line_contents[3]): raise svntest.Failure #------------------------------------------------------------- diff --git a/subversion/tests/cmdline/davautocheck.sh b/subversion/tests/cmdline/davautocheck.sh index e88f760672153..6cde16c492d39 100755 --- a/subversion/tests/cmdline/davautocheck.sh +++ b/subversion/tests/cmdline/davautocheck.sh @@ -334,7 +334,7 @@ fi # Stop any previous instances, os we can re-use the port. if [ -x $STOPSCRIPT ]; then $STOPSCRIPT ; sleep 1; fi -ss > /dev/null 2>&1 || netstat > /dev/null 2>&1 || fail "unable to find ss or netstat required to find a free port" +ss -n > /dev/null 2>&1 || netstat -n > /dev/null 2>&1 || fail "unable to find ss or netstat required to find a free port" HTTPD_PORT=3691 while \ @@ -547,10 +547,9 @@ Alias /fsdavroot $ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/fsdavroot __EOF__ -location_common() { +location_common_without_authz() { cat >> "$HTTPD_CFG" <<__EOF__ DAV svn - AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz" AuthType Basic AuthName "Subversion Repository" AuthUserFile $HTTPD_USERS @@ -560,6 +559,12 @@ cat >> "$HTTPD_CFG" <<__EOF__ SVNBlockRead ${BLOCK_READ_SETTING} __EOF__ } +location_common() { +location_common_without_authz +cat >> "$HTTPD_CFG" <<__EOF__ + AuthzSVNAccessFile "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/authz" +__EOF__ +} location_common cat >> "$HTTPD_CFG" <<__EOF__ SVNParentPath "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/repositories" @@ -610,6 +615,15 @@ cat >> "$HTTPD_CFG" <<__EOF__ ${SVN_PATH_AUTHZ_LINE} + +__EOF__ +location_common_without_authz +cat >> "$HTTPD_CFG" <<__EOF__ + SVNParentPath "$ABS_BUILDDIR/subversion/tests/cmdline/svn-test-work/repositories" + Require valid-user + Satisfy Any + AuthzSVNReposRelativeAccessFile "^/authz" + __EOF__ location_common diff --git a/subversion/tests/cmdline/diff_tests.py b/subversion/tests/cmdline/diff_tests.py index 14df65b7c1024..caf689584679a 100755 --- a/subversion/tests/cmdline/diff_tests.py +++ b/subversion/tests/cmdline/diff_tests.py @@ -833,7 +833,7 @@ def diff_head_of_moved_file(sbox): ' This is the file \'mu\'.\n', '+\n', '+Actually, it\'s a new mu.\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] svntest.actions.run_and_verify_svn(expected_output, [], @@ -1024,7 +1024,7 @@ def strip_eols(lines): None, [], 'diff', '-r', 'BASE:2', wc_dir) # to do the comparison, remove all output lines starting with +++ or --- - re_infoline = re.compile('^(\+\+\+|---).*$') + re_infoline = re.compile(r'^(\+\+\+|---).*$') list1 = [] list2 = [] @@ -4256,7 +4256,7 @@ def diff_dir_replaced_by_file(sbox): '+++ %s\t(working copy)\n' % sbox.path('A/B/E'), '@@ -0,0 +1 @@\n', '+text\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] svntest.actions.run_and_verify_svn(expected_output, [], @@ -4464,7 +4464,7 @@ def diff_local_missing_obstruction(sbox): 'Added: K\n', '## -0,0 +1 ##\n', '+V\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', 'Index: %s\n' % (sbox.path('iota'),), '===================================================================\n', '--- %s\t(revision 1)\n' % (sbox.path('iota'),), @@ -4475,7 +4475,7 @@ def diff_local_missing_obstruction(sbox): 'Added: K\n', '## -0,0 +1 ##\n', '+V\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ] svntest.actions.run_and_verify_svn(expected_output, [], 'diff', wc_dir) @@ -4639,7 +4639,7 @@ def diff_repo_repo_added_file_mime_type(sbox): 'Deleted: svn:mime-type\n', '## -1 +0,0 ##\n', '-text/plain\n', - '\ No newline at end of property\n'] + '\\ No newline at end of property\n'] svntest.actions.run_and_verify_svn(expected_output, [], 'diff', '-r2:1', newfile) @@ -4661,7 +4661,7 @@ def diff_switched_file(sbox): '@@ -1 +1,2 @@\n', ' This is the file \'mu\'.\n', '+Mu????\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] svntest.actions.run_and_verify_svn(expected_output, [], 'diff', '-r', '1', sbox.ospath('iota')) @@ -4676,7 +4676,7 @@ def diff_switched_file(sbox): '-This is the file \'iota\'.\n', '+This is the file \'mu\'.\n', '+Mu????\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] svntest.actions.run_and_verify_svn(expected_output, [], 'diff', '-r', '1', sbox.ospath('')) @@ -4709,7 +4709,7 @@ def diff_parent_dir(sbox): 'Deleted: A\n', '## -1 +0,0 ##\n', '-B\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ] svntest.actions.run_and_verify_svn(expected_output, [], @@ -4726,7 +4726,7 @@ def diff_parent_dir(sbox): 'Deleted: A\n', '## -1 +0,0 ##\n', '-B\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ] svntest.actions.run_and_verify_svn(expected_output, [], @@ -4823,7 +4823,7 @@ def diff_local_copied_dir(sbox): 'Added: p2\n', '## -0,0 +1 ##\n', '+v2\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ] svntest.actions.run_and_verify_svn(expected_output_C2, [], @@ -5036,14 +5036,14 @@ def diff_symlinks(sbox): '+++ %s\t(working copy)\n' % sbox.path('to-iota'), '@@ -0,0 +1 @@\n', '+link iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '\n', 'Property changes on: %s\n' % sbox.path('to-iota'), '___________________________________________________________________\n', 'Added: svn:special\n', '## -0,0 +1 ##\n', '+*\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ], [], 'diff', wc_dir) svntest.actions.run_and_verify_svn([ @@ -5055,14 +5055,14 @@ def diff_symlinks(sbox): '+++ b/to-iota\t(working copy)\n', '@@ -0,0 +1 @@\n', '+iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '\n', 'Property changes on: to-iota\n', '___________________________________________________________________\n', 'Added: svn:special\n', '## -0,0 +1 ##\n', '+*\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ], [], 'diff', wc_dir, '--git') sbox.simple_commit() @@ -5076,9 +5076,9 @@ def diff_symlinks(sbox): '+++ %s\t(working copy)\n' % sbox.path('to-iota'), '@@ -1 +1 @@\n', '-link iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '+link A/B/E/alpha\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ], [], 'diff', wc_dir) svntest.actions.run_and_verify_svn([ @@ -5090,9 +5090,9 @@ def diff_symlinks(sbox): '+++ b/to-iota\t(working copy)\n', '@@ -1 +1 @@\n', '-iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '+A/B/E/alpha\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ], [], 'diff', wc_dir, '--git') @@ -5329,6 +5329,41 @@ def diff_nonexistent_in_wc(sbox): svntest.actions.run_and_verify_svn(expected_output_head_base, [], 'diff', '-r', '1') +def diff_invalid_change_arg(sbox): + "invalid change argument" + + sbox.build() + + svntest.actions.run_and_verify_svn( + None, + (r'.*svn: E205000: Non-numeric change argument \(--1\) given to -c'), + 'diff', sbox.wc_dir, '-c', '--1') + + svntest.actions.run_and_verify_svn( + None, + (r'.*svn: E205000: Non-numeric change argument \(-r-1\) given to -c'), + 'diff', sbox.wc_dir, '-c', '-r-1') + + svntest.actions.run_and_verify_svn( + None, + (r'.*svn: E205000: Negative number in range \(1--3\) not supported with -c'), + 'diff', sbox.wc_dir, '-c', '1--3') + + # 'r' is not a number + svntest.actions.run_and_verify_svn( + None, + (r'.*svn: E205000: Non-numeric change argument \(r1--r3\) given to -c'), + 'diff', sbox.wc_dir, '-c', 'r1--r3') + + svntest.actions.run_and_verify_svn( + None, + (r'.*svn: E205000: Negative number in range \(r1-r-3\) not supported with -c'), + 'diff', sbox.wc_dir, '-c', 'r1-r-3') + + svntest.actions.run_and_verify_svn( + None, + (r'.*svn: E205000: There is no change 0'), + 'diff', sbox.wc_dir, '-c', '1-0') ######################################################################## #Run the tests @@ -5431,6 +5466,7 @@ def diff_nonexistent_in_wc(sbox): diff_file_replaced_by_symlink, diff_git_format_copy, diff_nonexistent_in_wc, + diff_invalid_change_arg, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/entries-dump.c b/subversion/tests/cmdline/entries-dump.c index 469a2ff0a97f3..886ac9e8fad7d 100644 --- a/subversion/tests/cmdline/entries-dump.c +++ b/subversion/tests/cmdline/entries-dump.c @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ #include "svn_pools.h" #include "svn_wc.h" #include "svn_dirent_uri.h" +#include "svn_xml.h" #include "private/svn_wc_private.h" @@ -41,14 +43,61 @@ #include "../../libsvn_wc/lock.h" static void -str_value(const char *name, const char *value) +print_prefix(void) +{ + puts("if b'' == '':\n" + " def _to_str(s):\n" + " return s\n" + "else:\n" + " def _to_str(s):\n" + " return s.decode('utf-8', 'surrogateescape')\n" + "\n" + "class Entry(object):\n" + " \"\"\"An Entry object represents one node's entry in a pre-1.6" + " .svn/entries file.\n\n" + "Similar to #svn_wc_entry_t, but not all fields are populated.\n\n" + "Entry objects are generated by the 'entries-dump'" + " test helper tool.\"\"\"\n\n" + " if b'' == '':\n" + " def set_strval(self, name, val):\n" + " self.__setattr__(name, val)\n" + " else:\n" + " def set_strval(self, name, val):\n" + " global _to_str\n" + " self.__setattr__(name, _to_str(val))\n"); +} + +static void +print_as_bytes(const char *val) +{ + printf("b'"); + while(*val) + { + printf("\\x%02x", (unsigned int)(unsigned char)*val++); + } + printf("'"); +} + +static void +str_value(const char *name, const char *value, apr_pool_t *pool) { if (value == NULL) printf("e.%s = None\n", name); else - printf("e.%s = '%s'\n", name, value); -} + { + svn_stringbuf_t *escaped_value = NULL; + svn_xml_escape_attr_cstring(&escaped_value, value, pool); + /* Print the human-readable value. */ + assert(NULL == strchr(escaped_value->data, '\n')); + printf("# e.%s = '%s'\n", name, escaped_value->data); + + /* Print the machine-readable value. */ + printf("e.set_strval('%s', ", name); + print_as_bytes(value); + printf(")\n"); + } +} static void int_value(const char *name, long int value) @@ -76,6 +125,7 @@ entries_dump(const char *dir_path, svn_wc_adm_access_t *related, apr_pool_t *poo svn_error_t *err; svn_wc_context_t *wc_ctx = NULL; const char *dir_abspath; + apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_dirent_get_absolute(&dir_abspath, dir_path, pool)); @@ -124,44 +174,45 @@ entries_dump(const char *dir_path, svn_wc_adm_access_t *related, apr_pool_t *poo for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) { + svn_stringbuf_t *escaped_key; const char *key = apr_hash_this_key(hi); const svn_wc_entry_t *entry = apr_hash_this_val(hi); + svn_pool_clear(iterpool); SVN_ERR_ASSERT(strcmp(key, entry->name) == 0); - printf("e = Entry()\n"); - str_value("name", entry->name); + str_value("name", entry->name, iterpool); int_value("revision", entry->revision); - str_value("url", entry->url); - str_value("repos", entry->repos); - str_value("uuid", entry->uuid); + str_value("url", entry->url, iterpool); + str_value("repos", entry->repos, iterpool); + str_value("uuid", entry->uuid, iterpool); int_value("kind", entry->kind); int_value("schedule", entry->schedule); bool_value("copied", entry->copied); bool_value("deleted", entry->deleted); bool_value("absent", entry->absent); bool_value("incomplete", entry->incomplete); - str_value("copyfrom_url", entry->copyfrom_url); + str_value("copyfrom_url", entry->copyfrom_url, iterpool); int_value("copyfrom_rev", entry->copyfrom_rev); - str_value("conflict_old", entry->conflict_old); - str_value("conflict_new", entry->conflict_new); - str_value("conflict_wrk", entry->conflict_wrk); - str_value("prejfile", entry->prejfile); + str_value("conflict_old", entry->conflict_old, iterpool); + str_value("conflict_new", entry->conflict_new, iterpool); + str_value("conflict_wrk", entry->conflict_wrk, iterpool); + str_value("prejfile", entry->prejfile, iterpool); /* skip: text_time */ /* skip: prop_time */ /* skip: checksum */ int_value("cmt_rev", entry->cmt_rev); /* skip: cmt_date */ - str_value("cmt_author", entry->cmt_author); - str_value("lock_token", entry->lock_token); - str_value("lock_owner", entry->lock_owner); - str_value("lock_comment", entry->lock_comment); + str_value("cmt_author", entry->cmt_author, iterpool); + str_value("lock_token", entry->lock_token, iterpool); + str_value("lock_owner", entry->lock_owner, iterpool); + str_value("lock_comment", entry->lock_comment, iterpool); /* skip: lock_creation_date */ /* skip: has_props */ /* skip: has_prop_mods */ /* skip: cachable_props */ /* skip: present_props */ - str_value("changelist", entry->changelist); + str_value("changelist", entry->changelist, iterpool); /* skip: working_size */ /* skip: keep_local */ int_value("depth", entry->depth); @@ -170,8 +221,17 @@ entries_dump(const char *dir_path, svn_wc_adm_access_t *related, apr_pool_t *poo /* skip: file_external_peg_rev */ /* skip: file_external_rev */ bool_value("locked", locked && *entry->name == '\0'); - printf("entries['%s'] = e\n", (const char *)key); + /* Print the human-readable value. */ + escaped_key = NULL; + svn_xml_escape_attr_cstring(&escaped_key, key, iterpool); + assert(NULL == strchr(escaped_key->data, '\n')); + printf("# entries['%s'] = e\n", escaped_key->data); + /* Print the machine-readable value. */ + printf("entries[_to_str("); + print_as_bytes(key); + printf(")] = e\n"); } + svn_pool_destroy(iterpool); if (wc_ctx) SVN_ERR(svn_wc_context_destroy(wc_ctx)); @@ -282,6 +342,7 @@ tree_dump_dir(const char *local_abspath, { struct directory_walk_baton *bt = walk_baton; const char *path; + svn_stringbuf_t *escaped_path; if (kind != svn_node_dir) return SVN_NO_ERROR; @@ -307,7 +368,15 @@ tree_dump_dir(const char *local_abspath, printf("entries = {}\n"); SVN_ERR(entries_dump(path, bt->adm_access, scratch_pool)); - printf("dirs['%s'] = entries\n", path); + /* Print the human-readable value. */ + escaped_path = NULL; + svn_xml_escape_attr_cstring(&escaped_path, path, scratch_pool); + assert(NULL == strchr(escaped_path->data, '\n')); + printf("# dirs['%s'] = entries\n", escaped_path->data); + /* Print the machine-readable value. */ + printf("dirs[_to_str("); + print_as_bytes(path); + printf(")] = entries\n"); return SVN_NO_ERROR; } @@ -384,15 +453,25 @@ main(int argc, const char *argv[]) cmd = NULL; if (!cmd || !strcmp(cmd, "--entries")) - err = entries_dump(path, NULL, pool); + { + print_prefix(); + err = entries_dump(path, NULL, pool); + } else if (!strcmp(cmd, "--subdirs")) - err = directory_dump(path, pool); + { + err = directory_dump(path, pool); + } else if (!strcmp(cmd, "--tree-dump")) - err = tree_dump(path, pool); + { + print_prefix(); + err = tree_dump(path, pool); + } else - err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - "Invalid command '%s'", - cmd); + { + err = svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + "Invalid command '%s'", + cmd); + } if (err) { svn_handle_error2(err, stderr, FALSE, "entries-dump: "); diff --git a/subversion/tests/cmdline/getopt_tests.py b/subversion/tests/cmdline/getopt_tests.py index ed6470c5f8eff..0cb35b47084dc 100755 --- a/subversion/tests/cmdline/getopt_tests.py +++ b/subversion/tests/cmdline/getopt_tests.py @@ -94,7 +94,7 @@ def load_expected_output(basename): 'version X.Y.Z '), # The copyright end date keeps changing; fix forever. (re.compile(r'Copyright \(C\) 20\d\d The Apache ' - 'Software Foundation\.'), + r'Software Foundation\.'), 'Copyright (C) YYYY The Apache Software Foundation'), # In 'svn --version --quiet', we print only the version # number in a single line. @@ -120,7 +120,7 @@ def load_expected_output(basename): # seen switch_res_line. switched_rep_lines_res = [ # We don't care about the actual canonical host - (re.compile('^\* running on.*$'), '* running on'), + (re.compile(r'^\* running on.*$'), '* running on'), ] def process_lines(lines): diff --git a/subversion/tests/cmdline/info_tests.py b/subversion/tests/cmdline/info_tests.py index 39b8ce21e3948..aa4de40a51e73 100755 --- a/subversion/tests/cmdline/info_tests.py +++ b/subversion/tests/cmdline/info_tests.py @@ -96,7 +96,7 @@ def match_xml_element(str, exprs): atttribute_re = re.compile( r"\s+(?P[\w-]+)\s*=\s*(['\"])(?P[^'\"]*)\2", re.M) self_closing_re = re.compile(r"\s*/>", re.M) - content_re_str = "\\s*>(?P.*?)" + content_re_str = r"\s*>(?P.*?)" m = start_tag_re.match(str) if not m: @@ -402,7 +402,7 @@ def info_repos_root_url(sbox): 'Path' : re.escape(os.path.basename(sbox.repo_dir)), 'Repository Root' : re.escape(sbox.repo_url), 'URL' : re.escape(sbox.repo_url), - 'Relative URL' : '\^/', # escape ^ -- this isn't a regexp + 'Relative URL' : r'\^/', # escape ^ -- this isn't a regexp 'Revision' : '1', 'Node Kind' : 'directory', 'Last Changed Rev' : '1', @@ -412,7 +412,7 @@ def info_repos_root_url(sbox): 'Name' : 'iota', 'Repository Root' : re.escape(sbox.repo_url), 'URL' : re.escape(sbox.repo_url + '/iota'), - 'Relative URL' : '\^/iota', # escape ^ -- this isn't a regexp + 'Relative URL' : r'\^/iota', # escape ^ -- this isn't a regexp 'Revision' : '1', 'Node Kind' : 'file', 'Last Changed Rev' : '1', @@ -843,6 +843,127 @@ def info_file_in_file_replaced_dir(sbox): svntest.actions.run_and_verify_info([expected], sbox.repo_url + '/dir/file@1') +@Issue(4869) +def info_tree_conflict_source(sbox): + "info --xml: verify source-left, source-right" + + sbox.build() + wc_dir = sbox.wc_dir + + B_path = os.path.join(wc_dir, 'A', 'B') + lambda_path = os.path.join(B_path, 'lambda') + alpha_path = os.path.join(B_path, 'E', 'alpha') + F_path = os.path.join(B_path, 'F') + + B2_url = sbox.repo_url + '/A/B2' + B2_path = os.path.join(wc_dir, 'A', 'B2') + lambda2_path = os.path.join(B2_path, 'lambda') + + # Rev 2 copy B to B2 + sbox.simple_repo_copy('A/B', 'A/B2') + sbox.simple_update() + + # Rev 3: + # edit A/B/lambda to test text conflict case + # add property to A/B/E/alpha to test property conflict case + # rename A/B/F to A/B/Z to test tree conflict case + + svntest.main.file_write(lambda_path, 'B/lambda side of conflict') + sbox.simple_propset('blue', 'azul', 'A/B/E/alpha') + sbox.simple_move('A/B/F', 'A/B/Z'); + sbox.simple_commit() + + # Rev 4: + # edit A/B2/lambda + # add property to A/B2/E/alpha + # rename A/B2/F to A/B2/Y + + svntest.main.file_write(lambda2_path, 'B2/lambda side of conflict') + sbox.simple_propset('blue', 'bleue', 'A/B2/E/alpha') + sbox.simple_move('A/B2/F', 'A/B2/Y'); + sbox.simple_commit() + + # Now merge B2 into B to cause a text conflict, property conflict, and + # tree conflict + sbox.simple_update() + + svntest.actions.run_and_verify_svn2(svntest.verify.AnyOutput, [], + 0, 'merge', B2_url, B_path) + + # Verify 'svn info --xml' on the text conflicted case + + exit_code, output, error = svntest.actions.run_and_verify_svn(None, + [], 'info', + lambda_path, + '--xml') + + verify_xml_elements(output, + [('version', {'revision' : '1', + 'side' : 'source-left', + 'kind' : 'file', + 'path-in-repos': 'A/B/lambda', + 'repos-url' : sbox.repo_url, + }, + )]) + + verify_xml_elements(output, + [('version', {'revision' : '4', + 'side' : 'source-right', + 'kind' : 'file', + 'path-in-repos': 'A/B2/lambda', + 'repos-url' : sbox.repo_url, + }, + )]) + + # Verify 'svn info --xml' on the property conflicted case + + exit_code, output, error = svntest.actions.run_and_verify_svn(None, + [], 'info', + alpha_path, + '--xml') + + verify_xml_elements(output, + [('version', {'revision' : '1', + 'side' : 'source-left', + 'kind' : 'file', + 'path-in-repos': 'A/B/E/alpha', + 'repos-url' : sbox.repo_url, + }, + )]) + + verify_xml_elements(output, + [('version', {'revision' : '4', + 'side' : 'source-right', + 'kind' : 'file', + 'path-in-repos': 'A/B2/E/alpha', + 'repos-url' : sbox.repo_url, + }, + )]) + + # Verify 'svn info --xml' on the tree conflicted case + + exit_code, output, error = svntest.actions.run_and_verify_svn(None, + [], 'info', + F_path, + '--xml') + + verify_xml_elements(output, + [('version', {'revision' : '1', + 'side' : 'source-left', + 'kind' : 'dir', + 'path-in-repos': 'A/B/F', + 'repos-url' : sbox.repo_url, + }, + )]) + + verify_xml_elements(output, + [('version', {'revision' : '4', + 'side' : 'source-right', + 'kind' : 'none', + 'path-in-repos': 'A/B2/F', + 'repos-url' : sbox.repo_url, + }, + )]) ######################################################################## # Run the tests @@ -869,6 +990,7 @@ def info_file_in_file_replaced_dir(sbox): info_item_size_repos_recursive, info_item_failures, info_file_in_file_replaced_dir, + info_tree_conflict_source, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/legacy/utf8_tests.py b/subversion/tests/cmdline/legacy/utf8_tests.py index 9b903a89df350..2c533ed744766 100755 --- a/subversion/tests/cmdline/legacy/utf8_tests.py +++ b/subversion/tests/cmdline/legacy/utf8_tests.py @@ -143,7 +143,7 @@ def basic_utf8_conversion(sbox): # Also check that the environment contains the expected locale settings # either by default, or because we set them above. if localematch: - localeregex = re.compile('^en_US\.ISO-?8859-1$', re.I) + localeregex = re.compile(r'^en_US\.ISO-?8859-1$', re.I) for env in [ 'LC_ALL', 'LC_CTYPE', 'LANG' ]: env_value = os.getenv(env) if env_value: diff --git a/subversion/tests/cmdline/lock_tests.py b/subversion/tests/cmdline/lock_tests.py index 3e2952c86d4dc..4207b2aa6cb79 100755 --- a/subversion/tests/cmdline/lock_tests.py +++ b/subversion/tests/cmdline/lock_tests.py @@ -91,8 +91,8 @@ def lock_file(sbox): '-m', '', file_path) # --- Meanwhile, in our other working copy... --- - err_re = "(svn\: E195022\: File '.*iota' is locked in another)|" + \ - "(svn\: E160039: User '?jconstant'? does not own lock on path.*iota')" + err_re = r"(svn\: E195022\: File '.*iota' is locked in another)|" + \ + r"(svn\: E160039: User '?jconstant'? does not own lock on path.*iota')" svntest.main.run_svn(None, 'update', wc_b) # -- Try to change a file -- @@ -112,8 +112,8 @@ def lock_file(sbox): # change the locked file's properties svntest.main.run_svn(None, 'propset', 'sneakyuser', 'Sally', file_path_b) - err_re = "(svn\: E195022\: File '.*iota' is locked in another)|" + \ - "(svn\: E160039\: User '?jconstant'? does not own lock on path)" + err_re = r"(svn\: E195022\: File '.*iota' is locked in another)|" + \ + r"(svn\: E160039\: User '?jconstant'? does not own lock on path)" # attempt (and fail) to commit as user Sally svntest.actions.run_and_verify_commit(wc_b, None, None, err_re, @@ -1275,8 +1275,8 @@ def ls_url_encoded(sbox): [], "lock", fname) # Make sure ls shows it being locked. - expected_output = " +2 " + re.escape(svntest.main.wc_author) + " +O .+f|" \ - " +2 " + re.escape(svntest.main.wc_author) + " .+\./" + expected_output = r" +2 %(author)s +O .+f| +2 %(author)s .+\./" % \ + {'author': re.escape(svntest.main.wc_author)} svntest.actions.run_and_verify_svn(expected_output, [], "list", "-v", dirname) diff --git a/subversion/tests/cmdline/log_tests.py b/subversion/tests/cmdline/log_tests.py index 559dfb509cd69..2b18353d7dc92 100755 --- a/subversion/tests/cmdline/log_tests.py +++ b/subversion/tests/cmdline/log_tests.py @@ -471,8 +471,7 @@ def parse_log_output(log_lines, with_diffs=False): # Regular expression to match the header line of a log message, with # these groups: (revision number), (author), (date), (num lines). - header_re = re.compile('^r([0-9]+) \| ' \ - + '([^|]*) \| ([^|]*) \| ([0-9]+) lines?') + header_re = re.compile(r'^r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) lines?') # The log chain to return. chain = [] @@ -984,10 +983,10 @@ def escape_control_chars(sbox): # Verify the output contains either the expected fuzzy escape # sequence, or the literal control char. - match_unescaped_ctrl_re = "This msg contains a Ctrl-T \(.\) " \ - "and a Ctrl-I \(\t\)\." - match_escaped_ctrl_re = "^This msg contains a Ctrl-T \(\?\\\\020\) " \ - "and a Ctrl-I \(\t\)\." + match_unescaped_ctrl_re = r"This msg contains a Ctrl-T \(.\) " \ + r"and a Ctrl-I \(\t\)\." + match_escaped_ctrl_re = r"^This msg contains a Ctrl-T \(\?\\020\) " \ + r"and a Ctrl-I \(\t\)\." matched = None for line in output: if re.match(match_unescaped_ctrl_re, line) \ @@ -1490,8 +1489,8 @@ def retrieve_revprops(sbox): # Can't set revprops with log. svntest.actions.run_and_verify_log_xml( - expected_stderr=(".*cannot assign with 'with-revprop' option" - " \(drop the '='\)"), + expected_stderr=(r".*cannot assign with 'with-revprop' option" + r" \(drop the '='\)"), args=['--with-revprop=foo=bar']) # basic test without revprop options @@ -2670,8 +2669,8 @@ def log_revision_move_copy(sbox): '-r2') expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r3\ .*\n', + r'-+\n', + r'r3 .*\n', re.escape('Changed paths:\n'), re.escape(' D /A/B/E\n'), re.escape(' A /E (from /A/B/E:2)\n'), # Patched - Direct move @@ -2680,7 +2679,7 @@ def log_revision_move_copy(sbox): re.escape(' D /iota\n'), re.escape(' A /iotb (from /iota:2)\n'), # Patched - Direct move re.escape(' A /mutb (from /A/mu:1)\n'), # Copy (always r1) - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox.wc_dir, @@ -2779,6 +2778,42 @@ def log_on_deleted_deep(sbox): '', '-q', '-c', '1-2') +@XFail() +@Issue(4711) +def log_with_merge_history_and_search(sbox): + "log --use-merge-history --search" + + sbox.build() + + # r2: create branch + sbox.simple_repo_copy('A', 'A2') # r2 + + # r3: mod in trunk + sbox.simple_append('A/mu', 'line 2') + sbox.simple_commit(message='r3: mod') + sbox.simple_update() + + # r4: merge + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', sbox.ospath('A2')) + sbox.simple_commit(message='r4: merge') + sbox.simple_update() + + # Helper function + def count(haystack, needle): + """Return the number of times the string NEEDLE occurs in the string + HAYSTACK.""" + return len(haystack.split(needle)) - 1 + + # Check the output is valid + # ### Since the test is currently XFail, we only smoke test the output. + # ### When fixing this test to PASS, extend this validation. + _, output, _ = svntest.main.run_svn(None, 'log', '--xml', '-g', + '--search', "this will have no matches", + sbox.ospath('A2')) + + output = '\n'.join(output) + if count(output, ">>>>>> .merge-right.r3\n") expected_status.tweak('D/H/psi', status='C ') - psi_conflict_support_files = ["psi\.working", - "psi\.merge-right\.r3", - "psi\.merge-left\.r2"] + psi_conflict_support_files = [r"psi\.working", + r"psi\.merge-right\.r3", + r"psi\.merge-left\.r2"] svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', sbox.repo_url + '/A', None, expected_output, @@ -17313,7 +17302,7 @@ def merge_target_selection(sbox): # r4 svntest.main.file_write(sbox.ospath('dir/binary-file'), - '\9\8\7\6\5\4\3\2\1') + '\x09\x08\7\6\5\4\3\2\1') sbox.simple_commit() sbox.simple_update() @@ -18511,6 +18500,164 @@ def merge_dir_delete_force(sbox): 'merge', '-c2', '^/', sbox.wc_dir, '--ignore-ancestry', '--force') +# Issue #4859: Merge removing a folder with non-inheritable mergeinfo -> +# E155023: can't set properties: invalid status for updating properties +@Issue(4859) +def merge_deleted_folder_with_mergeinfo(sbox): + "merge deleted folder with mergeinfo" + + sbox.build() + + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + # Some non-inheritable mergeinfo + sbox.simple_propset('svn:mergeinfo', '/A/C:1*', 'A/D') + sbox.simple_commit() # r2 + + # Branching + sbox.simple_repo_copy('A', 'branch_A') # r3 + sbox.simple_update() + + # On branch, remove a folder that has non-inheritable mergeinfo + sbox.simple_rm('branch_A/D') + sbox.simple_commit() # r4 + + sbox.simple_update() + + # A merge that removes that folder + # (merge revision 4 only from 'branch_A' to 'A') + expected_output = wc.State(sbox.ospath(''), { + 'A/D' : Item(status='D '), + }) + expected_mergeinfo_output = wc.State(sbox.ospath(''), { + 'A' : Item(status=' U'), + }) + expected_status = svntest.actions.get_virginal_state(sbox.ospath('A'), 4).subtree('A') + expected_status.add({ '': Item(status=' M', wc_rev=4) }) + expected_status.tweak_some( + lambda path, item: [True] if path.split('/')[0] == 'D' else [], + status='D ') + svntest.actions.run_and_verify_merge(sbox.ospath('A'), 3, 4, + '^/branch_A', None, + expected_output, + expected_mergeinfo_output, + None, + None, + expected_status, + wc.State('', {}), + [], + check_props=False, + dry_run=False # as dry run is broken + ) + + os.chdir(was_cwd) + +# Issue #4859: Merge removing a folder with non-inheritable mergeinfo -> +# E155023: can't set properties: invalid status for updating properties +# +# In this test we split the merge into two separate operable parts, a +# delete followed later by an add, to check it will set the mergeinfo on the +# subtree paths if the deleted folder is later replaced within the same +# overall merge. +@Issue(4859) +def merge_deleted_folder_with_mergeinfo_2(sbox): + "merge deleted folder with mergeinfo 2" + + sbox.build() + + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + sbox.wc_dir = '' + + # Some non-inheritable mergeinfo + sbox.simple_propset('svn:mergeinfo', '/A/C:1*', 'A/D') + sbox.simple_commit() # r2 + + # Branching + sbox.simple_repo_copy('A', 'branch_A') # r3 + sbox.simple_update() + + # On branch, remove a folder that has non-inheritable mergeinfo + sbox.simple_rm('branch_A/D') + sbox.simple_commit() # r4 + + # A commit that we don't want to merge from the branch, to split the merge + # into two separate operable parts. + sbox.simple_mkdir('branch_A/IgnoreThis') + sbox.simple_commit() # r5 + + # On branch, replace the deleted folder with a new one, with mergeinfo, + # to check we don't omit setting mergeinfo on this. + sbox.simple_mkdir('branch_A/D') + sbox.simple_propset('svn:mergeinfo', '/branch_B/C:1*', 'branch_A/D') + sbox.simple_mkdir('branch_A/D/G', 'branch_A/D/G2') + sbox.simple_propset('svn:mergeinfo', '/branch_B/C/G:1*', 'branch_A/D/G') + sbox.simple_propset('svn:mergeinfo', '/branch_B/C/G2:1*', 'branch_A/D/G2') + sbox.simple_commit() # r6 + + sbox.simple_propset('svn:mergeinfo', '/branch_A:5', 'A') + sbox.simple_commit() # r7 + + sbox.simple_update() + + # A merge that removes that folder + expected_output = wc.State(sbox.ospath(''), { + 'A/D' : Item(status='A ', prev_status='D '), + 'A/D/G' : Item(status='A '), + 'A/D/G2' : Item(status='A '), + }) + # verify that mergeinfo is set/changed on A/D, A/D/G, A/D/G2. + expected_mergeinfo_output = wc.State(sbox.ospath(''), { + 'A' : Item(status=' U'), + 'A/D' : Item(status=' G'), + 'A/D/G' : Item(status=' G'), + 'A/D/G2' : Item(status=' G'), + }) + expected_status = svntest.actions.get_virginal_state(sbox.ospath('A'), 7).subtree('A') + expected_status.tweak_some( + lambda path, item: [True] if path.split('/')[0] == 'D' else [], + status='D ') + expected_status.add({ + '' : Item(status=' M', wc_rev=7), + 'D' : Item(status='RM', copied='+', wc_rev='-'), + 'D/G' : Item(status=' M', copied='+', wc_rev='-'), + 'D/G2' : Item(status=' M', copied='+', wc_rev='-'), + }) + svntest.actions.run_and_verify_merge(sbox.ospath('A'), None, None, + '^/branch_A', None, + expected_output, + expected_mergeinfo_output, + None, + None, + expected_status, + wc.State('', {}), + [], + check_props=False, + dry_run=False # as dry run is broken + ) + + # verify that mergeinfo is set/changed on A/D, A/D/G, A/D/G2. + + # NOTE: When writing out multi-line prop values in svn:* props, the + # client converts to local encoding and local eol style. + # Therefore, the expected output must contain the right kind of eoln + # strings. That's why we use os.linesep in the tests below, not just + # plain '\n'. + + expected_mergeinfo = [ + ('A', ['/branch_A:3-7']), + ('A/D', ['/branch_A/D:5-7'+os.linesep, '/branch_B/C:1*']), + ('A/D/G', ['/branch_A/D/G:5-7'+os.linesep, '/branch_B/C/G:1*']), + ('A/D/G2', ['/branch_A/D/G2:5-7'+os.linesep, '/branch_B/C/G2:1*']), + ] + for path, mergeinfo in expected_mergeinfo: + svntest.actions.check_prop('svn:mergeinfo', sbox.ospath(path), + [m.encode() for m in mergeinfo]) + + os.chdir(was_cwd) + ######################################################################## # Run the tests @@ -18658,6 +18805,8 @@ def merge_dir_delete_force(sbox): merge_to_empty_target_merge_to_infinite_target, conflict_naming, merge_dir_delete_force, + merge_deleted_folder_with_mergeinfo, + merge_deleted_folder_with_mergeinfo_2, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/merge_tree_conflict_tests.py b/subversion/tests/cmdline/merge_tree_conflict_tests.py index 344c2d24e0ede..86e85dafc29a3 100755 --- a/subversion/tests/cmdline/merge_tree_conflict_tests.py +++ b/subversion/tests/cmdline/merge_tree_conflict_tests.py @@ -1856,9 +1856,9 @@ def merge_replace_causes_tree_conflict2(sbox): actions.run_and_verify_status(wc_dir, expected_status) # Check the tree conflict types: - expected_stdout = '(R.*)|(Summary of conflicts.*)|( Tree conflicts.*)' \ - '|(.*local delete, incoming replace upon merge.*)' \ - '|( \>.*)' + expected_stdout = r'(R.*)|(Summary of conflicts.*)|( Tree conflicts.*)' \ + r'|(.*local delete, incoming replace upon merge.*)' \ + r'|( \>.*)' tree_conflicted_path = [A_B_E, A_mu, A_D_G_pi, A_D_H] for path in tree_conflicted_path: actions.run_and_verify_svn2(expected_stdout, [], 0, 'st', @@ -2364,6 +2364,233 @@ def spurios_tree_conflict_with_added_file(sbox): [], False, True, '--reintegrate', sbox.ospath('A_branch')) +def merge_local_missing_node_kind_none(sbox): + "crash in resolver triggered by none-type node" + + sbox.build() + wc_dir = sbox.wc_dir + + sbox.simple_mkdir('branches') + sbox.simple_commit() # r2 + sbox.simple_update() + + # Create a feature branch of A + sbox.simple_copy('A', 'branches/feature1') + sbox.simple_commit() #r3 + sbox.simple_update() + + # On the branch, move file alpha to another directory + sbox.simple_move('branches/feature1/B/E/alpha', + 'branches/feature1/D/H/alpha-from-B-E') + sbox.simple_commit() # r4 + sbox.simple_update() + + # Cherry-pick the delete-half of the above move into A (the trunk) + expected_output = svntest.wc.State(sbox.ospath('A/B/E'), { + 'alpha' : Item(status='D '), + }) + expected_mergeinfo_output = wc.State(sbox.ospath('A/B/E'), { + '' : Item(status=' U') + }) + expected_elision_output = wc.State(wc_dir, { + }) + expected_disk = wc.State('', { + 'beta' : Item(contents="This is the file 'beta'.\n"), + '.' : Item(props={u'svn:mergeinfo': + u'/branches/feature1/B/E:4'}), + }) + expected_status = wc.State(sbox.ospath('A/B/E'), { + '' : Item(status=' M', wc_rev='4'), + 'alpha' : Item(status='D ', wc_rev='4'), + 'beta' : Item(status=' ', wc_rev='4'), + }) + expected_skip = wc.State('', { + }) + svntest.actions.run_and_verify_merge(sbox.ospath('A/B/E'), 3, 4, + sbox.repo_url + '/branches/feature1/B/E', + None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + check_props=True) + sbox.simple_commit() # r5 + sbox.simple_update() + + # Create a new file on the feature branch + sbox.simple_add_text("This is the file 'pi'\n", 'branches/feature1/B/E/pi') + sbox.simple_commit() #r6 + sbox.simple_update() + + # Create a second branch based on the feature branch. + sbox.simple_copy('branches/feature1', 'branches/feature2') + sbox.simple_commit() #r7 + sbox.simple_update() + + # Create a new file kappa on this second branch + sbox.simple_add_text("This is the file 'kappa'\n", + 'branches/feature2/B/E/kappa') + sbox.simple_commit() #r8 + sbox.simple_update() + + # An unrelated additional change on the second branch. + sbox.simple_append('branches/feature2/B/E/beta', + "This is a change to file 'beta'.\n") + sbox.simple_commit() #r9 + sbox.simple_update() + + # Merge the second branch back into the feature branch + expected_output = svntest.wc.State(sbox.ospath('branches/feature1'), { + 'B/E/kappa' : Item(status='A '), + 'B/E/beta' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(sbox.ospath('branches/feature1'), { + '.' : Item(status=' U'), + }) + expected_elision_output = wc.State(wc_dir, { + }) + expected_disk = wc.State('', { + 'C' : Item(), + 'B/E/kappa' : Item(contents="This is the file 'kappa'\n"), + 'B/E/pi' : Item(contents="This is the file 'pi'\n"), + 'B/E/beta' : Item(contents="This is the file 'beta'.\n" + + "This is a change to file 'beta'.\n"), + 'B/lambda' : Item(contents="This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'D/H/omega' : Item(contents="This is the file 'omega'.\n"), + 'D/H/alpha-from-B-E': Item(contents="This is the file 'alpha'.\n"), + 'D/H/psi' : Item(contents="This is the file 'psi'.\n"), + 'D/H/chi' : Item(contents="This is the file 'chi'.\n"), + 'D/G/pi' : Item(contents="This is the file 'pi'.\n"), + 'D/G/rho' : Item(contents="This is the file 'rho'.\n"), + 'D/G/tau' : Item(contents="This is the file 'tau'.\n"), + 'D/gamma' : Item(contents="This is the file 'gamma'.\n"), + 'mu' : Item(contents="This is the file 'mu'.\n"), + '.' : Item(props={u'svn:mergeinfo': + u'/branches/feature2:7-9'}), + }) + expected_status = wc.State(sbox.ospath('branches/feature1'), { + '' : Item(status=' M', wc_rev='9'), + 'mu' : Item(status=' ', wc_rev='9'), + 'D' : Item(status=' ', wc_rev='9'), + 'D/H' : Item(status=' ', wc_rev='9'), + 'D/H/alpha-from-B-E': Item(status=' ', wc_rev='9'), + 'D/H/psi' : Item(status=' ', wc_rev='9'), + 'D/H/chi' : Item(status=' ', wc_rev='9'), + 'D/H/omega' : Item(status=' ', wc_rev='9'), + 'D/G' : Item(status=' ', wc_rev='9'), + 'D/G/pi' : Item(status=' ', wc_rev='9'), + 'D/G/tau' : Item(status=' ', wc_rev='9'), + 'D/G/rho' : Item(status=' ', wc_rev='9'), + 'D/gamma' : Item(status=' ', wc_rev='9'), + 'B' : Item(status=' ', wc_rev='9'), + 'B/E' : Item(status=' ', wc_rev='9'), + 'B/E/beta' : Item(status='M ', wc_rev='9'), + 'B/E/kappa' : Item(status='A ', copied='+', wc_rev='-'), + 'B/E/pi' : Item(status=' ', wc_rev='9'), + 'B/lambda' : Item(status=' ', wc_rev='9'), + 'B/F' : Item(status=' ', wc_rev='9'), + 'C' : Item(status=' ', wc_rev='9'), + }) + expected_skip = wc.State('', { + }) + svntest.actions.run_and_verify_merge(sbox.ospath('branches/feature1'), + None, None, + sbox.repo_url + '/branches/feature2', + None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + check_props=True) + sbox.simple_commit() # r10 + sbox.simple_update() + + # On the feature branch, rename the file kappa + sbox.simple_move('branches/feature1/B/E/kappa', + 'branches/feature1/B/E/kappanew') + sbox.simple_commit() # r11 + sbox.simple_update() + + # On the feature branch, move file kappanew to another directory + sbox.simple_move('branches/feature1/B/E/kappanew', + 'branches/feature1/D/H/kappanew') + sbox.simple_commit() # r12 + sbox.simple_update() + + # Cherry-pick r12 into A (the trunk) + # This triggers an assertion failure in Subversion 1.14.1 because of + # a node type lookup which uses the wrong token map: + # W: subversion/libsvn_subr/token.c:40: (apr_err=SVN_ERR_ASSERTION_FAIL) + # svn: E235000: In file 'subversion/libsvn_subr/token.c' line 40: internal malfunction + expected_output = svntest.wc.State(sbox.ospath('A'), { + 'B/E/kappanew' : Item(status=' ', treeconflict='C'), + 'D/H/kappanew' : Item(status='A '), + }) + expected_mergeinfo_output = wc.State(sbox.ospath('A'), { + '' : Item(status=' U'), + 'B/E' : Item(status=' U'), + }) + expected_elision_output = wc.State(wc_dir, { + }) + expected_disk = wc.State('', { + 'C' : Item(), + 'B/E' : Item(props={u'svn:mergeinfo': + u'/branches/feature1/B/E:4,12'}), + 'B/E/beta' : Item(contents="This is the file 'beta'.\n"), + 'B/lambda' : Item(contents="This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'D/H/omega' : Item(contents="This is the file 'omega'.\n"), + 'D/H/kappanew' : Item(contents="This is the file 'kappa'\n"), + 'D/H/psi' : Item(contents="This is the file 'psi'.\n"), + 'D/H/chi' : Item(contents="This is the file 'chi'.\n"), + 'D/G/pi' : Item(contents="This is the file 'pi'.\n"), + 'D/G/rho' : Item(contents="This is the file 'rho'.\n"), + 'D/G/tau' : Item(contents="This is the file 'tau'.\n"), + 'D/gamma' : Item(contents="This is the file 'gamma'.\n"), + 'mu' : Item(contents="This is the file 'mu'.\n"), + '.' : Item(props={u'svn:mergeinfo': + u'/branches/feature1:12'}), + }) + expected_status = wc.State(sbox.ospath('A'), { + '' : Item(status=' M', wc_rev='12'), + 'D' : Item(status=' ', wc_rev='12'), + 'D/H' : Item(status=' ', wc_rev='12'), + 'D/H/chi' : Item(status=' ', wc_rev='12'), + 'D/H/psi' : Item(status=' ', wc_rev='12'), + 'D/H/kappanew' : Item(status='A ', copied='+', wc_rev='-'), + 'D/H/omega' : Item(status=' ', wc_rev='12'), + 'D/G' : Item(status=' ', wc_rev='12'), + 'D/G/pi' : Item(status=' ', wc_rev='12'), + 'D/G/tau' : Item(status=' ', wc_rev='12'), + 'D/G/rho' : Item(status=' ', wc_rev='12'), + 'D/gamma' : Item(status=' ', wc_rev='12'), + 'B' : Item(status=' ', wc_rev='12'), + 'B/E' : Item(status=' M', wc_rev='12'), + 'B/E/beta' : Item(status=' ', wc_rev='12'), + 'B/E/kappanew' : Item(status='! ', treeconflict='C'), + 'B/F' : Item(status=' ', wc_rev='12'), + 'B/lambda' : Item(status=' ', wc_rev='12'), + 'mu' : Item(status=' ', wc_rev='12'), + 'C' : Item(status=' ', wc_rev='12'), + }) + expected_skip = wc.State('', { + }) + svntest.actions.run_and_verify_merge(sbox.ospath('A'), 11, 12, + sbox.repo_url + '/branches/feature1', + None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + check_props=True) + ######################################################################## # Run the tests @@ -2399,6 +2626,7 @@ def spurios_tree_conflict_with_added_file(sbox): merge_obstruction_recording, added_revision_recording_in_tree_conflict, spurios_tree_conflict_with_added_file, + merge_local_missing_node_kind_none, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/mod_authz_svn_tests.py b/subversion/tests/cmdline/mod_authz_svn_tests.py index 72c359a20b17b..210024e1defd4 100755 --- a/subversion/tests/cmdline/mod_authz_svn_tests.py +++ b/subversion/tests/cmdline/mod_authz_svn_tests.py @@ -25,7 +25,7 @@ ###################################################################### # General modules -import os, re, logging +import os, re, logging, shutil logger = logging.getLogger() @@ -1043,6 +1043,73 @@ def authn_sallrall(sbox): verify_gets(test_area_url, sallrall_tests) +@SkipUnless(svntest.main.is_ra_type_dav) +def repos_relative_access_file(sbox): + "repos-relative access file" + + sbox.build() + + test_area_url = sbox.repo_url.replace('/svn-test-work/repositories/', + '/authz-test-work/in-repos-authz/') + + svntest.main.write_authz_file(sbox, {"/": "", "/A": "%s = rw" % user1}) + shutil.move(sbox.authz_file, os.path.join(sbox.wc_dir, 'authz')) + sbox.simple_add('authz') + svntest.actions.run_and_verify_svn(None, [], 'relocate', + sbox.file_protocol_repo_url(), sbox.wc_dir) + sbox.simple_commit(message="adding in-repository authz rules file") + + in_repos_authz_tests = ( + { 'path': '', 'status': 401, }, + { 'path': '/authz', 'status': 401, }, + { 'path': '/authz', 'user' : user1, 'pw' : user1_pass, + 'status': 403, }, + { 'path': '/A', 'user' : user1, 'pw' : user1_pass, + 'status': 301, }, + { 'path': '/A/', 'user' : user1, 'pw' : user1_pass, + 'status': 200, }, + ) + + verify_gets(test_area_url, in_repos_authz_tests) + +# test for the bug also known as CVE-2020-17525 +@SkipUnless(svntest.main.is_ra_type_dav) +def nonexistent_repos_relative_access_file(sbox): + "repos-relative access file with bad repository URL" + + sbox.build() + + test_area_url = sbox.repo_url.replace('/svn-test-work/repositories/', + '/authz-test-work/in-repos-authz/') + + # Construct a bad test-area URL to see what happens if we attempt to access + # a repository in a subdirectory which does not exist in SVNParentPath. + # This used to crash the server with a NULL-pointer dereference upon + # unauthenticated access. + test_area_url += '-this/does/not/exist' + + svntest.main.write_authz_file(sbox, {"/": "", "/A": "%s = rw" % user1}) + shutil.move(sbox.authz_file, os.path.join(sbox.wc_dir, 'authz')) + sbox.simple_add('authz') + svntest.actions.run_and_verify_svn(None, [], 'relocate', + sbox.file_protocol_repo_url(), sbox.wc_dir) + sbox.simple_commit(message="adding in-repository authz rules file") + + # access is denied across the board since this repository does not exist + in_repos_authz_tests = ( + { 'path': '', 'status': 401, }, + { 'path': '/authz', 'status': 401, }, + { 'path': '/authz', 'user' : user1, 'pw' : user1_pass, + 'status': 403, }, + { 'path': '/A', 'user' : user1, 'pw' : user1_pass, + 'status': 403, }, + { 'path': '/A/', 'user' : user1, 'pw' : user1_pass, + 'status': 403, }, + ) + + verify_gets(test_area_url, in_repos_authz_tests) + + ######################################################################## # Run the tests @@ -1058,6 +1125,8 @@ def authn_sallrall(sbox): authn_group, authn_sallrany, authn_sallrall, + repos_relative_access_file, + nonexistent_repos_relative_access_file, ] serial_only = True diff --git a/subversion/tests/cmdline/mod_dav_svn_tests.py b/subversion/tests/cmdline/mod_dav_svn_tests.py index 3aa6cad5cf10b..eb8b7aa36c42a 100755 --- a/subversion/tests/cmdline/mod_dav_svn_tests.py +++ b/subversion/tests/cmdline/mod_dav_svn_tests.py @@ -686,6 +686,67 @@ def last_modified_header(sbox): raise svntest.Failure('Unexpected Last-Modified header: %s' % last_modified) r.read() +@SkipUnless(svntest.main.is_ra_type_dav) +def create_name_with_control_chars(sbox): + "test creating items with control chars in names" + + sbox.build(create_wc=False) + + h = svntest.main.create_http_connection(sbox.repo_url) + + # POST /repos/!svn/me + # Create a new transaction. + req_body = ( + '(create-txn-with-props ' + '(svn:txn-client-compat-version 6 1.14.4 ' + 'svn:txn-user-agent 45 SVN/1.14.4 (x86-microsoft-windows) serf/1.3.9 ' + 'svn:log 0 ))' + ) + headers = { + 'Authorization': 'Basic ' + base64.b64encode(b'jconstant:rayjandom').decode(), + 'Content-Type': 'application/vnd.svn-skel', + } + h.request('POST', sbox.repo_url + '/!svn/me', req_body, headers) + r = h.getresponse() + if r.status != httplib.CREATED: + raise svntest.Failure('Unexpected status: %d %s' % (r.status, r.reason)) + txn_name = r.getheader('SVN-Txn-Name') + r.read() + + # MKCOL /repos/!svn/txn/TXN_NAME/tab%09name + # Must fail with a 400 Bad Request. + headers = { + 'Authorization': 'Basic ' + base64.b64encode(b'jconstant:rayjandom').decode(), + } + h.request('MKCOL', sbox.repo_url + '/!svn/txr/' + txn_name + '/tab%09name', None, headers) + r = h.getresponse() + if r.status != httplib.BAD_REQUEST: + raise svntest.Failure('Unexpected status: %d %s' % (r.status, r.reason)) + r.read() + + # PUT /repos/!svn/txn/TXN_NAME/tab%09name + # Must fail with a 400 Bad Request. + headers = { + 'Authorization': 'Basic ' + base64.b64encode(b'jconstant:rayjandom').decode(), + } + h.request('PUT', sbox.repo_url + '/!svn/txr/' + txn_name + '/tab%09name', None, headers) + r = h.getresponse() + if r.status != httplib.BAD_REQUEST: + raise svntest.Failure('Unexpected status: %d %s' % (r.status, r.reason)) + r.read() + + # COPY /repos/!svn/rvr/1/iota -> /repos/!svn/txn/TXN_NAME/tab%09name + # Must fail with a 400 Bad Request. + headers = { + 'Authorization': 'Basic ' + base64.b64encode(b'jconstant:rayjandom').decode(), + 'Destination': sbox.repo_url + '/!svn/txr/' + txn_name + '/tab%09name' + } + h.request('COPY', sbox.repo_url + '/!svn/rvr/1/iota', None, headers) + r = h.getresponse() + if r.status != httplib.BAD_REQUEST: + raise svntest.Failure('Unexpected status: %d %s' % (r.status, r.reason)) + r.read() + ######################################################################## # Run the tests @@ -700,6 +761,7 @@ def last_modified_header(sbox): propfind_allprop, propfind_propname, last_modified_header, + create_name_with_control_chars, ] serial_only = True diff --git a/subversion/tests/cmdline/patch_tests.py b/subversion/tests/cmdline/patch_tests.py index 3cd4dd7127c95..07fd93815f363 100755 --- a/subversion/tests/cmdline/patch_tests.py +++ b/subversion/tests/cmdline/patch_tests.py @@ -1574,15 +1574,7 @@ def patch_no_svn_eol_style(sbox): patch_file_path = sbox.get_tempname('my.patch') mu_path = sbox.ospath('A/mu') - # CRLF is a string that will match a CRLF sequence read from a text file. - # ### On Windows, we assume CRLF will be read as LF, so it's a poor test. - if os.name == 'nt': - crlf = '\n' - else: - crlf = '\r\n' - - # Strict EOL style matching breaks Windows tests at least with Python 2 - keep_eol_style = not svntest.main.is_os_windows() + crlf = '\r\n' eols = [crlf, '\015', '\n', '\012'] for target_eol in eols: @@ -1603,7 +1595,7 @@ def patch_no_svn_eol_style(sbox): ] # Set mu contents - svntest.main.file_write(mu_path, ''.join(mu_contents)) + svntest.main.file_write(mu_path, ''.join(mu_contents), mode='wb') unidiff_patch = [ "Index: mu", @@ -1647,7 +1639,8 @@ def patch_no_svn_eol_style(sbox): target_eol, ] - svntest.main.file_write(patch_file_path, ''.join(unidiff_patch)) + svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), + mode='wb') expected_output = wc.State(wc_dir, { 'A/mu' : Item(status='U '), @@ -1666,7 +1659,8 @@ def patch_no_svn_eol_style(sbox): expected_disk, expected_status, expected_skip, - [], True, True, keep_eol_style) + [], True, True, + keep_eol_style=True) expected_output = ["Reverted '" + mu_path + "'\n"] svntest.actions.run_and_verify_svn(expected_output, [], @@ -1681,17 +1675,13 @@ def patch_with_svn_eol_style(sbox): patch_file_path = sbox.get_tempname('my.patch') mu_path = sbox.ospath('A/mu') - # CRLF is a string that will match a CRLF sequence read from a text file. - # ### On Windows, we assume CRLF will be read as LF, so it's a poor test. if os.name == 'nt': - crlf = '\n' + native_nl = '\r\n' else: - crlf = '\r\n' - - # Strict EOL style matching breaks Windows tests at least with Python 2 - keep_eol_style = not svntest.main.is_os_windows() + native_nl = '\n' + crlf = '\r\n' - eols = [crlf, '\015', '\n', '\012'] + eols = [crlf, '\015', native_nl, '\012'] eol_styles = ['CRLF', 'CR', 'native', 'LF'] rev = 1 for target_eol, target_eol_style in zip(eols, eol_styles): @@ -1714,7 +1704,7 @@ def patch_with_svn_eol_style(sbox): # Set mu contents svntest.main.run_svn(None, 'rm', mu_path) svntest.main.run_svn(None, 'commit', '-m', 'delete mu', mu_path) - svntest.main.file_write(mu_path, ''.join(mu_contents)) + svntest.main.file_write(mu_path, ''.join(mu_contents), mode='wb') svntest.main.run_svn(None, 'add', mu_path) svntest.main.run_svn(None, 'propset', 'svn:eol-style', target_eol_style, mu_path) @@ -1762,7 +1752,8 @@ def patch_with_svn_eol_style(sbox): target_eol, ] - svntest.main.file_write(patch_file_path, ''.join(unidiff_patch)) + svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), + mode='wb') expected_output = [ 'U %s\n' % sbox.ospath('A/mu'), @@ -1786,7 +1777,7 @@ def patch_with_svn_eol_style(sbox): None, # expected err 1, # check-props 1, # dry-run - keep_eol_style) # keep-eol-style + keep_eol_style=True) expected_output = ["Reverted '" + mu_path + "'\n"] svntest.actions.run_and_verify_svn(expected_output, [], 'revert', '-R', wc_dir) @@ -1800,17 +1791,13 @@ def patch_with_svn_eol_style_uncommitted(sbox): patch_file_path = sbox.get_tempname('my.patch') mu_path = sbox.ospath('A/mu') - # CRLF is a string that will match a CRLF sequence read from a text file. - # ### On Windows, we assume CRLF will be read as LF, so it's a poor test. if os.name == 'nt': - crlf = '\n' + native_nl = '\r\n' else: - crlf = '\r\n' - - # Strict EOL style matching breaks Windows tests at least with Python 2 - keep_eol_style = not svntest.main.is_os_windows() + native_nl = '\n' + crlf = '\r\n' - eols = [crlf, '\015', '\n', '\012'] + eols = [crlf, '\015', native_nl, '\012'] eol_styles = ['CRLF', 'CR', 'native', 'LF'] for target_eol, target_eol_style in zip(eols, eol_styles): for patch_eol in eols: @@ -1830,7 +1817,7 @@ def patch_with_svn_eol_style_uncommitted(sbox): ] # Set mu contents - svntest.main.file_write(mu_path, ''.join(mu_contents)) + svntest.main.file_write(mu_path, ''.join(mu_contents), mode='wb') svntest.main.run_svn(None, 'propset', 'svn:eol-style', target_eol_style, mu_path) @@ -1876,7 +1863,8 @@ def patch_with_svn_eol_style_uncommitted(sbox): target_eol, ] - svntest.main.file_write(patch_file_path, ''.join(unidiff_patch)) + svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), + mode='wb') expected_output = wc.State(wc_dir, { 'A/mu' : Item(status='U '), @@ -1899,7 +1887,7 @@ def patch_with_svn_eol_style_uncommitted(sbox): None, # expected err 1, # check-props 1, # dry-run - keep_eol_style) # keep-eol-style + keep_eol_style=True) expected_output = ["Reverted '" + mu_path + "'\n"] svntest.actions.run_and_verify_svn(expected_output, [], 'revert', '-R', wc_dir) @@ -3446,7 +3434,7 @@ def patch_one_property(sbox, trailing_eol): value = "v\n" else: value = "v" - unidiff_patch += ['\ No newline at end of property\n'] + unidiff_patch += ['\\ No newline at end of property\n'] svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb') @@ -5820,7 +5808,7 @@ def patch_binary_file(sbox): 'Added: svn:mime-type\n', '## -0,0 +1 ##\n', '+application/binary\n', - '\ No newline at end of property\n', + '\\ No newline at end of property\n', ] _, diff_output, _ = svntest.actions.run_and_verify_svn(expected_output, [], @@ -6018,7 +6006,7 @@ def patch_final_eol(sbox): '@@ -1 +1 @@\n', '-This is the file \'mu\'.\n', '+This is the file \'mu\'.\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', 'Index: iota\n', '===================================================================\n', '--- iota\t(revision 1)\n', @@ -6026,7 +6014,7 @@ def patch_final_eol(sbox): '@@ -1 +1 @@\n', '-This is the file \'iota\'.\n', '+This is the file \'iota\'.\n', - '\ No newline at end of file' # Missing EOL + '\\ No newline at end of file' # Missing EOL ] patch = sbox.get_tempname('patch') @@ -6086,7 +6074,7 @@ def patch_final_eol(sbox): '+++ A/mu\t(working copy)\n', '@@ -1 +1 @@\n', '-This is the file \'mu\'.\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '+This is the file \'mu\'.\n', 'Index: iota\n', '===================================================================\n', @@ -6094,7 +6082,7 @@ def patch_final_eol(sbox): '+++ iota\t(working copy)\n', '@@ -1 +1 @@\n', '-This is the file \'iota\'.\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '+This is the file \'iota\'.' # Missing eol ] @@ -6759,9 +6747,9 @@ def patch_add_remove_executable(sbox): sbox.build() wc_dir = sbox.wc_dir - eicar_data = 'X5O!P%@AP[4\PZX54(P^)7CC)7}$' \ + eicar_data = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$' \ 'EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\0' - other_data = 'X5O!P%@AP[4\PZX54(P^)7CC)7}$' \ + other_data = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$' \ 'SOME-LESS-INTERESTING-OTHER-TEXT!!!$H+H*\0' \ '\0\0\0\0\0\0\0\0' @@ -6925,7 +6913,7 @@ def patch_git_symlink(sbox): '+++ b/link-to-iota\n', '@@ -0,0 +1 @@\n', '+iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] patch_edit = [ @@ -6935,9 +6923,9 @@ def patch_git_symlink(sbox): '+++ b/link-to-iota\n', '@@ -1 +1 @@\n', '-iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '+A/mu\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] patch_to_file = [ @@ -6948,7 +6936,7 @@ def patch_git_symlink(sbox): '+++ /dev/null\n', '@@ -1 +0,0 @@\n', '-A/mu\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', 'diff --git a/link-to-iota b/link-to-iota\n', 'new file mode 100644\n', 'index 0000000..1b130bf\n', @@ -7052,7 +7040,7 @@ def patch_like_git_symlink(sbox): '+++ b/link-to-iota\n', '@@ -0,0 +1 @@\n', '+iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] patch_edit = [ @@ -7062,9 +7050,9 @@ def patch_like_git_symlink(sbox): '+++ b/link-to-iota\n', '@@ -1 +1 @@\n', '-iota\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', '+A/mu\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', ] patch_to_file = [ @@ -7075,7 +7063,7 @@ def patch_like_git_symlink(sbox): '+++ /dev/null\n', '@@ -1 +0,0 @@\n', '-A/mu\n', - '\ No newline at end of file\n', + '\\ No newline at end of file\n', 'diff --git a/link-to-iota b/link-to-iota\n', 'new file mode 100644\n', 'index 0000000..1b130bf\n', @@ -7722,7 +7710,7 @@ def patch_merge(sbox): ' J\n' ' K\n' ' L\n' - '\ No newline at end of file', mode='wb') + '\\ No newline at end of file', mode='wb') expected_skip = wc.State('', { }) expected_output = wc.State(wc_dir, { @@ -7748,7 +7736,7 @@ def patch_merge(sbox): ' J\n' ' K\n' ' L\n' - '\ No newline at end of file', mode='wb') + '\\ No newline at end of file', mode='wb') svntest.actions.run_and_verify_patch(wc_dir, local_patch, expected_output, None, @@ -7884,7 +7872,7 @@ def patch_empty_prop(sbox): value = "v\n" else: value = "v" - unidiff_patch += ['\ No newline at end of property\n'] + unidiff_patch += ['\\ No newline at end of property\n'] patch_file_path = sbox.get_tempname('my.patch') svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb') diff --git a/subversion/tests/cmdline/prop_tests.py b/subversion/tests/cmdline/prop_tests.py index ada2e09fcc581..b0362a496e15e 100755 --- a/subversion/tests/cmdline/prop_tests.py +++ b/subversion/tests/cmdline/prop_tests.py @@ -349,7 +349,7 @@ def update_conflict_props(sbox): expected_status = svntest.actions.get_virginal_state(wc_dir, 2) expected_status.tweak('A/mu', 'A', status=' C') - extra_files = ['mu.*\.prej', 'dir_conflicts.*\.prej'] + extra_files = [r'mu.*\.prej', r'dir_conflicts.*\.prej'] # Do the update and check the results in three ways... INCLUDING PROPS svntest.actions.run_and_verify_update(wc_dir, expected_output, diff --git a/subversion/tests/cmdline/special_tests.py b/subversion/tests/cmdline/special_tests.py index 2ec22cb48722f..3b6dbccf0722a 100755 --- a/subversion/tests/cmdline/special_tests.py +++ b/subversion/tests/cmdline/special_tests.py @@ -71,7 +71,7 @@ def general_symlink(sbox): exit_code, stdout_lines, stderr_lines = svntest.main.run_svn(1, 'diff', wc_dir) - regex = '^\+link linktarget' + regex = r'^\+link linktarget' for line in stdout_lines: if re.match(regex, line): break diff --git a/subversion/tests/cmdline/stat_tests.py b/subversion/tests/cmdline/stat_tests.py index 2799f9c2ec132..d65cc76b21132 100755 --- a/subversion/tests/cmdline/stat_tests.py +++ b/subversion/tests/cmdline/stat_tests.py @@ -559,7 +559,7 @@ def status_uninvited_parent_directory(sbox): for line in out: # The "/?" is just to allow for an optional trailing slash. - if re.match("\\s+\\*.*\.other/?$", line): + if re.match(r"\s+\*.*\.other/?$", line): raise svntest.Failure @Issue(1289) diff --git a/subversion/tests/cmdline/svnadmin_tests.py b/subversion/tests/cmdline/svnadmin_tests.py index 0528ad42bb453..e8078f7e1c6ed 100755 --- a/subversion/tests/cmdline/svnadmin_tests.py +++ b/subversion/tests/cmdline/svnadmin_tests.py @@ -58,17 +58,20 @@ def read_rep_cache(repo_dir): """ db_path = os.path.join(repo_dir, 'db', 'rep-cache.db') db1 = svntest.sqlite3.connect(db_path) - schema1 = db1.execute("pragma user_version").fetchone()[0] - # Can't test newer rep-cache schemas with an old built-in SQLite; see the - # documentation of STMT_CREATE_SCHEMA_V2 in ../../libsvn_fs_fs/rep-cache-db.sql - if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2): - raise svntest.Failure("Can't read rep-cache schema %d using old " - "Python-SQLite version %s < (3,8,2)" % - (schema1, - svntest.sqlite3.sqlite_version_info)) - - content = { row[0]: row[1:] for row in - db1.execute("select * from rep_cache") } + try: + schema1 = db1.execute("pragma user_version").fetchone()[0] + # Can't test newer rep-cache schemas with an old built-in SQLite; see the + # documentation of STMT_CREATE_SCHEMA_V2 in ../../libsvn_fs_fs/rep-cache-db.sql + if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2): + raise svntest.Failure("Can't read rep-cache schema %d using old " + "Python-SQLite version %s < (3,8,2)" % + (schema1, + svntest.sqlite3.sqlite_version_info)) + + content = { row[0]: row[1:] for row in + db1.execute("select * from rep_cache") } + finally: + db1.close() return content def check_hotcopy_bdb(src, dst): @@ -1787,7 +1790,7 @@ def expected_output_list(path): "Owner: jrandom", "Created:.*", "Expires:.*", - "Comment \(1 line\):", + r"Comment \(1 line\):", "Locking files", "\n", # empty line ] @@ -3511,8 +3514,8 @@ def dump_exclude(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r1\ .*\n', + r'-+\n', + r'r1 .*\n', # '/A/D/H' and '/A/B/E' is not added. re.escape('Changed paths:\n'), re.escape(' A /A\n'), @@ -3528,7 +3531,7 @@ def dump_exclude(sbox): re.escape(' A /A/D/gamma\n'), re.escape(' A /A/mu\n'), re.escape(' A /iota\n'), - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -3564,18 +3567,18 @@ def dump_exclude_copysource(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r2\ .*\n', + r'-+\n', + r'r2 .*\n', re.escape('Changed paths:\n'), # Simple add, not copy. re.escape(' A /branches/branch1\n'), - '-+\\n', - 'r1\ .*\n', + r'-+\n', + r'r1 .*\n', # '/trunk' is not added. re.escape('Changed paths:\n'), re.escape(' A /branches\n'), re.escape(' A /tags\n'), - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -3609,13 +3612,13 @@ def dump_include(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r1\ .*\n', + r'-+\n', + r'r1 .*\n', # '/B' is not added. re.escape('Changed paths:\n'), re.escape(' A /A\n'), re.escape(' A /C\n'), - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -3651,17 +3654,17 @@ def dump_not_include_copysource(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r2\ .*\n', + r'-+\n', + r'r2 .*\n', re.escape('Changed paths:\n'), # Simple add, not copy. re.escape(' A /branches/branch1\n'), - '-+\\n', - 'r1\ .*\n', + r'-+\n', + r'r1 .*\n', # Only '/branches' is added in r1. re.escape('Changed paths:\n'), re.escape(' A /branches\n'), - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -3693,12 +3696,12 @@ def dump_exclude_by_pattern(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r1\ .*\n', + r'-+\n', + r'r1 .*\n', re.escape('Changed paths:\n'), # Only '/bbc' is added in r1. re.escape(' A /bbc\n'), - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -3730,14 +3733,14 @@ def dump_include_by_pattern(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r1\ .*\n', + r'-+\n', + r'r1 .*\n', # '/bbc' is not added. re.escape('Changed paths:\n'), re.escape(' A /aaa\n'), re.escape(' A /aab\n'), re.escape(' A /aac\n'), - '-+\\n' + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -3782,27 +3785,27 @@ def dump_exclude_all_rev_changes(sbox): # Check log. Revision properties ('svn:log' etc.) should be empty for r2. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r3 | jrandom | .* | 1 line\\n', + r'-+\n', + r'r3 \| jrandom \| .* \| 1 line\n', re.escape('Changed paths:'), re.escape(' A /r3a'), re.escape(' A /r3b'), re.escape(' A /r3c'), '', re.escape('Revision 3.'), - '-+\\n', + r'-+\n', re.escape('r2 | (no author) | (no date) | 1 line'), '', '', - '-+\\n', - 'r1 | jrandom | .* | 1 line\\n', + r'-+\n', + r'r1 | jrandom | .* | 1 line\n', re.escape('Changed paths:'), re.escape(' A /r1a'), re.escape(' A /r1b'), re.escape(' A /r1c'), '', re.escape('Revision 1.'), - '-+\\n', + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', sbox2.repo_url) @@ -3944,8 +3947,8 @@ def dump_include_copied_directory(sbox): # Check log. expected_output = svntest.verify.RegexListOutput([ - '-+\\n', - 'r2\ .*\n', + r'-+\n', + r'r2 .*\n', # Only '/COPY' is added re.escape('Changed paths:\n'), re.escape(' A /COPY'), @@ -3958,9 +3961,9 @@ def dump_include_copied_directory(sbox): re.escape(' A /COPY/H/omega'), re.escape(' A /COPY/H/psi'), re.escape(' A /COPY/gamma'), - '-+\\n', - 'r1\ .*\n', - '-+\\n' + r'-+\n', + r'r1 .*\n', + r'-+\n', ]) svntest.actions.run_and_verify_svn(expected_output, [], 'log', '-v', '-q', sbox2.repo_url) @@ -4000,7 +4003,7 @@ def load_normalize_node_props(sbox): 2005-05-03T19:10:19.975578Z PROPS-END -Node-path: +Node-path:\x20 Node-kind: dir Node-action: change Prop-content-length: 32 @@ -4036,6 +4039,28 @@ def load_normalize_node_props(sbox): if output != ['\n', '\n']: raise svntest.Failure("Unexpected property value %s" % output) +@SkipUnless(svntest.main.is_fs_type_fsfs) +@SkipUnless(svntest.main.fs_has_rep_sharing) +@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid) +def build_repcache(sbox): + "svnadmin build-repcache" + + sbox.build(create_wc = False) + + # Remember and remove the existing rep-cache. + rep_cache = read_rep_cache(sbox.repo_dir) + rep_cache_path = os.path.join(sbox.repo_dir, 'db', 'rep-cache.db') + os.remove(rep_cache_path) + + # Build a new rep-cache and compare with the original one. + expected_output = ["* Processed revision 1.\n"] + svntest.actions.run_and_verify_svnadmin(expected_output, [], + "build-repcache", sbox.repo_dir) + + new_rep_cache = read_rep_cache(sbox.repo_dir) + if new_rep_cache != rep_cache: + raise svntest.Failure + ######################################################################## # Run the tests @@ -4116,6 +4141,7 @@ def load_normalize_node_props(sbox): recover_prunes_rep_cache_when_disabled, dump_include_copied_directory, load_normalize_node_props, + build_repcache, ] if __name__ == '__main__': diff --git a/subversion/tests/cmdline/svndumpfilter_tests.py b/subversion/tests/cmdline/svndumpfilter_tests.py index a45b50b990891..adba4bd0a6e6d 100755 --- a/subversion/tests/cmdline/svndumpfilter_tests.py +++ b/subversion/tests/cmdline/svndumpfilter_tests.py @@ -71,7 +71,7 @@ def filter_and_return_output(dump, bufsize=0, *varargs): # Since we call svntest.main.run_command_stdin() in binary mode, # normalize the stderr line endings on Windows ourselves. if sys.platform == 'win32': - errput = map(lambda x : x.replace('\r\n', '\n'), errput) + errput = [x.replace('\r\n', '\n') for x in errput] return output, errput diff --git a/subversion/tests/cmdline/svnfsfs_tests.py b/subversion/tests/cmdline/svnfsfs_tests.py index ccf5b3abbfcbc..eca5fcbbaf5eb 100755 --- a/subversion/tests/cmdline/svnfsfs_tests.py +++ b/subversion/tests/cmdline/svnfsfs_tests.py @@ -167,38 +167,38 @@ def test_stats(sbox): 'Histogram of directory property sizes:', 'Histogram of directory property representation sizes:'] patterns_to_find = { - 'Reading revisions' : ['\s+ 0[ 0-9]*'], - 'Global .*' : ['.*\d+ bytes in .*\d+ revisions', - '.*\d+ bytes in .*\d+ changes', - '.*\d+ bytes in .*\d+ node revision records', - '.*\d+ bytes in .*\d+ representations', - '.*\d+ bytes expanded representation size', - '.*\d+ bytes with rep-sharing off' ], - 'Noderev .*' : ['.*\d+ bytes in .*\d+ nodes total', - '.*\d+ bytes in .*\d+ directory noderevs', - '.*\d+ bytes in .*\d+ file noderevs' ], - 'Representation .*' : ['.*\d+ bytes in .*\d+ representations total', - '.*\d+ bytes in .*\d+ directory representations', - '.*\d+ bytes in .*\d+ file representations', - '.*\d+ bytes in .*\d+ representations of added file nodes', - '.*\d+ bytes in .*\d+ directory property representations', - '.*\d+ bytes in .*\d+ file property representations', - '.*\d+ average delta chain length', - '.*\d+ bytes in header & footer overhead' ], + 'Reading revisions' : [r'\s+ 0[ 0-9]*'], + 'Global .*' : [r'.*\d+ bytes in .*\d+ revisions', + r'.*\d+ bytes in .*\d+ changes', + r'.*\d+ bytes in .*\d+ node revision records', + r'.*\d+ bytes in .*\d+ representations', + r'.*\d+ bytes expanded representation size', + r'.*\d+ bytes with rep-sharing off' ], + 'Noderev .*' : [r'.*\d+ bytes in .*\d+ nodes total', + r'.*\d+ bytes in .*\d+ directory noderevs', + r'.*\d+ bytes in .*\d+ file noderevs' ], + 'Representation .*' : [r'.*\d+ bytes in .*\d+ representations total', + r'.*\d+ bytes in .*\d+ directory representations', + r'.*\d+ bytes in .*\d+ file representations', + r'.*\d+ bytes in .*\d+ representations of added file nodes', + r'.*\d+ bytes in .*\d+ directory property representations', + r'.*\d+ bytes in .*\d+ file property representations', + r'.*\d+ average delta chain length', + r'.*\d+ bytes in header & footer overhead' ], '.* representation statistics:' : - ['.*\d+ bytes in .*\d+ reps', - '.*\d+ bytes in .*\d+ shared reps', - '.*\d+ bytes expanded size', - '.*\d+ bytes expanded shared size', - '.*\d+ bytes with rep-sharing off', - '.*\d+ shared references', - '.*\d+ average delta chain length'], - 'Largest.*:' : ['.*\d+ r\d+ */\S*'], + [r'.*\d+ bytes in .*\d+ reps', + r'.*\d+ bytes in .*\d+ shared reps', + r'.*\d+ bytes expanded size', + r'.*\d+ bytes expanded shared size', + r'.*\d+ bytes with rep-sharing off', + r'.*\d+ shared references', + r'.*\d+ average delta chain length'], + 'Largest.*:' : [r'.*\d+ r\d+ */\S*'], 'Extensions by number .*:' : - ['.*\d+ \( ?\d+%\) representations'], + [r'.*\d+ \( ?\d+%\) representations'], 'Extensions by size .*:' : - ['.*\d+ \( ?\d+%\) bytes'], - 'Histogram of .*:' : ['.*\d+ \.\. < \d+.*\d+ \( ?\d+%\) bytes in *\d+ \( ?\d+%\) items'] + [r'.*\d+ \( ?\d+%\) bytes'], + 'Histogram of .*:' : [r'.*\d+ \.\. < \d+.*\d+ \( ?\d+%\) bytes in *\d+ \( ?\d+%\) items'] } # check that the output contains all sections diff --git a/subversion/tests/cmdline/svnmover_tests.py b/subversion/tests/cmdline/svnmover_tests.py index d0acd0f2892d0..20aec7f4a7785 100755 --- a/subversion/tests/cmdline/svnmover_tests.py +++ b/subversion/tests/cmdline/svnmover_tests.py @@ -35,7 +35,7 @@ ###################################################################### _commit_re = re.compile('^Committed r([0-9]+)') -_log_re = re.compile('^ ([ADRM] /[^\(]+($| \(from .*:[0-9]+\)$))') +_log_re = re.compile(r'^ ([ADRM] /[^\(]+($| \(from .*:[0-9]+\)$))') _err_re = re.compile('^svnmover: (.*)$') def mk_file(sbox, file_name): diff --git a/subversion/tests/cmdline/svnmucc_tests.py b/subversion/tests/cmdline/svnmucc_tests.py index 3159ec29f3d3f..ea2d52c75eb6e 100755 --- a/subversion/tests/cmdline/svnmucc_tests.py +++ b/subversion/tests/cmdline/svnmucc_tests.py @@ -49,7 +49,7 @@ def reject_bogus_mergeinfo(sbox): sbox.repo_url + '/A') _svnmucc_re = re.compile(b'^(r[0-9]+) committed by jrandom at (.*)$') -_log_re = re.compile('^ ([ADRM] /[^\(]+($| \(from .*:[0-9]+\)$))') +_log_re = re.compile(r'^ ([ADRM] /[^\(]+($| \(from .*:[0-9]+\)$))') _err_re = re.compile('^svnmucc: (.*)$') def test_svnmucc(repo_url, expected_path_changes, *varargs): @@ -534,7 +534,8 @@ def svnmucc_type_errors(sbox): 'put', sbox.ospath('file'), 'A') xtest_svnmucc(sbox.repo_url, - ["svnmucc: E160020: Path 'Z' already exists"], + ["svnmucc: E160020: Path 'Z' already exists, or was created " + "by an earlier operation"], '-m', '', 'mkdir', 'A/Z', 'put', sbox.ospath('file'), 'A/Z') @@ -576,7 +577,8 @@ def svnmucc_propset_and_put(sbox): # Put same file twice (non existing) xtest_svnmucc(sbox.repo_url, - ["svnmucc: E160020: Path 't3' already exists"], + ["svnmucc: E160020: Path 't3' already exists, or was created " + "by an earlier operation"], '-m', '', 'put', sbox.ospath('file'), 't3', 'put', sbox.ospath('file'), 't3') diff --git a/subversion/tests/cmdline/svnserveautocheck.sh b/subversion/tests/cmdline/svnserveautocheck.sh index 8572cd67cfb34..d8e1cdfa11ad7 100755 --- a/subversion/tests/cmdline/svnserveautocheck.sh +++ b/subversion/tests/cmdline/svnserveautocheck.sh @@ -143,7 +143,7 @@ if type time > /dev/null ; then TIME_CMD() { time "$@"; } ; else TIME_CMD() { "$ MAKE=${MAKE:-make} PATH="$PATH:/usr/sbin/:/usr/local/sbin/" -ss > /dev/null 2>&1 || netstat > /dev/null 2>&1 || fail "unable to find ss or netstat required to find a free port" +ss -n > /dev/null 2>&1 || netstat -n > /dev/null 2>&1 || fail "unable to find ss or netstat required to find a free port" SVNSERVE_PORT=$(random_port) while \ diff --git a/subversion/tests/cmdline/svntest/actions.py b/subversion/tests/cmdline/svntest/actions.py index 91a88dad7d2d6..f506f11948c02 100644 --- a/subversion/tests/cmdline/svntest/actions.py +++ b/subversion/tests/cmdline/svntest/actions.py @@ -418,7 +418,7 @@ def run_and_verify_svnrdump(dumpfile_content, expected_stdout, # Since main.run_svnrdump() uses binary mode, normalize the stderr # line endings on Windows ourselves. if sys.platform == 'win32': - err = map(lambda x : x.replace('\r\n', '\n'), err) + err = [x.replace('\r\n', '\n') for x in err] # Ignore "consider upgrade" warnings to allow regression tests to pass # when run against a 1.6 mod_dav_svn. @@ -1045,9 +1045,9 @@ def run_and_parse_info(*args): # normal line key, value = line.split(':', 1) - if re.search(' \(\d+ lines?\)$', key): + if re.search(r' \(\d+ lines?\)$', key): # numbered continuation lines - match = re.match('^(.*) \((\d+) lines?\)$', key) + match = re.match(r'^(.*) \((\d+) lines?\)$', key) key = match.group(1) lock_comment_lines = int(match.group(2)) elif len(value) > 1: @@ -1923,7 +1923,7 @@ def _run_and_verify_resolve(cmd, expected_paths, *args): "Merge conflicts in '" + path + "' marked as resolved.\n" for path in expected_paths]), verify.UnorderedRegexListOutput([ - "Conflict in property.*at '" + path + "' marked as resolved.\n" \ + "Conflict in property.*at '" + re.escape(path) + "' marked as resolved.\n" \ for path in expected_paths]), verify.UnorderedOutput([ "Tree conflict at '" + path + "' marked as resolved.\n" for path in diff --git a/subversion/tests/cmdline/svntest/main.py b/subversion/tests/cmdline/svntest/main.py index a02733a7f7d66..72d9777084e23 100644 --- a/subversion/tests/cmdline/svntest/main.py +++ b/subversion/tests/cmdline/svntest/main.py @@ -527,10 +527,10 @@ def wait_on_pipe(waiter, binary_mode, stdin=None): # We always expect STDERR to be strings, not byte-arrays. if not isinstance(stderr, str): - stderr = stderr.decode("utf-8") + stderr = stderr.decode("utf-8", 'surrogateescape') if not binary_mode: if not isinstance(stdout, str): - stdout = stdout.decode("utf-8") + stdout = stdout.decode("utf-8", 'surrogateescape') # Normalize Windows line endings if in text mode. if windows: @@ -902,8 +902,6 @@ def run_entriesdump(path): ### report on this? or continue to just skip it? return None - class Entry(object): - pass entries = { } exec(''.join(filter_dbg(stdout_lines))) return entries @@ -927,8 +925,6 @@ def run_entriesdump_tree(path): ### report on this? or continue to just skip it? return None - class Entry(object): - pass dirs = { } exec(''.join(filter_dbg(stdout_lines))) return dirs @@ -1364,7 +1360,7 @@ def write_restrictive_svnserve_conf(repo_dir, anon_access="none", fp.write("groups-db = groups\n") if options.enable_sasl: fp.write("realm = svntest\n" - "[sasl]\n", + "[sasl]\n" "use-sasl = true\n"); else: fp.write("password-db = passwd\n") @@ -1470,7 +1466,7 @@ def merge_notify_line(revstart=None, revend=None, same_URL=True, merge operation on revisions REVSTART through REVEND. Omit both REVSTART and REVEND for the case where the left and right sides of the merge are from different URLs.""" - from_foreign_phrase = foreign and "\(from foreign repository\) " or "" + from_foreign_phrase = foreign and r"\(from foreign repository\) " or "" if target: target_re = re.escape(target) else: @@ -2084,6 +2080,23 @@ def format(self, record): record.levelshort = self._level_short[record.levelno] return logging.Formatter.format(self, record) + +class LoggingStdoutHandler(logging.StreamHandler): + """ + The handler is always writing using sys.stdout at call time rather than the + value of sys.stdout at construction time. + + Inspired by logging._StderrHandler on Python 3. + """ + + def __init__(self, level=logging.NOTSET): + logging.Handler.__init__(self, level) + + @property + def stream(self): + return sys.stdout + + def _create_parser(usage=None): """Return a parser for our test suite.""" @@ -2276,7 +2289,7 @@ def parse_options(arglist=sys.argv[1:], usage=None): datefmt='%Y-%m-%d %H:%M:%S') else: formatter = AbbreviatedFormatter('%(levelshort)s: %(message)s') - handler = logging.StreamHandler(sys.stdout) + handler = LoggingStdoutHandler() handler.setFormatter(formatter) logger.addHandler(handler) diff --git a/subversion/tests/cmdline/svntest/verify.py b/subversion/tests/cmdline/svntest/verify.py index 0fb7bcd29ed70..1dd0fed3156ce 100644 --- a/subversion/tests/cmdline/svntest/verify.py +++ b/subversion/tests/cmdline/svntest/verify.py @@ -1023,7 +1023,7 @@ def make_diff_prop_added(pname, pval): ] + make_diff_prop_val("+", pval) def make_diff_prop_modified(pname, pval1, pval2): - """Return a property diff for modification of property PNAME, old value + r"""Return a property diff for modification of property PNAME, old value PVAL1, new value PVAL2. PVAL is a single string with no embedded newlines. A newline at the diff --git a/subversion/tests/cmdline/svntest/wc.py b/subversion/tests/cmdline/svntest/wc.py index 1b703f1ea0e68..6047a9f65cf77 100644 --- a/subversion/tests/cmdline/svntest/wc.py +++ b/subversion/tests/cmdline/svntest/wc.py @@ -28,6 +28,7 @@ import re import logging import pprint +import io if sys.version_info[0] >= 3: # Python >=3.0 @@ -92,30 +93,30 @@ '([KOBT ])' '([C ]) ' '([* ]) +' - '((?P\d+|-|\?) +(\d|-|\?)+ +(\S+) +)?' + r'((?P\d+|-|\?) +(\d|-|\?)+ +(\S+) +)?' '(?P.+)$') _re_parse_status_ex = re.compile('^ (' - '( \> moved (from (?P.+)|to (?P.*)))' - '|( \> swapped places with (?P.+).*)' - '|(\> (?P.+))' + r'( \> moved (from (?P.+)|to (?P.*)))' + r'|( \> swapped places with (?P.+).*)' + r'|(\> (?P.+))' ')$') _re_parse_skipped = re.compile("^(Skipped[^']*) '(.+)'( --.*)?\n") _re_parse_summarize = re.compile("^([MAD ][M ]) (.+)\n") -_re_parse_checkout = re.compile('^([RMAGCUDE_ B][MAGCUDE_ ])' - '([B ])' - '([CAUD ])\s+' - '(.+)') -_re_parse_co_skipped = re.compile('^(Restored|Skipped|Removed external)' - '\s+\'(.+)\'(( --|: ).*)?') -_re_parse_co_restored = re.compile('^(Restored)\s+\'(.+)\'') +_re_parse_checkout = re.compile(r'^([RMAGCUDE_ B][MAGCUDE_ ])' + r'([B ])' + r'([CAUD ])\s+' + r'(.+)') +_re_parse_co_skipped = re.compile(r'^(Restored|Skipped|Removed external)' + r'\s+\'(.+)\'(( --|: ).*)?') +_re_parse_co_restored = re.compile(r'^(Restored)\s+\'(.+)\'') # Lines typically have a verb followed by whitespace then a path. -_re_parse_commit_ext = re.compile('^(([A-Za-z]+( [a-z]+)*)) \'(.+)\'( --.*)?') -_re_parse_commit = re.compile('^(\w+( \(bin\))?)\s+(.+)') +_re_parse_commit_ext = re.compile("^(([A-Za-z]+( [a-z]+)*)) '(.+)'( --.*)?") +_re_parse_commit = re.compile(r'^(\w+( \(bin\))?)\s+(.+)') #rN: eids 0 15 branches 4 _re_parse_eid_header = re.compile('^r(-1|[0-9]+): eids ([0-9]+) ([0-9]+) ' @@ -686,10 +687,18 @@ def from_wc(cls, base, load_props=False, ignore_svn=True, if os.path.isfile(node): try: if keep_eol_style: - contents = open(node, 'r', newline='').read() + + contents = io.open(node, 'r', newline='', + encoding='utf-8').read() else: - contents = open(node, 'r').read() + contents = io.open(node, 'r', encoding='utf-8').read() + if not isinstance(contents, str): + # Python 2: contents is read as an unicode object, + # but we expect it is a str. + contents = contents.encode() except: + # If the file contains non UTF-8 character, we treat its + # content as binary represented as a bytes object. contents = open(node, 'rb').read() else: contents = None diff --git a/subversion/tests/cmdline/trans_tests.py b/subversion/tests/cmdline/trans_tests.py index 71ca45e6d9f0a..4108dfbfb841c 100755 --- a/subversion/tests/cmdline/trans_tests.py +++ b/subversion/tests/cmdline/trans_tests.py @@ -304,7 +304,7 @@ def keywords_from_birth(sbox): fp = open(url_unexp_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$URL: (http|https|file|svn|svn\\+ssh)://", + and (re.match(r"\$URL: (http|https|file|svn|svn\+ssh)://", lines[0]))): logger.warn("URL expansion failed for %s", url_unexp_path) raise svntest.Failure @@ -314,7 +314,7 @@ def keywords_from_birth(sbox): fp = open(url_exp_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$URL: (http|https|file|svn|svn\\+ssh)://", + and (re.match(r"\$URL: (http|https|file|svn|svn\+ssh)://", lines[0]))): logger.warn("URL expansion failed for %s", url_exp_path) raise svntest.Failure @@ -324,7 +324,7 @@ def keywords_from_birth(sbox): fp = open(id_unexp_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$Id: id_unexp", lines[0]))): + and (re.match(r"\$Id: id_unexp", lines[0]))): logger.warn("Id expansion failed for %s", id_exp_path) raise svntest.Failure fp.close() @@ -333,7 +333,7 @@ def keywords_from_birth(sbox): fp = open(id_exp_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$Id: id_exp", lines[0]))): + and (re.match(r"\$Id: id_exp", lines[0]))): logger.warn("Id expansion failed for %s", id_exp_path) raise svntest.Failure fp.close() @@ -342,7 +342,7 @@ def keywords_from_birth(sbox): fp = open(header_unexp_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$Header: (https?|file|svn|svn\\+ssh)://.* jrandom", + and (re.match(r"\$Header: (https?|file|svn|svn\+ssh)://.* jrandom", lines[0]))): logger.warn("Header expansion failed for %s", header_unexp_path) raise svntest.Failure @@ -352,7 +352,7 @@ def keywords_from_birth(sbox): fp = open(header_exp_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$Header: (https?|file|svn|svn\\+ssh)://.* jrandom", + and (re.match(r"\$Header: (https?|file|svn|svn\+ssh)://.* jrandom", lines[0]))): logger.warn("Header expansion failed for %s", header_exp_path) raise svntest.Failure @@ -402,7 +402,7 @@ def keywords_from_birth(sbox): fp = open(id_with_space_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$Id: .*id with space", lines[0]))): + and (re.match(r"\$Id: .*id with space", lines[0]))): logger.warn("Id expansion failed for %s", id_with_space_path) raise svntest.Failure fp.close() @@ -411,7 +411,7 @@ def keywords_from_birth(sbox): fp = open(id_exp_with_dollar_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$Id: .*id_exp with_\$_sign [^$]* jrandom \$", + and (re.match(r"\$Id: .*id_exp with_\$_sign [^$]* jrandom \$", lines[0]))): logger.warn("Id expansion failed for %s", id_exp_with_dollar_path) @@ -637,7 +637,7 @@ def keyword_expanded_on_checkout(sbox): fp = open(other_url_path, 'r') lines = fp.readlines() if not ((len(lines) == 1) - and (re.match("\$URL: (http|https|file|svn|svn\\+ssh)://", + and (re.match(r"\$URL: (http|https|file|svn|svn\+ssh)://", lines[0]))): logger.warn("URL expansion failed for %s", other_url_path) raise svntest.Failure diff --git a/subversion/tests/cmdline/update_tests.py b/subversion/tests/cmdline/update_tests.py index bf967e5f39425..e9b8048295f75 100755 --- a/subversion/tests/cmdline/update_tests.py +++ b/subversion/tests/cmdline/update_tests.py @@ -620,8 +620,8 @@ def update_to_resolve_text_conflicts(sbox): # "Extra" files that we expect to result from the conflicts. # These are expressed as list of regexps. What a cool system! :-) - extra_files = ['mu.*\.r1', 'mu.*\.r2', 'mu.*\.mine', - 'rho.*\.r1', 'rho.*\.r2', 'rho.*\.mine', 'rho.*\.prej'] + extra_files = [r'mu.*\.r1', r'mu.*\.r2', r'mu.*\.mine', + r'rho.*\.r1', r'rho.*\.r2', r'rho.*\.mine', r'rho.*\.prej'] # Do the update and check the results in three ways. # All "extra" files are passed to detect_conflict_files(). @@ -1650,15 +1650,11 @@ def conflict_markers_matching_eol(sbox): mu_path = sbox.ospath('A/mu') - # CRLF is a string that will match a CRLF sequence read from a text file. - # ### On Windows, we assume CRLF will be read as LF, so it's a poor test. if os.name == 'nt': - crlf = '\n' + native_nl = '\r\n' else: - crlf = '\r\n' - - # Strict EOL style matching breaks Windows tests at least with Python 2 - keep_eol_style = not svntest.main.is_os_windows() + native_nl = '\n' + crlf = '\r\n' # Checkout a second working copy wc_backup = sbox.add_wc_path('backup') @@ -1677,7 +1673,7 @@ def conflict_markers_matching_eol(sbox): # do the test for each eol-style for eol, eolchar in zip(['CRLF', 'CR', 'native', 'LF'], - [crlf, '\015', '\n', '\012']): + [crlf, '\015', native_nl, '\012']): # rewrite file mu and set the eol-style property. svntest.main.file_write(mu_path, "This is the file 'mu'."+ eolchar, 'wb') svntest.main.run_svn(None, 'propset', 'svn:eol-style', eol, mu_path) @@ -1704,8 +1700,8 @@ def conflict_markers_matching_eol(sbox): svntest.main.run_svn(None, 'update', wc_backup) # Make a local mod to mu - svntest.main.file_append(mu_path, - 'Original appended text for mu' + eolchar) + svntest.main.file_append_binary(mu_path, + 'Original appended text for mu' + eolchar) # Commit the original change and note the 'theirs' revision number svntest.main.run_svn(None, 'commit', '-m', 'test log', wc_dir) @@ -1713,8 +1709,9 @@ def conflict_markers_matching_eol(sbox): theirs_rev = cur_rev # Make a local mod to mu, will conflict with the previous change - svntest.main.file_append(path_backup, - 'Conflicting appended text for mu' + eolchar) + svntest.main.file_append_binary(path_backup, + 'Conflicting appended text for mu' + + eolchar) # Create expected output tree for an update of the wc_backup. expected_backup_output = svntest.wc.State(wc_backup, { @@ -1764,7 +1761,7 @@ def conflict_markers_matching_eol(sbox): expected_backup_output, expected_backup_disk, expected_backup_status, - keep_eol_style=keep_eol_style) + keep_eol_style=True) # cleanup for next run svntest.main.run_svn(None, 'revert', '-R', wc_backup) @@ -1785,15 +1782,7 @@ def update_eolstyle_handling(sbox): mu_path = sbox.ospath('A/mu') - # CRLF is a string that will match a CRLF sequence read from a text file. - # ### On Windows, we assume CRLF will be read as LF, so it's a poor test. - if os.name == 'nt': - crlf = '\n' - else: - crlf = '\r\n' - - # Strict EOL style matching breaks Windows tests at least with Python 2 - keep_eol_style = not svntest.main.is_os_windows() + crlf = '\r\n' # Checkout a second working copy wc_backup = sbox.add_wc_path('backup') @@ -1825,7 +1814,7 @@ def update_eolstyle_handling(sbox): expected_backup_output, expected_backup_disk, expected_backup_status, - keep_eol_style=keep_eol_style) + keep_eol_style=True) # Test 2: now change the eol-style property to another value and commit, # update the still changed mu in the second working copy; there should be @@ -1851,7 +1840,7 @@ def update_eolstyle_handling(sbox): expected_backup_output, expected_backup_disk, expected_backup_status, - keep_eol_style=keep_eol_style) + keep_eol_style=True) # Test 3: now delete the eol-style property and commit, update the still # changed mu in the second working copy; there should be no conflict! @@ -1876,7 +1865,7 @@ def update_eolstyle_handling(sbox): expected_backup_output, expected_backup_disk, expected_backup_status, - keep_eol_style=keep_eol_style) + keep_eol_style=True) # Bug in which "update" put a bogus revision number on a schedule-add file, # causing the wrong version of it to be committed. @@ -2708,10 +2697,10 @@ def update_with_obstructing_additions(sbox): }) # "Extra" files that we expect to result from the conflicts. - extra_files = ['eta\.r0', 'eta\.r2', 'eta\.mine', - 'kappa\.r0', 'kappa\.r2', 'kappa\.mine', - 'epsilon\.r0', 'epsilon\.r2', 'epsilon\.mine', - 'kappa.prej', 'zeta.prej', 'dir_conflicts.prej'] + extra_files = [r'eta\.r0', r'eta\.r2', r'eta\.mine', + r'kappa\.r0', r'kappa\.r2', r'kappa\.mine', + r'epsilon\.r0', r'epsilon\.r2', r'epsilon\.mine', + r'kappa.prej', r'zeta.prej', r'dir_conflicts.prej'] # Perform forced update and check the results in three # ways (including props). @@ -3825,9 +3814,9 @@ def update_accept_conflicts(sbox): 'foo\n')) # Set the expected extra files for the test - extra_files = ['iota.*\.r2', 'iota.*\.r3', 'iota.*\.mine', - 'lambda.*\.r2', 'lambda.*\.r3', 'lambda.*\.mine', - 'rho.*\.r2', 'rho.*\.r3', 'rho.*\.mine'] + extra_files = [r'iota.*\.r2', r'iota.*\.r3', r'iota.*\.mine', + r'lambda.*\.r2', r'lambda.*\.r3', r'lambda.*\.mine', + r'rho.*\.r2', r'rho.*\.r3', r'rho.*\.mine'] # Set the expected status for the test expected_status = svntest.actions.get_virginal_state(wc_backup, 3) diff --git a/subversion/tests/cmdline/upgrade_tests.py b/subversion/tests/cmdline/upgrade_tests.py index c60dab5ce8c92..cff175b9cc3d2 100755 --- a/subversion/tests/cmdline/upgrade_tests.py +++ b/subversion/tests/cmdline/upgrade_tests.py @@ -51,7 +51,7 @@ Issue = svntest.testcase.Issue_deco Wimp = svntest.testcase.Wimp_deco -wc_is_too_old_regex = (".*is too old \(format \d+.*\).*") +wc_is_too_old_regex = (r".*is too old \(format \d+.*\).*") def get_current_format(): diff --git a/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c b/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c index 51bfac203b71c..9f788a3a41403 100644 --- a/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c +++ b/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c @@ -35,6 +35,8 @@ #include "private/svn_subr_private.h" #include "../../libsvn_fs_fs/index.h" +#include "../../libsvn_fs_fs/rep-cache.h" +#include "../../libsvn_fs/fs-loader.h" #include "../svn_test_fs.h" @@ -440,6 +442,63 @@ load_index(const svn_test_opts_t *opts, apr_pool_t *pool) #undef REPO_NAME +/* ------------------------------------------------------------------------ */ + +static svn_error_t * +build_rep_cache(const svn_test_opts_t *opts, apr_pool_t *pool) +{ + svn_fs_t *fs; + fs_fs_data_t *ffd; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t rev; + svn_boolean_t exists; + const char *fs_path; + svn_fs_fs__ioctl_build_rep_cache_input_t input = {0}; + + /* Bail (with success) on known-untestable scenarios */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "this will test FSFS repositories only"); + + if (opts->server_minor_version && (opts->server_minor_version < 6)) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.6 SVN doesn't support FSFS rep-sharing"); + + /* Create a filesystem and explicitly disable rep-sharing. */ + fs_path = "test-repo-build-rep-cache-test"; + SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, NULL, pool)); + ffd = fs->fsap_data; + ffd->rep_sharing_allowed = FALSE; + + /* Add the Greek tree. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev)); + + /* Make sure the rep-cache does not exist. */ + SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); + SVN_TEST_ASSERT(!exists); + + /* Build and verify the rep-cache. */ + ffd->rep_sharing_allowed = TRUE; + + input.start_rev = rev; + input.end_rev = rev; + SVN_ERR(svn_fs_ioctl(fs, SVN_FS_FS__IOCTL_BUILD_REP_CACHE, + &input, NULL, NULL, NULL, pool, pool)); + + SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); + SVN_TEST_ASSERT(exists); + + SVN_ERR(svn_fs_verify(fs_path, NULL, 0, SVN_INVALID_REVNUM, + NULL, NULL, NULL, NULL, pool)); + + return SVN_NO_ERROR; +} + /* The test table. */ @@ -455,6 +514,8 @@ static struct svn_test_descriptor_t test_funcs[] = "dump the P2L index"), SVN_TEST_OPTS_PASS(load_index, "load the P2L index"), + SVN_TEST_OPTS_PASS(build_rep_cache, + "build the representation cache"), SVN_TEST_NULL }; diff --git a/subversion/tests/libsvn_repos/authz-test.c b/subversion/tests/libsvn_repos/authz-test.c index 33e93233b80ba..b48a880c17fc4 100644 --- a/subversion/tests/libsvn_repos/authz-test.c +++ b/subversion/tests/libsvn_repos/authz-test.c @@ -522,7 +522,7 @@ static struct svn_test_descriptor_t test_funcs[] = "test svn_authz__get_global_rights"), SVN_TEST_PASS2(issue_4741_groups, "issue 4741 groups"), - SVN_TEST_XFAIL2(reposful_reposless_stanzas_inherit, + SVN_TEST_PASS2(reposful_reposless_stanzas_inherit, "[foo:/] inherits [/]"), SVN_TEST_NULL }; diff --git a/tools/backup/hot-backup.py.in b/tools/backup/hot-backup.py.in index 84b7c410b047a..a7ec37f3a015f 100755 --- a/tools/backup/hot-backup.py.in +++ b/tools/backup/hot-backup.py.in @@ -36,6 +36,7 @@ ###################################################################### import sys, os, getopt, stat, re, time, shutil, subprocess +import functools ###################################################################### # Global Settings @@ -193,7 +194,7 @@ def comparator(a, b): if not inca: return -1 elif not incb: - return 1; + return 1 elif (int(inca) < int(incb)): return -1 else: @@ -219,7 +220,7 @@ def get_youngest_revision(): raise Exception("Unable to find the youngest revision for repository '%s'" ": %s" % (repo_dir, stderr_lines[0].rstrip())) - return stdout_lines[0].strip() + return stdout_lines[0].strip().decode() ###################################################################### # Main @@ -255,7 +256,7 @@ regexp = re.compile("^" + re.escape(repo) + "-" + re.escape(youngest) + directory_list = os.listdir(backup_dir) young_list = [x for x in directory_list if regexp.search(x)] if young_list: - young_list.sort(comparator) + young_list.sort(key = functools.cmp_to_key(comparator)) increment = regexp.search(young_list.pop()).groupdict()['increment'] if increment: backup_subdir = os.path.join(backup_dir, repo + "-" + youngest + "-" @@ -348,7 +349,7 @@ if num_backups > 0: regexp = re.compile("^" + re.escape(repo) + "-[0-9]+(-[0-9]+)?" + ext_re + "$") directory_list = os.listdir(backup_dir) old_list = [x for x in directory_list if regexp.search(x)] - old_list.sort(comparator) + old_list.sort(key = functools.cmp_to_key(comparator)) del old_list[max(0,len(old_list)-num_backups):] for item in old_list: old_backup_item = os.path.join(backup_dir, item) diff --git a/tools/client-side/bash_completion b/tools/client-side/bash_completion index 44d15bb2e3628..2d06d8fa35667 100644 --- a/tools/client-side/bash_completion +++ b/tools/client-side/bash_completion @@ -1137,7 +1137,7 @@ _svnadmin () cur=${COMP_WORDS[COMP_CWORD]} # Possible expansions, without pure-prefix abbreviations such as "h". - cmds='crashtest create delrevprop deltify dump dump-revprops freeze \ + cmds='build-repcache crashtest create delrevprop deltify dump dump-revprops freeze \ help hotcopy info list-dblogs list-unused-dblogs \ load load-revprops lock lslocks lstxns pack recover rev-size rmlocks \ rmtxns setlog setrevprop setuuid unlock upgrade verify --version' @@ -1163,6 +1163,9 @@ _svnadmin () cmdOpts= case ${COMP_WORDS[1]} in + build-repcache) + cmdOpts="-r --revision -q --quiet -M --memory-cache-size" + ;; create) cmdOpts="--bdb-txn-nosync --bdb-log-keep --config-dir \ --fs-type --compatible-version" diff --git a/tools/client-side/svn-mergeinfo-normalizer/svn-mergeinfo-normalizer.c b/tools/client-side/svn-mergeinfo-normalizer/svn-mergeinfo-normalizer.c index 529621bf7e4fa..1973c6ea3df69 100644 --- a/tools/client-side/svn-mergeinfo-normalizer/svn-mergeinfo-normalizer.c +++ b/tools/client-side/svn-mergeinfo-normalizer/svn-mergeinfo-normalizer.c @@ -408,7 +408,10 @@ svn_min__check_cancel(void *baton) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; int opt_id; @@ -425,12 +428,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) svn_boolean_t force_interactive = FALSE; apr_hash_t *cfg_hash; svn_boolean_t read_pass_from_stdin = FALSE; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -946,7 +952,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/tools/client-side/svnconflict/svnconflict.c b/tools/client-side/svnconflict/svnconflict.c index dcc7576c734e8..e750cd1829420 100644 --- a/tools/client-side/svnconflict/svnconflict.c +++ b/tools/client-side/svnconflict/svnconflict.c @@ -632,7 +632,10 @@ svnconflict_resolve_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; int opt_id; @@ -647,12 +650,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) svn_config_t *cfg_config; apr_hash_t *cfg_hash; svn_boolean_t read_pass_from_stdin = FALSE; + const char **argv; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -949,7 +955,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/tools/dev/svnraisetreeconflict/svnraisetreeconflict.c b/tools/dev/svnraisetreeconflict/svnraisetreeconflict.c index a68b5d2d8e271..784c9bd8e9733 100644 --- a/tools/dev/svnraisetreeconflict/svnraisetreeconflict.c +++ b/tools/dev/svnraisetreeconflict/svnraisetreeconflict.c @@ -302,7 +302,10 @@ check_lib_versions(void) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { apr_getopt_t *os; const apr_getopt_option_t options[] = @@ -313,10 +316,13 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) {0, 0, 0, 0} }; apr_array_header_t *remaining_argv; + const char **argv; /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -383,7 +389,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/tools/dev/wc-ng/svn-wc-db-tester.c b/tools/dev/wc-ng/svn-wc-db-tester.c index ba63b6368018e..43cb6b0764c97 100644 --- a/tools/dev/wc-ng/svn-wc-db-tester.c +++ b/tools/dev/wc-ng/svn-wc-db-tester.c @@ -156,7 +156,10 @@ check_lib_versions(void) * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { apr_getopt_t *os; const apr_getopt_option_t options[] = @@ -167,10 +170,13 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) {0, 0, 0, 0} }; apr_array_header_t *remaining_argv; + const char **argv; /* Check library versions */ SVN_ERR(check_lib_versions()); + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); + #if defined(WIN32) || defined(__CYGWIN__) /* Set the working copy administrative directory name. */ if (getenv("SVN_ASP_DOT_NET_HACK")) @@ -237,7 +243,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; diff --git a/tools/dist/release.py b/tools/dist/release.py index 141f9376a3788..b6cf2912d5082 100755 --- a/tools/dist/release.py +++ b/tools/dist/release.py @@ -1558,7 +1558,7 @@ def write_changelog(args): mergeinfo = mergeinfo.splitlines() separator_pattern = re.compile('^-{72}$') - revline_pattern = re.compile('^r(\d+) \| [^\|]+ \| [^\|]+ \| \d+ lines?$') + revline_pattern = re.compile(r'^r(\d+) \| [^|]+ \| [^|]+ \| \d+ lines?$') changes_prefix_pattern = re.compile(r'^\[(U|D)?:?([^\]]+)?\](.+)$') changes_suffix_pattern = re.compile(r'^(.+)\[(U|D)?:?([^\]]+)?\]$') # TODO: push this into backport.status as a library function diff --git a/tools/hook-scripts/mailer/mailer.py b/tools/hook-scripts/mailer/mailer.py index 8e7fc93fcac39..0b578c33e6a9c 100755 --- a/tools/hook-scripts/mailer/mailer.py +++ b/tools/hook-scripts/mailer/mailer.py @@ -46,25 +46,21 @@ import os import sys -try: - # Python >=3.0 +if sys.hexversion >= 0x3000000: + PY3 = True import configparser - from urllib.parse import quote as urllib_parse_quote -except ImportError: - # Python <3.0 + from urllib.parse import quote as _url_quote +else: + PY3 = False import ConfigParser as configparser - from urllib import quote as urllib_parse_quote + from urllib import quote as _url_quote import time import subprocess -if sys.version_info[0] >= 3: - # Python >=3.0 - from io import StringIO -else: - # Python <3.0 - from cStringIO import StringIO +from io import BytesIO import smtplib import re import tempfile +import codecs # Minimal version of Subversion's bindings required _MIN_SVN_VERSION = [1, 5, 0] @@ -83,6 +79,28 @@ % ".".join([str(x) for x in _MIN_SVN_VERSION])) sys.exit(1) +# Absorb difference between Python 2 and Python >= 3 +if PY3: + def to_bytes(x): + return x.encode('utf-8') + + def to_str(x): + return x.decode('utf-8') + + # We never use sys.stdin nor sys.stdout TextIOwrapper. + _stdin = sys.stdin.buffer + _stdout = sys.stdout.buffer +else: + # Python 2 + def to_bytes(x): + return x + + def to_str(x): + return x + + _stdin = sys.stdin + _stdout = sys.stdout + SEPARATOR = '=' * 78 @@ -101,7 +119,10 @@ def main(pool, cmd, config_fname, repos_dir, cmd_args): revision = int(cmd_args[0]) author = cmd_args[1] propname = cmd_args[2] - action = (cmd == 'propchange2' and cmd_args[3] or 'A') + if cmd == 'propchange2' and cmd_args[3]: + action = cmd_args[3] + else: + action = 'A' repos = Repository(repos_dir, revision, pool) # Override the repos revision author with the author of the propchange repos.author = author @@ -127,7 +148,7 @@ def main(pool, cmd, config_fname, repos_dir, cmd_args): def remove_leading_slashes(path): - while path and path[0] == '/': + while path and path[0:1] == b'/': path = path[1:] return path @@ -158,8 +179,16 @@ def make_subject(self, group, params): except ValueError: truncate_subject = 0 - if truncate_subject and len(subject) > truncate_subject: - subject = subject[:(truncate_subject - 3)] + "..." + # truncate subject as UTF-8 string. + # Note: there still exists an issue on combining characters. + if truncate_subject: + bsubject = to_bytes(subject) + if len(bsubject) > truncate_subject: + idx = truncate_subject - 2 + while b'\x80' <= bsubject[idx-1:idx] <= b'\xbf': + idx -= 1 + subject = to_str(bsubject[:idx-1]) + "..." + return subject def start(self, group, params): @@ -177,11 +206,15 @@ def finish(self): representation.""" raise NotImplementedError - def write(self, output): + def write_binary(self, output): """Override this method. - Append the literal text string OUTPUT to the output representation.""" + Append the binary data OUTPUT to the output representation.""" raise NotImplementedError + def write(self, output): + """Append the literal text string OUTPUT to the output representation.""" + return self.write_binary(to_bytes(output)) + def run(self, cmd): """Override this method, if the default implementation is not sufficient. Execute CMD, writing the stdout produced to the output representation.""" @@ -192,7 +225,7 @@ def run(self, cmd): buf = pipe_ob.stdout.read(self._CHUNKSIZE) while buf: - self.write(buf) + self.write_binary(buf) buf = pipe_ob.stdout.read(self._CHUNKSIZE) # wait on the child so we don't end up with a billion zombies @@ -234,7 +267,7 @@ def _rfc2047_encode(self, hdr): # Return the result of splitting HDR into tokens (on space # characters), encoding (per RFC2047) each token as necessary, and # slapping 'em back to together again. - from email.Header import Header + from email.header import Header def _maybe_encode_header(hdr_token): try: @@ -246,7 +279,7 @@ def _maybe_encode_header(hdr_token): return ' '.join(map(_maybe_encode_header, hdr.split())) def mail_headers(self, group, params): - from email import Utils + from email import utils subject = self._rfc2047_encode(self.make_subject(group, params)) from_hdr = self._rfc2047_encode(self.from_addr) @@ -265,7 +298,7 @@ def mail_headers(self, group, params): 'X-Svn-Commit-Revision: %d\n' \ 'X-Svn-Commit-Repository: %s\n' \ % (from_hdr, to_hdr, subject, - Utils.formatdate(), Utils.make_msgid(), group, + utils.formatdate(), utils.make_msgid(), group, self.repos.author or 'no_author', self.repos.rev, os.path.basename(self.repos.repos_dir)) if self.reply_to: @@ -279,8 +312,8 @@ class SMTPOutput(MailedOutput): def start(self, group, params): MailedOutput.start(self, group, params) - self.buffer = StringIO() - self.write = self.buffer.write + self.buffer = BytesIO() + self.write_binary = self.buffer.write self.write(self.mail_headers(group, params)) @@ -359,7 +392,7 @@ class StandardOutput(OutputBase): def __init__(self, cfg, repos, prefix_param): OutputBase.__init__(self, cfg, repos, prefix_param) - self.write = sys.stdout.write + self.write_binary = _stdout.write def start(self, group, params): self.write("Group: " + (group or "defaults") + "\n") @@ -368,6 +401,12 @@ def start(self, group, params): def finish(self): pass + if (PY3 and (codecs.lookup(sys.stdout.encoding) != codecs.lookup('utf-8'))): + def write(self, output): + """Write text as *default* encoding string""" + return self.write_binary(output.encode(sys.stdout.encoding, + 'backslashreplace')) + class PipeOutput(MailedOutput): "Deliver a mail message to an MTA via a pipe." @@ -388,7 +427,7 @@ def start(self, group, params): # construct the pipe for talking to the mailer self.pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE, close_fds=sys.platform != "win32") - self.write = self.pipe.stdin.write + self.write_binary = self.pipe.stdin.write # start writing out the mail message self.write(self.mail_headers(group, params)) @@ -429,12 +468,12 @@ def __init__(self, pool, cfg, repos): self.changelist = sorted(editor.get_changes().items()) - log = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '' + log = to_str(repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or b'') # collect the set of groups and the unique sets of params for the options self.groups = { } for path, change in self.changelist: - for (group, params) in self.cfg.which_groups(path, log): + for (group, params) in self.cfg.which_groups(to_str(path), log): # turn the params into a hashable object and stash it away param_list = sorted(params.items()) # collect the set of paths belonging to this group @@ -448,6 +487,7 @@ def __init__(self, pool, cfg, repos): # figure out the changed directories dirs = { } for path, change in self.changelist: + path = to_str(path) if change.item_kind == svn.core.svn_node_dir: dirs[path] = None else: @@ -484,7 +524,7 @@ def generate(self): # build a renderer, tied to our output stream renderer = TextCommitRenderer(self.output) - for (group, param_tuple), (params, paths) in self.groups.items(): + for (group, param_tuple), (params, paths) in sorted(self.groups.items()): try: self.output.start(group, params) @@ -538,7 +578,7 @@ def generate(self): elif self.action == 'M': self.output.write('Property diff:\n') tempfile1 = tempfile.NamedTemporaryFile() - tempfile1.write(sys.stdin.read()) + tempfile1.write(_stdin.read()) tempfile1.flush() tempfile2 = tempfile.NamedTemporaryFile() tempfile2.write(self.repos.get_rev_prop(self.propname)) @@ -600,7 +640,7 @@ def __init__(self, pool, cfg, repos, author, do_lock): or 'unlock_subject_prefix')) # read all the locked paths from STDIN and strip off the trailing newlines - self.dirlist = [x.rstrip() for x in sys.stdin.readlines()] + self.dirlist = [to_str(x).rstrip() for x in _stdin.readlines()] # collect the set of groups and the unique sets of params for the options self.groups = { } @@ -629,11 +669,12 @@ def __init__(self, pool, cfg, repos, author, do_lock): # The lock comment is the same for all paths, so we can just pull # the comment for the first path in the dirlist and cache it. self.lock = svn.fs.svn_fs_get_lock(self.repos.fs_ptr, - self.dirlist[0], self.pool) + to_bytes(self.dirlist[0]), + self.pool) def generate(self): ret = 0 - for (group, param_tuple), (params, paths) in self.groups.items(): + for (group, param_tuple), (params, paths) in sorted(self.groups.items()): try: self.output.start(group, params) @@ -702,9 +743,9 @@ def _get_url(self, action, repos_rev, change): # parameters for the configuration module, otherwise we may get # KeyError exceptions. params = self.params.copy() - params['path'] = change.path and urllib_parse_quote(change.path) or None - params['base_path'] = change.base_path and urllib_parse_quote(change.base_path) \ - or None + params['path'] = _url_quote(change.path) if change.path else None + params['base_path'] = (_url_quote(change.base_path) + if change.base_path else None) params['rev'] = repos_rev params['base_rev'] = change.base_rev @@ -758,7 +799,7 @@ def generate_content(renderer, cfg, repos, changelist, group, params, paths, author=repos.author, date=date, rev=repos.rev, - log=repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or '', + log=to_str(repos.get_rev_prop(svn.core.SVN_PROP_REVISION_LOG) or b''), commit_url=commit_url, added_data=generate_list('A', changelist, paths, True), replaced_data=generate_list('R', changelist, paths, True), @@ -866,7 +907,9 @@ def __getitem__(self, idx): # figure out if/how to generate a diff - base_path = remove_leading_slashes(change.base_path) + base_path_bytes = remove_leading_slashes(change.base_path) + base_path = (to_str(base_path_bytes) + if base_path_bytes is not None else None) if change.action == svn.repos.CHANGE_ACTION_DELETE: # it was delete. kind = 'D' @@ -877,7 +920,7 @@ def __getitem__(self, idx): # show the diff? if self.diffsels.delete: diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev), - base_path, None, None, self.pool) + base_path_bytes, None, None, self.pool) label1 = '%s\t%s\t(r%s)' % (base_path, self.date, change.base_rev) label2 = '/dev/null\t00:00:00 1970\t(deleted)' @@ -898,13 +941,13 @@ def __getitem__(self, idx): # show the diff? if self.diffsels.modify: diff = svn.fs.FileDiff(self.repos.get_root(change.base_rev), - base_path, + base_path_bytes, self.repos.root_this, change.path, self.pool) - label1 = '%s\t%s\t(r%s, copy source)' \ - % (base_path, base_date, change.base_rev) - label2 = '%s\t%s\t(r%s)' \ - % (change.path, self.date, self.repos.rev) + label1 = ('%s\t%s\t(r%s, copy source)' + % (base_path, base_date, change.base_rev)) + label2 = ('%s\t%s\t(r%s)' + % (to_str(change.path), self.date, self.repos.rev)) singular = False else: # this file was copied. @@ -912,11 +955,12 @@ def __getitem__(self, idx): if self.diffsels.copy: diff = svn.fs.FileDiff(None, None, self.repos.root_this, change.path, self.pool) - label1 = '/dev/null\t00:00:00 1970\t' \ - '(empty, because file is newly added)' - label2 = '%s\t%s\t(r%s, copy of r%s, %s)' \ - % (change.path, self.date, self.repos.rev, \ - change.base_rev, base_path) + label1 = ('/dev/null\t00:00:00 1970\t' + '(empty, because file is newly added)') + label2 = ('%s\t%s\t(r%s, copy of r%s, %s)' + % (to_str(change.path), + self.date, self.repos.rev, change.base_rev, + base_path)) singular = False else: # the file was added. @@ -932,7 +976,7 @@ def __getitem__(self, idx): label1 = '/dev/null\t00:00:00 1970\t' \ '(empty, because file is newly added)' label2 = '%s\t%s\t(r%s)' \ - % (change.path, self.date, self.repos.rev) + % (to_str(change.path), self.date, self.repos.rev) singular = True elif not change.text_changed: @@ -954,7 +998,7 @@ def __getitem__(self, idx): label1 = '%s\t%s\t(r%s)' \ % (base_path, base_date, change.base_rev) label2 = '%s\t%s\t(r%s)' \ - % (change.path, self.date, self.repos.rev) + % (to_str(change.path), self.date, self.repos.rev) singular = False if diff: @@ -977,7 +1021,7 @@ def __getitem__(self, idx): # return a data item for this diff return _data( path=change.path, - base_path=base_path, + base_path=base_path_bytes, base_rev=change.base_rev, diff=diff, diff_url=diff_url, @@ -1144,7 +1188,7 @@ def _render_list(self, header, data_list): props = ' (props changed)' else: props = '' - w(' %s%s%s\n' % (d.path, is_dir, props)) + w(' %s%s%s\n' % (to_str(d.path), is_dir, props)) if d.copied: if is_dir: text = '' @@ -1153,7 +1197,7 @@ def _render_list(self, header, data_list): else: text = ' unchanged' w(' - copied%s from r%d, %s%s\n' - % (text, d.base_rev, d.base_path, is_dir)) + % (text, d.base_rev, to_str(d.base_path), is_dir)) def _render_diffs(self, diffs, section_header): """Render diffs. Write the SECTION_HEADER if there are actually @@ -1170,18 +1214,20 @@ def _render_diffs(self, diffs, section_header): w(section_header) section_header_printed = True if diff.kind == 'D': - w('\nDeleted: %s\n' % diff.base_path) + w('\nDeleted: %s\n' % to_str(diff.base_path)) elif diff.kind == 'A': - w('\nAdded: %s\n' % diff.path) + w('\nAdded: %s\n' % to_str(diff.path)) elif diff.kind == 'C': w('\nCopied: %s (from r%d, %s)\n' - % (diff.path, diff.base_rev, diff.base_path)) + % (to_str(diff.path), diff.base_rev, + to_str(diff.base_path))) elif diff.kind == 'W': w('\nCopied and modified: %s (from r%d, %s)\n' - % (diff.path, diff.base_rev, diff.base_path)) + % (to_str(diff.path), diff.base_rev, + to_str(diff.base_path))) else: # kind == 'M' - w('\nModified: %s\n' % diff.path) + w('\nModified: %s\n' % to_str(diff.path)) if diff.diff_url: w('URL: %s\n' % diff.diff_url) @@ -1198,8 +1244,9 @@ def _render_diffs(self, diffs, section_header): w('Binary file (source and/or target). No diff available.\n') continue + wb = self.output.write_binary for line in diff.content: - w(line.raw) + wb(line.raw) class Repository: @@ -1218,6 +1265,8 @@ def __init__(self, repos_dir, rev, pool): self.root_this = self.get_root(rev) self.author = self.get_rev_prop(svn.core.SVN_PROP_REVISION_AUTHOR) + if self.author is not None: + self.author = to_str(self.author) def get_rev_prop(self, propname, rev = None): if not rev: @@ -1507,7 +1556,7 @@ def usage(): usage() cmd = sys.argv[1] - repos_dir = svn.core.svn_path_canonicalize(sys.argv[2]) + repos_dir = to_str(svn.core.svn_path_canonicalize(to_bytes(sys.argv[2]))) try: expected_args = cmd_list[cmd] except KeyError: diff --git a/tools/hook-scripts/mailer/tests/mailer-t1.output b/tools/hook-scripts/mailer/tests/mailer-t1.output index 10466ff9345b5..58606b23ceb1e 100644 --- a/tools/hook-scripts/mailer/tests/mailer-t1.output +++ b/tools/hook-scripts/mailer/tests/mailer-t1.output @@ -1,4 +1,4 @@ -Group: file +Group: All Subject: r1 - dir1 dir2 Author: mailer test @@ -9,9 +9,43 @@ Log: initial load Added: + dir1/ + dir1/file3 + dir1/file4 + dir2/ + dir2/file5 + dir2/file6 file1 file2 +Added: dir1/file3 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ dir1/file3 Sun Sep 9 01:46:40 2001 (r1) +@@ -0,0 +1 @@ ++file3 + +Added: dir1/file4 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ dir1/file4 Sun Sep 9 01:46:40 2001 (r1) +@@ -0,0 +1 @@ ++file4 + +Added: dir2/file5 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ dir2/file5 Sun Sep 9 01:46:40 2001 (r1) +@@ -0,0 +1 @@ ++file5 + +Added: dir2/file6 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ dir2/file6 Sun Sep 9 01:46:40 2001 (r1) +@@ -0,0 +1 @@ ++file6 + Added: file1 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) @@ -25,7 +59,7 @@ Added: file2 +++ file2 Sun Sep 9 01:46:40 2001 (r1) @@ -0,0 +1 @@ +file2 -Group: file plus other areas +Group: file Subject: r1 - dir1 dir2 Author: mailer test @@ -39,15 +73,6 @@ Added: file1 file2 -Changes in other areas also in this revision: -Added: - dir1/ - dir1/file3 - dir1/file4 - dir2/ - dir2/file5 - dir2/file6 - Added: file1 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) @@ -61,37 +86,7 @@ Added: file2 +++ file2 Sun Sep 9 01:46:40 2001 (r1) @@ -0,0 +1 @@ +file2 - -Diffs of changes in other areas also in this revision: - -Added: dir1/file3 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ dir1/file3 Sun Sep 9 01:46:40 2001 (r1) -@@ -0,0 +1 @@ -+file3 - -Added: dir1/file4 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ dir1/file4 Sun Sep 9 01:46:40 2001 (r1) -@@ -0,0 +1 @@ -+file4 - -Added: dir2/file5 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ dir2/file5 Sun Sep 9 01:46:40 2001 (r1) -@@ -0,0 +1 @@ -+file5 - -Added: dir2/file6 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ dir2/file6 Sun Sep 9 01:46:40 2001 (r1) -@@ -0,0 +1 @@ -+file6 -Group: All +Group: file plus other areas Subject: r1 - dir1 dir2 Author: mailer test @@ -101,6 +96,11 @@ New Revision: 1 Log: initial load +Added: + file1 + file2 + +Changes in other areas also in this revision: Added: dir1/ dir1/file3 @@ -108,8 +108,22 @@ Added: dir2/ dir2/file5 dir2/file6 - file1 - file2 + +Added: file1 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ file1 Sun Sep 9 01:46:40 2001 (r1) +@@ -0,0 +1 @@ ++file1 + +Added: file2 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ file2 Sun Sep 9 01:46:40 2001 (r1) +@@ -0,0 +1 @@ ++file2 + +Diffs of changes in other areas also in this revision: Added: dir1/file3 ============================================================================== @@ -138,21 +152,7 @@ Added: dir2/file6 +++ dir2/file6 Sun Sep 9 01:46:40 2001 (r1) @@ -0,0 +1 @@ +file6 - -Added: file1 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ file1 Sun Sep 9 01:46:40 2001 (r1) -@@ -0,0 +1 @@ -+file1 - -Added: file2 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ file2 Sun Sep 9 01:46:40 2001 (r1) -@@ -0,0 +1 @@ -+file2 -Group: file +Group: All Subject: r2 - dir1 dir2 Author: mailer test @@ -163,9 +163,19 @@ Log: two file changes. Fixes Blah#123 Modified: + dir1/ (props changed) + dir2/file5 file1 (props changed) file2 (contents, props changed) +Modified: dir2/file5 +============================================================================== +--- dir2/file5 Sun Sep 9 01:46:40 2001 (r1) ++++ dir2/file5 Sun Sep 9 04:33:20 2001 (r2) +@@ -1 +1,2 @@ + file5 ++change C2 + Modified: file2 ============================================================================== --- file2 Sun Sep 9 01:46:40 2001 (r1) @@ -204,7 +214,7 @@ Modified: file2 @@ -1 +1,2 @@ file2 +change C1 -Group: All +Group: file Subject: r2 - dir1 dir2 Author: mailer test @@ -215,19 +225,9 @@ Log: two file changes. Fixes Blah#123 Modified: - dir1/ (props changed) - dir2/file5 file1 (props changed) file2 (contents, props changed) -Modified: dir2/file5 -============================================================================== ---- dir2/file5 Sun Sep 9 01:46:40 2001 (r1) -+++ dir2/file5 Sun Sep 9 04:33:20 2001 (r2) -@@ -1 +1,2 @@ - file5 -+change C2 - Modified: file2 ============================================================================== --- file2 Sun Sep 9 01:46:40 2001 (r1) @@ -286,6 +286,11 @@ Added: - copied unchanged from r2, file1 dir3/ (props changed) - copied from r2, dir1/ +Replaced: + dir3/file3 + - copied unchanged from r1, dir1/file3 + dir3/file4 + - copied unchanged from r1, dir1/file4 Copied: dir2/file7 (from r2, file1) ============================================================================== @@ -293,6 +298,20 @@ Copied: dir2/file7 (from r2, file1) +++ dir2/file7 Sun Sep 9 07:20:00 2001 (r3, copy of r2, file1) @@ -0,0 +1 @@ +file1 + +Copied: dir3/file3 (from r1, dir1/file3) +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ dir3/file3 Sun Sep 9 07:20:00 2001 (r3, copy of r1, dir1/file3) +@@ -0,0 +1 @@ ++file3 + +Copied: dir3/file4 (from r1, dir1/file4) +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ dir3/file4 Sun Sep 9 07:20:00 2001 (r3, copy of r1, dir1/file4) +@@ -0,0 +1 @@ ++file4 Group: All Subject: r4 - dir3 @@ -314,7 +333,7 @@ Copied and modified: dir3/file8 (from r2, file1) @@ -1 +1,2 @@ file1 +change C3 -Group: file +Group: All Subject: r5 - dir1 dir3 Author: mailer test @@ -325,8 +344,10 @@ Log: changes and deletes of properties Modified: + dir1/ (props changed) + dir3/ (props changed) file2 (props changed) -Group: file plus other areas +Group: file Subject: r5 - dir1 dir3 Author: mailer test @@ -338,12 +359,7 @@ changes and deletes of properties Modified: file2 (props changed) - -Changes in other areas also in this revision: -Modified: - dir1/ (props changed) - dir3/ (props changed) -Group: All +Group: file plus other areas Subject: r5 - dir1 dir3 Author: mailer test @@ -353,11 +369,14 @@ New Revision: 5 Log: changes and deletes of properties +Modified: + file2 (props changed) + +Changes in other areas also in this revision: Modified: dir1/ (props changed) dir3/ (props changed) - file2 (props changed) -Group: file +Group: All Subject: r6 - dir1 dir4 Author: mailer test @@ -368,7 +387,18 @@ Log: mixed addition and change. Fixes Blaz#456 Blah#987 Added: + dir4/ file9 +Modified: + dir1/file3 + +Modified: dir1/file3 +============================================================================== +--- dir1/file3 Sun Sep 9 12:53:20 2001 (r5) ++++ dir1/file3 Sun Sep 9 15:40:00 2001 (r6) +@@ -1 +1,2 @@ + file3 ++change C4 Added: file9 ============================================================================== @@ -376,8 +406,8 @@ Added: file9 +++ file9 Sun Sep 9 15:40:00 2001 (r6) @@ -0,0 +1 @@ +file9 -Group: file plus other areas -Subject: r6 - dir1 dir4 +Group: bugtracker +Subject: Fix for Blah#987: r6 - dir1 dir4 Author: mailer test Date: Sun Sep 9 15:40:00 2001 @@ -386,24 +416,12 @@ New Revision: 6 Log: mixed addition and change. Fixes Blaz#456 Blah#987 -Added: - file9 - -Changes in other areas also in this revision: Added: dir4/ + file9 Modified: dir1/file3 -Added: file9 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ file9 Sun Sep 9 15:40:00 2001 (r6) -@@ -0,0 +1 @@ -+file9 - -Diffs of changes in other areas also in this revision: - Modified: dir1/file3 ============================================================================== --- dir1/file3 Sun Sep 9 12:53:20 2001 (r5) @@ -411,6 +429,13 @@ Modified: dir1/file3 @@ -1 +1,2 @@ file3 +change C4 + +Added: file9 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ file9 Sun Sep 9 15:40:00 2001 (r6) +@@ -0,0 +1 @@ ++file9 Group: bugtracker Subject: Fix for Blaz#456: r6 - dir1 dir4 @@ -441,8 +466,8 @@ Added: file9 +++ file9 Sun Sep 9 15:40:00 2001 (r6) @@ -0,0 +1 @@ +file9 -Group: bugtracker -Subject: Fix for Blah#987: r6 - dir1 dir4 +Group: file +Subject: r6 - dir1 dir4 Author: mailer test Date: Sun Sep 9 15:40:00 2001 @@ -452,18 +477,7 @@ Log: mixed addition and change. Fixes Blaz#456 Blah#987 Added: - dir4/ file9 -Modified: - dir1/file3 - -Modified: dir1/file3 -============================================================================== ---- dir1/file3 Sun Sep 9 12:53:20 2001 (r5) -+++ dir1/file3 Sun Sep 9 15:40:00 2001 (r6) -@@ -1 +1,2 @@ - file3 -+change C4 Added: file9 ============================================================================== @@ -471,7 +485,7 @@ Added: file9 +++ file9 Sun Sep 9 15:40:00 2001 (r6) @@ -0,0 +1 @@ +file9 -Group: All +Group: file plus other areas Subject: r6 - dir1 dir4 Author: mailer test @@ -482,11 +496,23 @@ Log: mixed addition and change. Fixes Blaz#456 Blah#987 Added: - dir4/ file9 + +Changes in other areas also in this revision: +Added: + dir4/ Modified: dir1/file3 +Added: file9 +============================================================================== +--- /dev/null 00:00:00 1970 (empty, because file is newly added) ++++ file9 Sun Sep 9 15:40:00 2001 (r6) +@@ -0,0 +1 @@ ++file9 + +Diffs of changes in other areas also in this revision: + Modified: dir1/file3 ============================================================================== --- dir1/file3 Sun Sep 9 12:53:20 2001 (r5) @@ -494,13 +520,47 @@ Modified: dir1/file3 @@ -1 +1,2 @@ file3 +change C4 +Group: All +Subject: r7 - dir1 dir2 dir3 dir3/dir5 -Added: file9 +Author: mailer test +Date: Sun Sep 9 18:26:40 2001 +New Revision: 7 + +Log: +adds, deletes, and a change + +Added: + dir1/file10 + dir3/dir5/ +Deleted: + dir2/ + file2 +Modified: + dir3/file3 + +Added: dir1/file10 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ file9 Sun Sep 9 15:40:00 2001 (r6) ++++ dir1/file10 Sun Sep 9 18:26:40 2001 (r7) @@ -0,0 +1 @@ -+file9 ++file10 + +Modified: dir3/file3 +============================================================================== +--- dir3/file3 Sun Sep 9 15:40:00 2001 (r6) ++++ dir3/file3 Sun Sep 9 18:26:40 2001 (r7) +@@ -1 +1,2 @@ + file3 ++change C5 + +Deleted: file2 +============================================================================== +--- file2 Sun Sep 9 18:26:40 2001 (r6) ++++ /dev/null 00:00:00 1970 (deleted) +@@ -1,2 +0,0 @@ +-file2 +-change C1 Group: file Subject: r7 - dir1 dir2 dir3 dir3/dir5 @@ -568,47 +628,6 @@ Modified: dir3/file3 file3 +change C5 Group: All -Subject: r7 - dir1 dir2 dir3 dir3/dir5 - -Author: mailer test -Date: Sun Sep 9 18:26:40 2001 -New Revision: 7 - -Log: -adds, deletes, and a change - -Added: - dir1/file10 - dir3/dir5/ -Deleted: - dir2/ - file2 -Modified: - dir3/file3 - -Added: dir1/file10 -============================================================================== ---- /dev/null 00:00:00 1970 (empty, because file is newly added) -+++ dir1/file10 Sun Sep 9 18:26:40 2001 (r7) -@@ -0,0 +1 @@ -+file10 - -Modified: dir3/file3 -============================================================================== ---- dir3/file3 Sun Sep 9 15:40:00 2001 (r6) -+++ dir3/file3 Sun Sep 9 18:26:40 2001 (r7) -@@ -1 +1,2 @@ - file3 -+change C5 - -Deleted: file2 -============================================================================== ---- file2 Sun Sep 9 18:26:40 2001 (r6) -+++ /dev/null 00:00:00 1970 (deleted) -@@ -1,2 +0,0 @@ --file2 --change C1 -Group: All Subject: r8 - in dir6: . dir5 Author: mailer test @@ -644,7 +663,7 @@ Modified: dir6/file4 @@ -1 +1,2 @@ file4 +change C6 -Group: file +Group: All Subject: r9 - Author: mailer test @@ -662,7 +681,7 @@ Modified: Added: file11 ============================================================================== Binary file. No diff available. -Group: file plus other areas +Group: file Subject: r9 - Author: mailer test @@ -680,7 +699,7 @@ Modified: Added: file11 ============================================================================== Binary file. No diff available. -Group: All +Group: file plus other areas Subject: r9 - Author: mailer test @@ -698,7 +717,7 @@ Modified: Added: file11 ============================================================================== Binary file. No diff available. -Group: file +Group: All Subject: r10 - Author: mailer test @@ -715,7 +734,7 @@ Modified: Modified: file11 ============================================================================== Binary file (source and/or target). No diff available. -Group: file plus other areas +Group: file Subject: r10 - Author: mailer test @@ -732,7 +751,7 @@ Modified: Modified: file11 ============================================================================== Binary file (source and/or target). No diff available. -Group: All +Group: file plus other areas Subject: r10 - Author: mailer test diff --git a/tools/hook-scripts/mailer/tests/mailer-tweak.py b/tools/hook-scripts/mailer/tests/mailer-tweak.py index 08059803a7acc..e1ab053e6e9e9 100755 --- a/tools/hook-scripts/mailer/tests/mailer-tweak.py +++ b/tools/hook-scripts/mailer/tests/mailer-tweak.py @@ -50,10 +50,10 @@ def tweak_dates(pool, home='.'): for i in range(fs.youngest_rev(fsob, pool)): # convert secs into microseconds, then a string - date = core.svn_time_to_cstring((DATE_BASE+i*DATE_INCR) * 1000000L, pool) + date = core.svn_time_to_cstring((DATE_BASE+i*DATE_INCR) * 1000000, pool) #print date fs.change_rev_prop(fsob, i+1, core.SVN_PROP_REVISION_DATE, date, pool) - fs.change_rev_prop(fsob, i+1, core.SVN_PROP_REVISION_AUTHOR, 'mailer test', pool) + fs.change_rev_prop(fsob, i+1, core.SVN_PROP_REVISION_AUTHOR, b'mailer test', pool) def main(): if len(sys.argv) != 2: diff --git a/tools/hook-scripts/svnperms.py b/tools/hook-scripts/svnperms.py index 4275b1b15b28f..ef93621fafecc 100755 --- a/tools/hook-scripts/svnperms.py +++ b/tools/hook-scripts/svnperms.py @@ -137,7 +137,8 @@ def parse_groups(self, groupsiter): try: groupusers.extend(self._group[token[1:]]) except KeyError: - raise Error, "group '%s' not found" % token[1:] + raise Error("group '%s' not found" % \ + token[1:]) else: groupusers.append(token) self._group[option] = groupusers diff --git a/tools/server-side/svnauthz.c b/tools/server-side/svnauthz.c index 310757ca465e6..94d28cc4be7a5 100644 --- a/tools/server-side/svnauthz.c +++ b/tools/server-side/svnauthz.c @@ -490,7 +490,10 @@ canonicalize_access_file(const char **canonicalized_access_file, * return SVN_NO_ERROR. */ static svn_error_t * -sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) +sub_main(int *exit_code, + int argc, + const svn_cmdline__argv_char_t *cmdline_argv[], + apr_pool_t *pool) { svn_error_t *err; @@ -499,6 +502,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) apr_getopt_t *os; apr_array_header_t *received_opts; int i; + const char **argv; + + SVN_ERR(svn_cmdline__get_cstring_argv(&argv, argc, cmdline_argv, pool)); /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); @@ -752,14 +758,14 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } int -main(int argc, const char *argv[]) +SVN_CMDLINE__MAIN(int argc, const svn_cmdline__argv_char_t *argv[]) { apr_pool_t *pool; int exit_code = EXIT_SUCCESS; svn_error_t *err; /* Initialize the app. Send all error messages to 'stderr'. */ - if (svn_cmdline_init(argv[0], stderr) != EXIT_SUCCESS) + if (svn_cmdline_init("svnauthz", stderr) != EXIT_SUCCESS) return EXIT_FAILURE; pool = svn_pool_create(NULL); diff --git a/win-tests.py b/win-tests.py index 1b7fdfa52aec1..bc7144464581f 100644 --- a/win-tests.py +++ b/win-tests.py @@ -772,6 +772,9 @@ def _svn_authz_repo(self): local_tmp = os.path.join(self.abs_builddir, CMDLINE_TEST_SCRIPT_NATIVE_PATH, 'svn-test-work', 'local_tmp') + repositories = os.path.join(self.abs_builddir, + CMDLINE_TEST_SCRIPT_NATIVE_PATH, + 'svn-test-work', 'repositories') return \ '' + '\n' \ ' DAV svn' + '\n' \ @@ -787,6 +790,17 @@ def _svn_authz_repo(self): ' ' + '\n' \ ' SVNPathAuthz ' + self.path_authz_option + '\n' \ '' + '\n' \ + '' + '\n' \ + ' DAV svn' + '\n' \ + ' SVNParentPath ' + repositories + '\n' \ + ' AuthzSVNReposRelativeAccessFile "^/authz"\n' \ + ' SVNAdvertiseV2Protocol ' + self.httpv2_option + '\n' \ + ' AuthType Basic' + '\n' \ + ' AuthName "Subversion Repository"' + '\n' \ + ' AuthUserFile ' + self._quote(self.httpd_users) + '\n' \ + ' Require valid-user' + '\n' \ + ' SVNPathAuthz ' + self.path_authz_option + '\n' \ + '' + '\n' \ '' + '\n' \ ' DAV svn' + '\n' \ ' SVNParentPath ' + local_tmp + '\n' \ @@ -1263,7 +1277,11 @@ def stop(self): or isinstance(i, gen_base.TargetSWIGLib)) and i.lang == 'python': src = os.path.join(abs_objdir, i.filename) - copy_changed_file(src, to_dir=swig_py_libsvn) + basename = os.path.basename(src) + if sys.version_info[:2] >= (3, 5) \ + and basename.endswith('.pyd') and objdir == 'Debug': + basename = basename[:-4] + '_d.pyd' + copy_changed_file(src, os.path.join(swig_py_libsvn, basename)) py_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'python') @@ -1285,7 +1303,8 @@ def stop(self): if 'PYTHONPATH' in os.environ: pythonpath += os.pathsep + os.environ['PYTHONPATH'] - python_exe = 'python.exe' + python_exe = sys.executable if objdir != 'Debug' else \ + os.path.join(os.path.dirname(sys.executable), 'python_d.exe') old_cwd = os.getcwd() try: os.environ['PYTHONPATH'] = pythonpath @@ -1314,7 +1333,7 @@ def stop(self): ruby_args = [ '-I', os.path.join(abs_srcdir, ruby_subdir), os.path.join(abs_srcdir, ruby_subdir, 'test', 'run-test.rb'), - '--verbose' + '--collector=dir', '--verbose', ] print('-- Running Swig Ruby tests --')