diff options
author | Andrej Shadura <andrew.shadura@collabora.co.uk> | 2020-01-18 16:34:23 +0100 |
---|---|---|
committer | Andrej Shadura <andrew.shadura@collabora.co.uk> | 2020-01-18 16:34:23 +0100 |
commit | 3ffd22a8af524ebe47f02ab4ae091e5e57951b1d (patch) | |
tree | 3b633ee3e8b2ab5150d16ac08aa26befe904e0f0 | |
parent | 2ae17de0cd7dc785fc908fed8e636a20a1d599fc (diff) | |
parent | 70c6b83ca2d46cebe258174cd3c4b131d5cc82a9 (diff) |
Update upstream source from tag 'upstream/0.10.2_git20191228+2423bf1'
Update to upstream version '0.10.2~git20191228+2423bf1'
with Debian dir 86dbb455d49537fc4349d277d4055e0ea536d4c8
-rw-r--r-- | .bzrignore | 10 | ||||
-rw-r--r-- | .gitignore | 15 | ||||
-rw-r--r-- | .hgignore | 7 | ||||
-rw-r--r-- | .testr.conf | 4 | ||||
-rw-r--r-- | .travis.yml | 51 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | NEWS | 13 | ||||
-rw-r--r-- | PKG-INFO | 24 | ||||
-rw-r--r-- | appveyor.yml | 96 | ||||
-rw-r--r-- | build.cmd | 21 | ||||
-rw-r--r-- | setup.cfg | 2 | ||||
-rwxr-xr-x | setup.py | 39 | ||||
-rw-r--r-- | subvertpy/__init__.py | 4 | ||||
-rw-r--r-- | subvertpy/_ra.c | 127 | ||||
-rw-r--r-- | subvertpy/client.c | 938 | ||||
-rw-r--r-- | subvertpy/editor.c | 29 | ||||
-rw-r--r-- | subvertpy/properties.py | 10 | ||||
-rw-r--r-- | subvertpy/ra_svn.py | 11 | ||||
-rw-r--r-- | subvertpy/repos.c | 11 | ||||
-rw-r--r-- | subvertpy/subr.c | 130 | ||||
-rw-r--r-- | subvertpy/tests/__init__.py | 42 | ||||
-rw-r--r-- | subvertpy/tests/test_client.py | 6 | ||||
-rw-r--r-- | subvertpy/tests/test_marshall.py | 16 | ||||
-rw-r--r-- | subvertpy/tests/test_ra.py | 4 | ||||
-rw-r--r-- | subvertpy/tests/test_repos.py | 5 | ||||
-rw-r--r-- | subvertpy/tests/test_subr.py | 64 | ||||
-rw-r--r-- | subvertpy/tests/test_wc.py | 364 | ||||
-rw-r--r-- | subvertpy/util.c | 452 | ||||
-rw-r--r-- | subvertpy/util.h | 18 | ||||
-rw-r--r-- | subvertpy/wc.c | 3767 | ||||
-rw-r--r-- | subvertpy/wc.h | 21 | ||||
-rw-r--r-- | subvertpy/wc_adm.c | 2237 | ||||
-rw-r--r-- | tox.ini | 9 |
33 files changed, 5544 insertions, 3014 deletions
diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 00000000..befb80e5 --- /dev/null +++ b/.bzrignore @@ -0,0 +1,10 @@ +build +_trial_temp +dist +MANIFEST +.coverage +tags +.testrepository +.noseids +apidocs +.pybuild diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f38e1928 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +build +_trial_temp +dist +sdist +MANIFEST +.coverage +tags +.testrepository +.noseids +apidocs +*.pyc +*.so +*~ +.tox/ +.pybuild diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..dc39b7a5 --- /dev/null +++ b/.hgignore @@ -0,0 +1,7 @@ +syntax: glob +build +_trial_temp +dist +MANIFEST +.coverage +tags diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 00000000..7d0aa966 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=PYTHONPATH=. python -m subunit.run $IDOPTION $LISTOPT subvertpy.tests.test_suite +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/.travis.yml b/.travis.yml index 33c6526d..a10ec6b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,41 @@ -sudo: required -language: generic - -services: - - docker - +language: python +dist: xenial +addons: + apt: + update: true + packages: libapr1-dev libaprutil1-dev libdb5.3-dev liblz4-dev libsasl2-dev libperl-dev libserf-dev libsqlite3-dev libtool python-all-dev libneon27-gnutls-dev libutf8proc-dev +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" +env: + - SVN_VERSION=1.13.0 + - SVN_VERSION=1.12.2 + - SVN_VERSION=1.11.1 + - SVN_VERSION=1.10.6 + SVN_OPTIONS="--with-lz4=internal" + - SVN_VERSION=1.9.7 + - SVN_VERSION=1.8.19 + - SVN_VERSION=1.7.19 + - SVN_VERSION=1.6.21 + - SVN_VERSION=1.5.9 script: - - wget -O- http://travis.debian.net/script.sh | sh - - -branches: - except: - - /^debian\/\d/ + - make check + - make style +install: + - travis_retry pip install -U pip coverage codecov flake8 +before_install: + - wget https://archive.apache.org/dist/subversion/subversion-${SVN_VERSION}.tar.gz + - tar xvfz subversion-${SVN_VERSION}.tar.gz + - cd subversion-${SVN_VERSION} + - ./configure ${SVN_OPTIONS} + - make + - sudo make install + - sudo ldconfig -v + - cd .. +after_success: + - python -m coverage combine + - codecov @@ -12,6 +12,9 @@ all: build build-inplace build:: $(SETUP) build +build-nodeprecated: + $(MAKE) build CFLAGS+=-Wno-deprecated-declarations + build-inplace:: $(SETUP) build_ext --inplace @@ -24,6 +27,12 @@ check:: build-inplace gdb-check:: $(MAKE) check DEBUGGER="gdb --args" +valgrind-check-python3: + PYTHONMALLOC=malloc $(MAKE) check PYTHON=python DEBUGGER="valgrind --suppressions=/usr/lib/valgrind/python3.supp" + +valgrind-check-python2: + PYTHONMALLOC=malloc $(MAKE) check PYTHON=python3 DEBUGGER="valgrind --suppressions=/usr/lib/valgrind/python.supp" + check-one:: $(MAKE) check TEST_OPTIONS=-f @@ -35,4 +44,4 @@ pydoctor: $(PYDOCTOR) $(PYDOCTOR_OPTIONS) --introspect-c-modules -c subvertpy.cfg --make-html style: - $(FLAKE8) --exclude=build,.git,build-pypy,.tox + $(FLAKE8) @@ -1,3 +1,16 @@ +0.11.0 UNRELEASED + + API CHANGES + + * ``subvertpy.wc.WorkingCopy`` has been renamed to + `` subvertpy.wc.Adm``. (Jelmer Vernooij) + + * ``subvertpy.client.mkdir``, ``subvertpy.client.copy``, + ``subvertpy.client.delete``, ``subvertpy.client.commit`` + no longer return the resulting commit but call an + optional callback with commit info. + (Jelmer Vernooij) + 0.10.1 2017-07-19 BUG FIXES diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 3fd6ebc0..00000000 --- a/PKG-INFO +++ /dev/null @@ -1,24 +0,0 @@ -Metadata-Version: 1.1 -Name: subvertpy -Version: 0.10.1 -Summary: Alternative Python bindings for Subversion -Home-page: https://jelmer.uk/subvertpy -Author: Jelmer Vernooij -Author-email: jelmer@jelmer.uk -License: LGPLv2.1 or later -Download-URL: https://jelmer.uk/subvertpy/subvertpy-0.10.1.tar.gz -Description: - Alternative Python bindings for Subversion. The goal is to have - complete, portable and "Pythonic" Python bindings. - -Keywords: svn subvertpy subversion bindings -Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta -Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Operating System :: POSIX -Classifier: Topic :: Software Development :: Version Control diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..c401fb86 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,96 @@ +environment: + + matrix: + + - PYTHON: "C:\\Python27" + PYTHON_VERSION: "2.7.x" + PYTHON_ARCH: "32" + + - PYTHON: "C:\\Python27-x64" + PYTHON_VERSION: "2.7.x" + PYTHON_ARCH: "64" + + - PYTHON: "C:\\Python33" + PYTHON_VERSION: "3.3.x" + PYTHON_ARCH: "32" + + - PYTHON: "C:\\Python33-x64" + PYTHON_VERSION: "3.3.x" + PYTHON_ARCH: "64" + DISTUTILS_USE_SDK: "1" + + - PYTHON: "C:\\Python34" + PYTHON_VERSION: "3.4.x" + PYTHON_ARCH: "32" + + - PYTHON: "C:\\Python34-x64" + PYTHON_VERSION: "3.4.x" + PYTHON_ARCH: "64" + DISTUTILS_USE_SDK: "1" + + - PYTHON: "C:\\Python35" + PYTHON_VERSION: "3.5.x" + PYTHON_ARCH: "32" + + - PYTHON: "C:\\Python35-x64" + PYTHON_VERSION: "3.5.x" + PYTHON_ARCH: "64" + + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "32" + + - PYTHON: "C:\\Python36-x64" + PYTHON_VERSION: "3.6.x" + PYTHON_ARCH: "64" + +install: + # If there is a newer build queued for the same PR, cancel this one. + # The AppVeyor 'rollout builds' option is supposed to serve the same + # purpose but it is problematic because it tends to cancel builds pushed + # directly to master instead of just PR builds (or the converse). + # credits: JuliaLang developers. + - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + throw "There are newer queued builds for this pull request, failing early." } + - ECHO "Filesystem root:" + - ps: "ls \"C:/\"" + + - ECHO "Installed SDKs:" + - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" + + # Install Python (from the official .msi of http://python.org) and pip when + # not already installed. + - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 } + + # Prepend newly installed Python to the PATH of this build (this cannot be + # done from inside the powershell script as it would require to restart + # the parent CMD process). + - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + + # Check that we have the expected version and architecture for Python + - "build.cmd %PYTHON%\\python.exe --version" + - "build.cmd %PYTHON%\\python.exe -c \"import struct; print(struct.calcsize('P') * 8)\"" + + # Install setuptools/wheel so that we can e.g. use bdist_wheel + - "pip install setuptools wheel" + + - "build.cmd %PYTHON%\\python.exe setup.py develop" + +build_script: + # Build the compiled extension + - "build.cmd %PYTHON%\\python.exe setup.py build" + +test_script: + - "build.cmd %PYTHON%\\python.exe setup.py test" + +after_test: + - "build.cmd %PYTHON%\\python.exe setup.py bdist_wheel" + # http://stackoverflow.com/questions/43255455/unicode-character-causing-error-with-bdist-wininst-on-python-3-but-not-python-2 + # - "python setup.py bdist_wininst" + - "build.cmd %PYTHON%\\python.exe setup.py bdist_msi" + - ps: "ls dist" + +artifacts: + - path: dist\* diff --git a/build.cmd b/build.cmd new file mode 100644 index 00000000..23df2b69 --- /dev/null +++ b/build.cmd @@ -0,0 +1,21 @@ +@echo off +:: To build extensions for 64 bit Python 3, we need to configure environment +:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: +:: MS Windows SDK for Windows 7 and .NET Framework 4 +:: +:: More details at: +:: https://github.com/cython/cython/wiki/CythonExtensionsOnWindows + +IF "%DISTUTILS_USE_SDK%"=="1" ( + ECHO Configuring environment to build with MSVC on a 64bit architecture + ECHO Using Windows SDK 7.1 + "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 + CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release + SET MSSdk=1 + REM Need the following to allow tox to see the SDK compiler + SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB +) ELSE ( + ECHO Using default MSVC build environment +) + +CALL %* diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..d62bc256 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +exclude = build,.git,build-pypy,.tox,subversion-* @@ -396,7 +396,8 @@ def subvertpy_modules(): SvnExtension( "subvertpy.client", [source_path(n) - for n in ("client.c", "editor.c", "util.c", "_ra.c", "wc.c")], + for n in ("client.c", "editor.c", "util.c", "_ra.c", "wc.c", + "wc_adm.c")], libraries=["svn_client-1", "svn_subr-1", "svn_ra-1", "svn_wc-1"]), SvnExtension( "subvertpy._ra", @@ -407,12 +408,18 @@ def subvertpy_modules(): libraries=["svn_repos-1", "svn_subr-1", "svn_fs-1"]), SvnExtension( "subvertpy.wc", - [source_path(n) for n in ("wc.c", "util.c", "editor.c")], - libraries=["svn_wc-1", "svn_subr-1"]) + [source_path(n) for n in + ["wc.c", "wc_adm.c", "util.c", "editor.c"]], + libraries=["svn_wc-1", "svn_subr-1"]), + SvnExtension( + "subvertpy.subr", + [source_path(n) + for n in ["util.c", "subr.c"]], + libraries=["svn_subr-1"]), ] -subvertpy_version = (0, 10, 1) +subvertpy_version = (0, 11, 0) subvertpy_version_string = ".".join(map(str, subvertpy_version)) @@ -422,15 +429,31 @@ if __name__ == "__main__": keywords='svn subvertpy subversion bindings', version=subvertpy_version_string, url='https://jelmer.uk/subvertpy', - download_url="https://jelmer.uk/subvertpy/subvertpy-%s.tar.gz" % ( + download_url="https://jelmer.uk/subvertpy/tarball/subvertpy-%s/" % ( subvertpy_version_string, ), license='LGPLv2.1 or later', author='Jelmer Vernooij', author_email='jelmer@jelmer.uk', long_description=""" - Alternative Python bindings for Subversion. The goal is to have - complete, portable and "Pythonic" Python bindings. - """, +Alternative Python bindings for Subversion. The goal is to have +complete, portable and "Pythonic" Python bindings. + +Bindings are provided for the working copy, client, delta, remote access and +repository APIs. A hookable server side implementation of the custom Subversion +protocol (svn_ra) is also provided. + +Differences with similar packages +--------------------------------- +subvertpy covers more of the APIs than python-svn. It provides a more +"Pythonic" API than python-subversion, which wraps the Subversion C API pretty +much directly. Neither provide a hookable server-side. + +Dependencies +------------ +Subvertpy depends on Python 2.7 or 3.5, and Subversion 1.4 or later. It should +work on Windows as well as most POSIX-based platforms (including Linux, BSDs +and Mac OS X). +""", packages=['subvertpy', 'subvertpy.tests'], ext_modules=subvertpy_modules(), scripts=['bin/subvertpy-fast-export'], diff --git a/subvertpy/__init__.py b/subvertpy/__init__.py index 6b0762de..89c1effd 100644 --- a/subvertpy/__init__.py +++ b/subvertpy/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2008 Jelmer Vernooij <jelmer@jelmer.uk> +# Copyright (C) 2006-2017 Jelmer Vernooij <jelmer@jelmer.uk> # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -17,7 +17,7 @@ """Python bindings for Subversion.""" __author__ = "Jelmer Vernooij <jelmer@jelmer.uk>" -__version__ = (0, 10, 1) +__version__ = (0, 11, 0) NODE_DIR = 2 NODE_FILE = 1 diff --git a/subvertpy/_ra.c b/subvertpy/_ra.c index 4c14131a..0757ac17 100644 --- a/subvertpy/_ra.c +++ b/subvertpy/_ra.c @@ -901,6 +901,16 @@ static PyObject *ra_get_repos_root(PyObject *self) */ static PyObject *ra_get_url(PyObject *self, void *closure) { + RemoteAccessObject *ra = (RemoteAccessObject *)self; + + return PyUnicode_FromString(ra->url); +} + +/** + * Obtain the URL of this repository. + */ +static PyObject *ra_get_session_url(PyObject *self) +{ const char *url; apr_pool_t *temp_pool; PyObject *r; @@ -927,6 +937,8 @@ static PyObject *ra_get_url(PyObject *self, void *closure) #endif } + + static PyObject *ra_do_update(PyObject *self, PyObject *args) { svn_revnum_t revision_to_update_to; @@ -1038,16 +1050,17 @@ static PyObject *ra_do_switch(PyObject *self, PyObject *args) bool recurse; bool send_copyfrom_args = false; bool ignore_ancestry = true; - char *switch_url; + const char *switch_url; PyObject *update_editor; const REPORTER_T *reporter; void *report_baton; apr_pool_t *temp_pool, *result_pool; ReporterObject *ret; + PyObject *py_switch_url; svn_error_t *err; - if (!PyArg_ParseTuple(args, "lsbsO|bb:do_switch", &revision_to_update_to, &update_target, - &recurse, &switch_url, &update_editor, &send_copyfrom_args, &ignore_ancestry)) + if (!PyArg_ParseTuple(args, "lsbOO|bb:do_switch", &revision_to_update_to, &update_target, + &recurse, &py_switch_url, &update_editor, &send_copyfrom_args, &ignore_ancestry)) return NULL; if (ra_check_busy(ra)) return NULL; @@ -1058,6 +1071,13 @@ static PyObject *ra_do_switch(PyObject *self, PyObject *args) return NULL; } + switch_url = py_object_to_svn_uri(py_switch_url, temp_pool); + if (switch_url == NULL) { + apr_pool_destroy(temp_pool); + ra->busy = false; + return NULL; + } + result_pool = Pool(NULL); if (result_pool == NULL) { apr_pool_destroy(temp_pool); @@ -1338,23 +1358,19 @@ static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwa hash_lock_tokens = NULL; } else { Py_ssize_t idx = 0; + char *key, *val; PyObject *k, *v; hash_lock_tokens = apr_hash_make(pool); while (PyDict_Next(lock_tokens, &idx, &k, &v)) { - if (!PyBytes_Check(k)) { - PyErr_SetString(PyExc_TypeError, "token not bytes"); + key = py_object_to_svn_string(k, pool); + if (key == NULL) { goto fail_prep; } - apr_hash_set(hash_lock_tokens, PyBytes_AsString(k), - PyBytes_Size(k), PyBytes_AsString(v)); + val = apr_pmemdup(pool, PyBytes_AsString(v), PyBytes_Size(v)); + apr_hash_set(hash_lock_tokens, key, strlen(key), val); } } - if (!PyDict_Check(revprops)) { - PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties"); - goto fail_prep; - } - if (ra_check_busy(ra)) goto fail_prep; @@ -1363,7 +1379,7 @@ static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwa #if ONLY_SINCE_SVN(1, 5) hash_revprops = prop_dict_to_hash(pool, revprops); if (hash_revprops == NULL) { - goto fail_prep2; + goto fail_prep; } Py_BEGIN_ALLOW_THREADS err = svn_ra_get_commit_editor3(ra->ra, &editor, @@ -1371,21 +1387,26 @@ static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwa hash_revprops, py_commit_callback, commit_callback, hash_lock_tokens, keep_locks, pool); #else + if (!PyDict_Check(revprops)) { + PyErr_SetString(PyExc_TypeError, "props should be dictionary"); + goto fail_prep; + } + /* Check that revprops has only one member named SVN_PROP_REVISION_LOG */ if (PyDict_Size(revprops) != 1) { PyErr_SetString(PyExc_ValueError, "Only svn:log can be set with Subversion 1.4"); - goto fail_prep2; + goto fail_prep; } py_log_msg = PyDict_GetItemString(revprops, SVN_PROP_REVISION_LOG); if (py_log_msg == NULL) { PyErr_SetString(PyExc_ValueError, "Only svn:log can be set with Subversion 1.4."); - goto fail_prep2; + goto fail_prep; } log_msg = py_object_to_svn_string(py_log_msg, pool); if (log_msg == NULL) { - goto fail_prep2; + goto fail_prep; } Py_BEGIN_ALLOW_THREADS @@ -1399,17 +1420,16 @@ static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwa if (err != NULL) { handle_svn_error(err); svn_error_clear(err); - goto fail_prep2; + goto fail_prep; } Py_INCREF(ra); return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, ra_done_handler, ra, commit_callback); -fail_prep2: +fail_prep: Py_DECREF(commit_callback); ra->busy = false; -fail_prep: apr_pool_destroy(pool); fail_pool: return NULL; @@ -1424,8 +1444,10 @@ static PyObject *ra_change_rev_prop(PyObject *self, PyObject *args) int vallen, oldvallen = -2; apr_pool_t *temp_pool; svn_string_t *val_string; +#if ONLY_SINCE_SVN(1, 7) const svn_string_t *old_val_string; const svn_string_t *const *old_val_string_p; +#endif if (!PyArg_ParseTuple(args, "lss#|z#:change_rev_prop", &rev, &name, &value, &vallen, &oldvalue, &oldvallen)) @@ -1475,18 +1497,14 @@ static PyObject *ra_get_dir(PyObject *self, PyObject *args, PyObject *kwargs) { apr_pool_t *temp_pool; apr_hash_t *dirents; - apr_hash_index_t *idx; apr_hash_t *props; svn_revnum_t fetch_rev; - const char *key; RemoteAccessObject *ra = (RemoteAccessObject *)self; - svn_dirent_t *dirent; - apr_ssize_t klen; const char *path; PyObject *py_path; svn_revnum_t revision = -1; unsigned int dirent_fields = 0; - PyObject *py_dirents, *py_props; + PyObject *py_dirents = NULL, *py_props; char *kwnames[] = { "path", "revision", "fields", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|lI:get_dir", kwnames, @@ -1517,45 +1535,21 @@ static PyObject *ra_get_dir(PyObject *self, PyObject *args, PyObject *kwargs) py_dirents = Py_None; Py_INCREF(py_dirents); } else { - py_dirents = PyDict_New(); + py_dirents = dirent_hash_to_dict(dirents, dirent_fields, temp_pool); if (py_dirents == NULL) { goto fail; } - idx = apr_hash_first(temp_pool, dirents); - while (idx != NULL) { - PyObject *item, *pykey; - apr_hash_this(idx, (const void **)&key, &klen, (void **)&dirent); - item = py_dirent(dirent, dirent_fields); - if (item == NULL) { - goto fail_dirents; - } - if (key == NULL) { - pykey = Py_None; - Py_INCREF(pykey); - } else { - pykey = PyUnicode_FromString((char *)key); - } - if (PyDict_SetItem(py_dirents, pykey, item) != 0) { - Py_DECREF(item); - Py_DECREF(pykey); - goto fail_dirents; - } - Py_DECREF(pykey); - Py_DECREF(item); - idx = apr_hash_next(idx); - } } py_props = prop_hash_to_dict(props); if (py_props == NULL) { - goto fail_dirents; + goto fail; } apr_pool_destroy(temp_pool); return Py_BuildValue("(NlN)", py_dirents, fetch_rev, py_props); -fail_dirents: - Py_DECREF(py_dirents); fail: + Py_XDECREF(py_dirents); apr_pool_destroy(temp_pool); return NULL; } @@ -1617,12 +1611,13 @@ static PyObject *ra_get_file(PyObject *self, PyObject *args) static PyObject *ra_get_lock(PyObject *self, PyObject *args) { - char *path; + const char *path; RemoteAccessObject *ra = (RemoteAccessObject *)self; - svn_lock_t *lock; + svn_lock_t *lock = NULL; + PyObject *py_path; apr_pool_t *temp_pool; - if (!PyArg_ParseTuple(args, "s:get_lock", &path)) + if (!PyArg_ParseTuple(args, "O:get_lock", &py_path)) return NULL; if (ra_check_busy(ra)) @@ -1631,10 +1626,22 @@ static PyObject *ra_get_lock(PyObject *self, PyObject *args) temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; + + path = py_object_to_svn_relpath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_lock(ra->ra, &lock, path, temp_pool)); apr_pool_destroy(temp_pool); - return wrap_lock(lock); + + if (lock == NULL) { + Py_RETURN_NONE; + } else { + return wrap_lock(lock); + } } static PyObject *ra_check_path(PyObject *self, PyObject *args) @@ -2239,12 +2246,15 @@ static int ra_set_progress_func(PyObject *self, PyObject *value, void *closure) static PyGetSetDef ra_getsetters[] = { { "progress_func", NULL, ra_set_progress_func, NULL }, + { "url", ra_get_url, NULL, NULL }, { NULL } }; #include "_ra_iter_log.c" static PyMethodDef ra_methods[] = { + { "get_session_url", (PyCFunction)ra_get_session_url, METH_NOARGS, + "S.get_session_url() -> url" }, { "get_file_revs", ra_get_file_revs, METH_VARARGS, "S.get_file_revs(path, start_rev, end_revs, handler)" }, { "get_locations", ra_get_locations, METH_VARARGS, @@ -2308,9 +2318,6 @@ static PyMethodDef ra_methods[] = { { "get_repos_root", (PyCFunction)ra_get_repos_root, METH_NOARGS, "S.get_repos_root() -> url\n" "Return the URL to the root of the repository." }, - { "get_url", (PyCFunction)ra_get_url, METH_NOARGS, - "S.get_url() -> url\n" - "Return the URL of the repository." }, { "get_log", (PyCFunction)ra_get_log, METH_VARARGS|METH_KEYWORDS, "S.get_log(callback, paths, start, end, limit=0, " "discover_changed_paths=False, strict_node_history=True, " @@ -2350,8 +2357,6 @@ static PyMethodDef ra_methods[] = { static PyMemberDef ra_members[] = { { "busy", T_BYTE, offsetof(RemoteAccessObject, busy), READONLY, "Whether this connection is in use at the moment" }, - { "url", T_STRING, offsetof(RemoteAccessObject, url), READONLY, - "URL this connection is to" }, { "corrected_url", T_STRING, offsetof(RemoteAccessObject, corrected_url), READONLY, "Corrected URL" }, { NULL, } @@ -3321,7 +3326,9 @@ static PyObject *get_platform_specific_client_providers(PyObject *self) return pylist; #else PyObject *pylist = PyList_New(0); +#if defined(WIN32) || defined(__CYGWIN__) || defined(SVN_KEYCHAIN_PROVIDER_AVAILABLE) PyObject *provider = NULL; +#endif if (pylist == NULL) { Py_DECREF(pylist); diff --git a/subvertpy/client.c b/subvertpy/client.c index 04b9ae57..30f804e6 100644 --- a/subvertpy/client.c +++ b/subvertpy/client.c @@ -70,15 +70,35 @@ typedef struct { apr_pool_t *pool; } InfoObject; +#define INVOKE_COMMIT_CALLBACK(pool, commit_info, callback) \ + { \ + PyObject *ret; \ + PyObject *py_commit_info = py_commit_info_tuple(commit_info); \ + if (py_commit_info == NULL) { \ + apr_pool_destroy(pool); \ + return NULL; \ + } \ + if (callback != Py_None) { \ + ret = PyObject_CallFunction(callback, "O", py_commit_info); \ + } else { \ + ret = Py_None; \ + Py_INCREF(ret); \ + } \ + Py_DECREF(py_commit_info); \ + if (ret == NULL) { \ + apr_pool_destroy(pool); \ + return NULL; \ + } \ + } \ + + static int client_set_auth(PyObject *self, PyObject *auth, void *closure); static int client_set_config(PyObject *self, PyObject *auth, void *closure); -static bool client_check_path(const char *path, apr_pool_t *scratch_pool) -{ - return svn_path_is_canonical(path, scratch_pool); -} - -static bool client_path_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **ret) +static bool client_list_to_apr_array( + apr_pool_t *pool, PyObject *l, + const char *(*convert)(PyObject*, apr_pool_t *), + apr_array_header_t **ret) { int i; const char *path; @@ -88,22 +108,17 @@ static bool client_path_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_arr } if (PyUnicode_Check(l) || PyBytes_Check(l)) { *ret = apr_array_make(pool, 1, sizeof(char *)); - path = py_object_to_svn_string(l, pool); + path = convert(l, pool); if (path == NULL) { return false; } - if (!client_check_path(path, pool)) { - PyErr_SetString(PyExc_ValueError, "Expected canonical path or URL"); - return false; - } APR_ARRAY_PUSH(*ret, const char *) = path; } else if (PyList_Check(l)) { *ret = apr_array_make(pool, PyList_Size(l), sizeof(char *)); for (i = 0; i < PyList_GET_SIZE(l); i++) { PyObject *item = PyList_GET_ITEM(l, i); - path = py_object_to_svn_string(item, pool); - if (!client_check_path(path, pool)) { - PyErr_SetString(PyExc_ValueError, "Expected canonical path or URL"); + path = convert(item, pool); + if (path == NULL) { return false; } APR_ARRAY_PUSH(*ret, const char *) = path; @@ -142,6 +157,9 @@ static bool to_opt_revision(PyObject *arg, svn_opt_revision_t *ret) char *text; if (PyUnicode_Check(arg)) { arg = PyUnicode_AsUTF8String(arg); + if (arg == NULL) { + return false; + } } else { Py_INCREF(arg); } @@ -437,7 +455,7 @@ static svn_error_t *py_log_msg_func2(const char **log_msg, const char **tmp_file return NULL; } -static PyObject *py_commit_info_tuple(svn_commit_info_t *ci) +static PyObject *py_commit_info_tuple(const svn_commit_info_t *ci) { if (ci == NULL) Py_RETURN_NONE; @@ -741,27 +759,79 @@ static PyObject *client_checkout(PyObject *self, PyObject *args, PyObject *kwarg return PyLong_FromLong(result_rev); } +#if ONLY_SINCE_SVN(1, 7) +static svn_error_t *py_commit_callback2(const svn_commit_info_t *commit_info, + void *callback, apr_pool_t *pool) { + PyObject *py_callback = callback; + PyObject *py_commit_info; + PyObject *ret; + + PyGILState_STATE state = PyGILState_Ensure(); + + py_commit_info = py_commit_info_tuple(commit_info); + + if (py_commit_info == NULL) { + PyGILState_Release(state); + return py_svn_error(); + } + + if (py_callback != Py_None) { + ret = PyObject_CallFunction(py_callback, "O", py_commit_info); + } else { + ret = Py_None; + Py_INCREF(ret); + } + Py_DECREF(py_commit_info); + + PyGILState_Release(state); + + if (ret == NULL) { + return py_svn_error(); + } + + return NULL; +} +#endif + static PyObject *client_commit(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *targets; ClientObject *client = (ClientObject *)self; bool recurse=true, keep_locks=true; apr_pool_t *temp_pool; +#if ONLY_BEFORE_SVN(1, 8) svn_commit_info_t *commit_info = NULL; - PyObject *ret; +#endif apr_array_header_t *apr_targets; + bool include_file_externals = false; + bool include_dir_externals = false; + bool keep_changelist = false; + bool commit_as_operations = false; + const apr_array_header_t *changelists = NULL; PyObject *revprops = Py_None; - char *kwnames[] = { "targets", "recurse", "keep_locks", "revprops", NULL }; + PyObject *callback = Py_None; + char *kwnames[] = { + "targets", "recurse", "keep_locks", "revprops", + "keep_changelist", "commit_as_operations", "include_file_externals", + "include_dir_externals", "callback", NULL }; #if ONLY_SINCE_SVN(1, 5) apr_hash_t *hash_revprops; #endif - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|bbO", kwnames, &targets, &recurse, &keep_locks, &revprops)) + /* TODO(jelmer): Support changelists */ + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|bbObbbbO", kwnames, + &targets, &recurse, &keep_locks, + &revprops, &keep_changelist, + &commit_as_operations, + &include_file_externals, + &include_dir_externals, + &callback)) { return NULL; + } temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; } - if (!client_path_list_to_apr_array(temp_pool, targets, &apr_targets)) { + if (!client_list_to_apr_array(temp_pool, targets, py_object_to_svn_path_or_url, &apr_targets)) { apr_pool_destroy(temp_pool); return NULL; } @@ -772,9 +842,16 @@ static PyObject *client_commit(PyObject *self, PyObject *args, PyObject *kwargs) return NULL; } - -#if ONLY_SINCE_SVN(1, 5) if (revprops != Py_None) { +#if ONLY_BEFORE_SVN(1, 5) + if (PyDict_Size(revprops) > 0) { + PyErr_SetString(PyExc_NotImplementedError, + "Setting revision properties only supported on svn >= 1.5"); + apr_pool_destroy(temp_pool); + return NULL; + } +#endif + hash_revprops = prop_dict_to_hash(temp_pool, revprops); if (hash_revprops == NULL) { apr_pool_destroy(temp_pool); @@ -784,26 +861,36 @@ static PyObject *client_commit(PyObject *self, PyObject *args, PyObject *kwargs) hash_revprops = NULL; } - /* FIXME: Support keep_changelist and changelists */ - RUN_SVN_WITH_POOL(temp_pool, svn_client_commit4(&commit_info, - apr_targets, recurse?svn_depth_infinity:svn_depth_files, - keep_locks, false, NULL, hash_revprops, - client->client, temp_pool)); +#if ONLY_SINCE_SVN(1, 8) + RUN_SVN_WITH_POOL(temp_pool, svn_client_commit6( + apr_targets, recurse?svn_depth_infinity:svn_depth_files, + keep_locks, keep_changelist, commit_as_operations, + include_file_externals, include_dir_externals, changelists, + hash_revprops, py_commit_callback2, callback, client->client, temp_pool)); +#elif ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_client_commit4( + &commit_info, apr_targets, recurse?svn_depth_infinity:svn_depth_files, + keep_locks, keep_changelist, changelists, hash_revprops, + client->client, temp_pool)); #else - if (revprops != Py_None && PyDict_Size(revprops) > 0) { - PyErr_SetString(PyExc_NotImplementedError, - "Setting revision properties only supported on svn >= 1.5"); + if (commit_as_operations) { + PyErr_SetString(PyExc_NotImplementedError, "commit_as_operations only support on svn >= 1.8"); apr_pool_destroy(temp_pool); return NULL; } + RUN_SVN_WITH_POOL(temp_pool, svn_client_commit3(&commit_info, apr_targets, recurse, keep_locks, client->client, temp_pool)); #endif - ret = py_commit_info_tuple(commit_info); + +#if ONLY_BEFORE_SVN(1, 8) + INVOKE_COMMIT_CALLBACK(temp_pool, commit_info, callback); +#endif + apr_pool_destroy(temp_pool); - return ret; + Py_RETURN_NONE; } static PyObject *client_export(PyObject *self, PyObject *args, PyObject *kwargs) @@ -812,10 +899,10 @@ static PyObject *client_export(PyObject *self, PyObject *args, PyObject *kwargs) char *kwnames[] = { "from", "to", "rev", "peg_rev", "recurse", "ignore_externals", "overwrite", "native_eol", "ignore_keywords", NULL }; svn_revnum_t result_rev; svn_opt_revision_t c_peg_rev, c_rev; - PyObject *py_from, *py_to; + PyObject *py_from, *py_to; const char *from, *to; apr_pool_t *temp_pool; - char *native_eol = NULL; + char *native_eol = NULL; PyObject *peg_rev=Py_None, *rev=Py_None; bool recurse=true, ignore_externals=false, overwrite=false, ignore_keywords=false; @@ -831,27 +918,27 @@ static PyObject *client_export(PyObject *self, PyObject *args, PyObject *kwargs) if (temp_pool == NULL) return NULL; - from = py_object_to_svn_string(py_from, temp_pool); - if (from == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } + from = py_object_to_svn_string(py_from, temp_pool); + if (from == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } - to = py_object_to_svn_dirent(py_to, temp_pool); - if (to == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } + to = py_object_to_svn_dirent(py_to, temp_pool); + if (to == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } #if ONLY_SINCE_SVN(1, 7) - RUN_SVN_WITH_POOL(temp_pool, svn_client_export5(&result_rev, from, to, + RUN_SVN_WITH_POOL(temp_pool, svn_client_export5(&result_rev, from, to, &c_peg_rev, &c_rev, overwrite, ignore_externals, ignore_keywords, recurse?svn_depth_infinity:svn_depth_files, native_eol, client->client, temp_pool)); #elif ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_export4(&result_rev, from, to, &c_peg_rev, &c_rev, overwrite, ignore_externals, - recurse?svn_depth_infinity:svn_depth_files, + recurse?svn_depth_infinity:svn_depth_files, native_eol, client->client, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_export3(&result_rev, from, to, @@ -864,148 +951,160 @@ static PyObject *client_export(PyObject *self, PyObject *args, PyObject *kwargs) static PyObject *client_cat(PyObject *self, PyObject *args, PyObject *kwargs) { - ClientObject *client = (ClientObject *)self; - char *kwnames[] = { "path", "output_stream", "revision", "peg_revision", NULL }; - char *path; - PyObject *peg_rev=Py_None, *rev=Py_None; - svn_opt_revision_t c_peg_rev, c_rev; - apr_pool_t *temp_pool; - svn_stream_t *stream; - bool expand_keywords = true; - PyObject *py_stream, *py_path, *ret; - apr_hash_t *props = NULL; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OOb", kwnames, &py_path, &py_stream, &rev, &peg_rev, &expand_keywords)) - return NULL; - - if (!to_opt_revision(rev, &c_rev)) - return NULL; - if (!to_opt_revision(peg_rev, &c_peg_rev)) - return NULL; - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_string(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - stream = new_py_stream(temp_pool, py_stream); - if (stream == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } + ClientObject *client = (ClientObject *)self; + char *kwnames[] = { "path", "output_stream", "revision", "peg_revision", NULL }; + char *path; + PyObject *peg_rev=Py_None, *rev=Py_None; + svn_opt_revision_t c_peg_rev, c_rev; + apr_pool_t *temp_pool; + svn_stream_t *stream; + bool expand_keywords = true; + PyObject *py_stream, *py_path, *ret; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OOb", kwnames, &py_path, &py_stream, &rev, &peg_rev, &expand_keywords)) + return NULL; + + if (!to_opt_revision(rev, &c_rev)) + return NULL; + if (!to_opt_revision(peg_rev, &c_peg_rev)) + return NULL; + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_string(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + stream = new_py_stream(temp_pool, py_stream); + if (stream == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } #if ONLY_SINCE_SVN(1, 9) - RUN_SVN_WITH_POOL(temp_pool, svn_client_cat3( - &props, stream, path, &c_peg_rev, &c_rev, expand_keywords, - client->client, temp_pool, temp_pool)); - - ret = prop_hash_to_dict(props); - if (ret == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } + { + apr_hash_t *props = NULL; + RUN_SVN_WITH_POOL(temp_pool, svn_client_cat3( + &props, stream, path, &c_peg_rev, &c_rev, expand_keywords, + client->client, temp_pool, temp_pool)); + + ret = prop_hash_to_dict(props); + if (ret == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + } #else - if (!expand_keywords) { - PyErr_SetString(PyExc_NotImplementedError, - "expand_keywords=false only supported with svn >= 1.9"); - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_client_cat2(stream, path, - &c_peg_rev, &c_rev, client->client, temp_pool)); - - ret = Py_None; - Py_INCREF(ret); + if (!expand_keywords) { + PyErr_SetString(PyExc_NotImplementedError, + "expand_keywords=false only supported with svn >= 1.9"); + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, svn_client_cat2(stream, path, + &c_peg_rev, &c_rev, client->client, temp_pool)); + ret = Py_None; + Py_INCREF(ret); #endif - apr_pool_destroy(temp_pool); - return ret; + apr_pool_destroy(temp_pool); + return ret; } static PyObject *client_delete(PyObject *self, PyObject *args) { - PyObject *paths; - bool force=false, keep_local=false; - apr_pool_t *temp_pool; - svn_commit_info_t *commit_info = NULL; - PyObject *ret, *py_revprops; - apr_array_header_t *apr_paths; - ClientObject *client = (ClientObject *)self; - apr_hash_t *hash_revprops; - - if (!PyArg_ParseTuple(args, "O|bbO", &paths, &force, &keep_local, &py_revprops)) - return NULL; - - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - if (!client_path_list_to_apr_array(temp_pool, paths, &apr_paths)) { - apr_pool_destroy(temp_pool); - return NULL; - } - - if (py_revprops != Py_None) { - hash_revprops = prop_dict_to_hash(temp_pool, py_revprops); - if (hash_revprops == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - } else { - hash_revprops = NULL; - } + PyObject *paths; + bool force=false, keep_local=false; + apr_pool_t *temp_pool; +#if ONLY_BEFORE_SVN(1, 7) + svn_commit_info_t *commit_info = NULL; +#endif + PyObject *py_revprops = Py_None; + apr_array_header_t *apr_paths; + ClientObject *client = (ClientObject *)self; + apr_hash_t *hash_revprops; + PyObject *callback = Py_None; -#if ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(temp_pool, svn_client_delete3( - &commit_info, apr_paths, force, keep_local, hash_revprops, client->client, temp_pool)); + if (!PyArg_ParseTuple(args, "O|bbOO", &paths, &force, &keep_local, &py_revprops, &callback)) + return NULL; + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + if (!client_list_to_apr_array(temp_pool, paths, py_object_to_svn_path_or_url, &apr_paths)) { + apr_pool_destroy(temp_pool); + return NULL; + } + + if (py_revprops != Py_None) { + hash_revprops = prop_dict_to_hash(temp_pool, py_revprops); + if (hash_revprops == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + } else { + hash_revprops = NULL; + } + +#if ONLY_SINCE_SVN(1, 7) + RUN_SVN_WITH_POOL(temp_pool, svn_client_delete4( + apr_paths, force, keep_local, hash_revprops, py_commit_callback2, callback, client->client, temp_pool)); +#elif ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_client_delete3( + &commit_info, apr_paths, force, keep_local, hash_revprops, client->client, temp_pool)); #else - if (hash_revprops != NULL) { - PyErr_SetString(PyExc_NotImplementedError, + if (hash_revprops != NULL) { + PyErr_SetString(PyExc_NotImplementedError, "revprops not supported against svn 1.4"); - apr_pool_destroy(temp_pool); - return NULL; - } + apr_pool_destroy(temp_pool); + return NULL; + } - if (keep_local) { - PyErr_SetString(PyExc_NotImplementedError, + if (keep_local) { + PyErr_SetString(PyExc_NotImplementedError, "keep_local not supported against svn 1.4"); - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_client_delete2( - &commit_info, apr_paths, force, client->client, temp_pool)); + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, svn_client_delete2( + &commit_info, apr_paths, force, client->client, temp_pool)); #endif - ret = py_commit_info_tuple(commit_info); +#if ONLY_BEFORE_SVN(1, 7) + INVOKE_COMMIT_CALLBACK(temp_pool, commit_info, callback); +#endif - apr_pool_destroy(temp_pool); + apr_pool_destroy(temp_pool); - return ret; + Py_RETURN_NONE; } -static PyObject *client_mkdir(PyObject *self, PyObject *args) +static PyObject *client_mkdir(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *paths, *revprops = NULL; bool make_parents = false; apr_pool_t *temp_pool; +#if ONLY_BEFORE_SVN(1, 7) svn_commit_info_t *commit_info = NULL; - PyObject *ret; +#endif apr_array_header_t *apr_paths; apr_hash_t *hash_revprops; ClientObject *client = (ClientObject *)self; + PyObject *callback = Py_None; + char *kwnames[] = { "path", "make_parents", "revprops", "callback", NULL }; - if (!PyArg_ParseTuple(args, "O|bO", &paths, &make_parents, &revprops)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|bOO", kwnames, &paths, &make_parents, &revprops, &callback)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; - if (!client_path_list_to_apr_array(temp_pool, paths, &apr_paths)) { + if (!client_list_to_apr_array(temp_pool, paths, py_object_to_svn_path_or_url, &apr_paths)) { apr_pool_destroy(temp_pool); return NULL; } @@ -1027,9 +1126,15 @@ static PyObject *client_mkdir(PyObject *self, PyObject *args) hash_revprops = NULL; } +#if ONLY_SINCE_SVN(1, 7) + RUN_SVN_WITH_POOL(temp_pool, svn_client_mkdir4(apr_paths, + make_parents?TRUE:FALSE, hash_revprops, py_commit_callback2, callback, + client->client, temp_pool)); +#else RUN_SVN_WITH_POOL(temp_pool, svn_client_mkdir3(&commit_info, apr_paths, make_parents?TRUE:FALSE, hash_revprops, client->client, temp_pool)); +#endif #else if (make_parents) { PyErr_SetString(PyExc_ValueError, @@ -1049,29 +1154,37 @@ static PyObject *client_mkdir(PyObject *self, PyObject *args) client->client, temp_pool)); #endif - ret = py_commit_info_tuple(commit_info); +#if ONLY_BEFORE_SVN(1, 7) + INVOKE_COMMIT_CALLBACK(temp_pool, commit_info, callback); +#endif apr_pool_destroy(temp_pool); - return ret; + Py_RETURN_NONE; } - - static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) { char *src_path, *dst_path; PyObject *src_rev = Py_None; +#if ONLY_BEFORE_SVN(1, 7) svn_commit_info_t *commit_info = NULL; +#endif apr_pool_t *temp_pool; svn_opt_revision_t c_src_rev; bool copy_as_child = true, make_parents = false; - PyObject *ret; apr_hash_t *revprops; bool ignore_externals = false; + bool metadata_only = false; ClientObject *client = (ClientObject *)self; + PyObject *callback = Py_None; + bool pin_externals = false; +#if ONLY_SINCE_SVN(1, 9) + apr_hash_t *pinned_externals = NULL; +#endif char *kwnames[] = { "src_path", "dst_path", "src_rev", "copy_as_child", - "make_parents", "ignore_externals", "revprpos", NULL }; + "make_parents", "ignore_externals", "revprops", "metadata_only", + "pin_externals", "callback", NULL }; #if ONLY_SINCE_SVN(1, 4) PyObject *py_revprops = Py_None; @@ -1081,9 +1194,10 @@ static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) svn_client_copy_source_t src; #endif - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|ObbbO", kwnames, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|ObbbObbO", kwnames, &src_path, &dst_path, &src_rev, ©_as_child, &make_parents, - &ignore_externals, &py_revprops)) + &ignore_externals, &py_revprops, &metadata_only, &pin_externals, + &callback)) return NULL; if (!to_opt_revision(src_rev, &c_src_rev)) return NULL; @@ -1129,6 +1243,21 @@ static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) return NULL; } #endif +#if ONLY_BEFORE_SVN(1, 9) + if (metadata_only) { + PyErr_SetString(PyExc_NotImplementedError, + "metadata_only not supported in svn < 1.9"); + apr_pool_destroy(temp_pool); + return NULL; + } + if (pin_externals) { + PyErr_SetString(PyExc_NotImplementedError, + "pin_externals not supported in svn < 1.9"); + apr_pool_destroy(temp_pool); + return NULL; + } + // TODO(jelmer): Set pinned_externals +#endif #if ONLY_SINCE_SVN(1, 5) src.path = src_path; src.revision = src.peg_revision = &c_src_rev; @@ -1138,9 +1267,20 @@ static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) apr_pool_destroy(temp_pool); return NULL; } - APR_ARRAY_IDX(src_paths, 0, svn_client_copy_source_t *) = &src; + APR_ARRAY_PUSH(src_paths, svn_client_copy_source_t *) = &src; #endif -#if ONLY_SINCE_SVN(1, 6) +#if ONLY_SINCE_SVN(1, 9) + RUN_SVN_WITH_POOL(temp_pool, svn_client_copy7(src_paths, + dst_path, copy_as_child, make_parents, + ignore_externals, metadata_only, pin_externals, + pinned_externals, revprops, py_commit_callback2, + callback, client->client, temp_pool)); +#elif ONLY_SINCE_SVN(1, 7) + RUN_SVN_WITH_POOL(temp_pool, svn_client_copy6(src_paths, + dst_path, copy_as_child, make_parents, + ignore_externals, revprops, py_commit_callback2, callback, + client->client, temp_pool)); +#elif ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_client_copy5(&commit_info, src_paths, dst_path, copy_as_child, make_parents, ignore_externals, revprops, client->client, temp_pool)); @@ -1152,9 +1292,12 @@ static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) RUN_SVN_WITH_POOL(temp_pool, svn_client_copy2(&commit_info, src_path, &c_src_rev, dst_path, client->client, temp_pool)); #endif - ret = py_commit_info_tuple(commit_info); +#if ONLY_BEFORE_SVN(1, 7) + INVOKE_COMMIT_CALLBACK(temp_pool, commit_info, callback); +#endif apr_pool_destroy(temp_pool); - return ret; + + Py_RETURN_NONE; } static PyObject *client_propset(PyObject *self, PyObject *args) @@ -1227,13 +1370,13 @@ static PyObject *client_propget(PyObject *self, PyObject *args) bool recurse = false; char *propname; apr_pool_t *temp_pool; - char *target; + const char *target; PyObject *peg_revision = Py_None; PyObject *revision; ClientObject *client = (ClientObject *)self; - PyObject *ret; + PyObject *ret, *py_target; - if (!PyArg_ParseTuple(args, "ssO|Ob", &propname, &target, &peg_revision, + if (!PyArg_ParseTuple(args, "sOO|Ob", &propname, &py_target, &peg_revision, &revision, &recurse)) return NULL; if (!to_opt_revision(peg_revision, &c_peg_rev)) @@ -1243,14 +1386,19 @@ static PyObject *client_propget(PyObject *self, PyObject *args) temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; + target = py_object_to_svn_abspath(py_target, temp_pool); + if (target == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } #if ONLY_SINCE_SVN(1, 8) /* FIXME: Support changelists */ /* FIXME: Support actual_revnum */ /* FIXME: Support depth properly */ - /* FIXME: Support inherited_props */ + /* FIXME: Support inherited_props */ RUN_SVN_WITH_POOL(temp_pool, svn_client_propget5(&hash_props, NULL, - propname, target, + propname, target, &c_peg_rev, &c_rev, NULL, recurse?svn_depth_infinity:svn_depth_files, NULL, client->client, temp_pool, temp_pool)); #elif ONLY_SINCE_SVN(1, 5) @@ -1332,39 +1480,39 @@ static PyObject *client_proplist(PyObject *self, PyObject *args, } - RUN_SVN_WITH_POOL(temp_pool, - svn_client_proplist2(&props, target, &c_peg_rev, &c_rev, - (depth == svn_depth_infinity), - client->client, temp_pool)); + RUN_SVN_WITH_POOL(temp_pool, + svn_client_proplist2(&props, target, &c_peg_rev, &c_rev, + (depth == svn_depth_infinity), + client->client, temp_pool)); - for (i = 0; i < props->nelts; i++) { - svn_client_proplist_item_t *item; - PyObject *prop_dict, *value; + for (i = 0; i < props->nelts; i++) { + svn_client_proplist_item_t *item; + PyObject *prop_dict, *value; - item = APR_ARRAY_IDX(props, i, svn_client_proplist_item_t *); + item = APR_ARRAY_IDX(props, i, svn_client_proplist_item_t *); - prop_dict = prop_hash_to_dict(item->prop_hash); - if (prop_dict == NULL) { - apr_pool_destroy(temp_pool); - Py_DECREF(prop_list); - return NULL; - } + prop_dict = prop_hash_to_dict(item->prop_hash); + if (prop_dict == NULL) { + apr_pool_destroy(temp_pool); + Py_DECREF(prop_list); + return NULL; + } - value = Py_BuildValue("(sO)", item->node_name, prop_dict); - if (value == NULL) { - apr_pool_destroy(temp_pool); - Py_DECREF(prop_list); - Py_DECREF(prop_dict); - return NULL; - } - if (PyList_Append(prop_list, value) != 0) { - apr_pool_destroy(temp_pool); - Py_DECREF(prop_list); - Py_DECREF(prop_dict); - Py_DECREF(value); - return NULL; - } - Py_DECREF(value); + value = Py_BuildValue("(sO)", item->node_name, prop_dict); + if (value == NULL) { + apr_pool_destroy(temp_pool); + Py_DECREF(prop_list); + Py_DECREF(prop_dict); + return NULL; + } + if (PyList_Append(prop_list, value) != 0) { + apr_pool_destroy(temp_pool); + Py_DECREF(prop_list); + Py_DECREF(prop_dict); + Py_DECREF(value); + return NULL; + } + Py_DECREF(value); } apr_pool_destroy(temp_pool); @@ -1415,50 +1563,77 @@ static PyObject *client_update(PyObject *self, PyObject *args, PyObject *kwargs) PyObject *ret; int i = 0; ClientObject *client = (ClientObject *)self; - bool allow_unver_obstructions = false, - depth_is_sticky = false; - char *kwnames[] = - { "path", "revision", "recurse", "ignore_externals", "depth_is_sticky", - "allow_unver_obstructions", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Obbbb", kwnames, - &paths, &rev, &recurse, &ignore_externals, - &depth_is_sticky, &allow_unver_obstructions)) - return NULL; - - if (!to_opt_revision(rev, &c_rev)) - return NULL; - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - if (!client_path_list_to_apr_array(temp_pool, paths, &apr_paths)) { - apr_pool_destroy(temp_pool); - return NULL; - } -#if ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(temp_pool, svn_client_update3(&result_revs, - apr_paths, &c_rev, recurse?svn_depth_infinity:svn_depth_files, - depth_is_sticky?TRUE:FALSE, ignore_externals, allow_unver_obstructions?TRUE:FALSE, - client->client, temp_pool)); + bool allow_unver_obstructions = false; + bool depth_is_sticky = false; + bool adds_as_modification = true; + bool make_parents = false; + char *kwnames[] = + { "path", "revision", "recurse", "ignore_externals", "depth_is_sticky", + "allow_unver_obstructions", "adds_as_modification", "make_parents", + NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Obbbbb", kwnames, + &paths, &rev, &recurse, &ignore_externals, + &depth_is_sticky, &allow_unver_obstructions, &adds_as_modification, + &make_parents)) + return NULL; + + if (!to_opt_revision(rev, &c_rev)) + return NULL; + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + if (!client_list_to_apr_array(temp_pool, paths, py_object_to_svn_path_or_url, &apr_paths)) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_BEFORE_SVN(1, 7) + if (!adds_as_modification) { + PyErr_SetString(PyExc_NotImplementedError, + "!adds_as_modification not supported before svn 1.7"); + apr_pool_destroy(temp_pool); + return NULL; + } + + if (make_parents) { + PyErr_SetString(PyExc_NotImplementedError, + "make_parents not supported before svn 1.7"); + apr_pool_destroy(temp_pool); + return NULL; + } +#endif + +#if ONLY_SINCE_SVN(1, 7) + RUN_SVN_WITH_POOL(temp_pool, svn_client_update4(&result_revs, + apr_paths, &c_rev, recurse?svn_depth_infinity:svn_depth_files, + depth_is_sticky?TRUE:FALSE, ignore_externals, allow_unver_obstructions?TRUE:FALSE, + adds_as_modification?TRUE:FALSE, make_parents?TRUE:FALSE, + client->client, temp_pool)); +#elif ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_client_update3(&result_revs, + apr_paths, &c_rev, recurse?svn_depth_infinity:svn_depth_files, + depth_is_sticky?TRUE:FALSE, ignore_externals, allow_unver_obstructions?TRUE:FALSE, + client->client, temp_pool)); #else - RUN_SVN_WITH_POOL(temp_pool, svn_client_update2(&result_revs, - apr_paths, &c_rev, - recurse, ignore_externals, client->client, temp_pool)); + RUN_SVN_WITH_POOL(temp_pool, svn_client_update2(&result_revs, + apr_paths, &c_rev, + recurse, ignore_externals, client->client, temp_pool)); #endif - ret = PyList_New(result_revs->nelts); - if (ret == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - for (i = 0; i < result_revs->nelts; i++) { - ret_rev = APR_ARRAY_IDX(result_revs, i, svn_revnum_t); - if (PyList_SetItem(ret, i, PyLong_FromLong(ret_rev)) != 0) { - Py_DECREF(ret); - return NULL; - } - } - apr_pool_destroy(temp_pool); - return ret; + ret = PyList_New(result_revs->nelts); + if (ret == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + for (i = 0; i < result_revs->nelts; i++) { + ret_rev = APR_ARRAY_IDX(result_revs, i, svn_revnum_t); + if (PyList_SetItem(ret, i, PyLong_FromLong(ret_rev)) != 0) { + Py_DECREF(ret); + return NULL; + } + } + apr_pool_destroy(temp_pool); + return ret; } static PyObject *client_list(PyObject *self, PyObject *args, PyObject *kwargs) @@ -1700,17 +1875,17 @@ static PyObject *client_log(PyObject *self, PyObject *args, PyObject *kwargs) } #endif - if (!client_path_list_to_apr_array(temp_pool, paths, &apr_paths)) { - apr_pool_destroy(temp_pool); - return NULL; - } + if (!client_list_to_apr_array(temp_pool, paths, py_object_to_svn_path_or_url, &apr_paths)) { + apr_pool_destroy(temp_pool); + return NULL; + } - if (revprops) { - if (!string_list_to_apr_array(temp_pool, revprops, &apr_revprops)) { - apr_pool_destroy(temp_pool); - return NULL; - } - } + if (revprops) { + if (!string_list_to_apr_array(temp_pool, revprops, &apr_revprops)) { + apr_pool_destroy(temp_pool); + return NULL; + } + } #if ONLY_SINCE_SVN(1, 6) revision_range.start = c_start_rev; @@ -1803,11 +1978,11 @@ static PyObject *client_info(PyObject *self, PyObject *args, PyObject *kwargs) Py_BEGIN_ALLOW_THREADS; #if ONLY_SINCE_SVN(1, 7) /* FIXME: Support changelists */ - err = svn_client_info3(path, &c_peg_rev, &c_rev, depth, fetch_excluded?TRUE:FALSE, - fetch_actual_only?TRUE:FALSE, NULL, - info_receiver, - entry_dict, - client->client, temp_pool); + err = svn_client_info3(path, &c_peg_rev, &c_rev, depth, fetch_excluded?TRUE:FALSE, + fetch_actual_only?TRUE:FALSE, NULL, + info_receiver, + entry_dict, + client->client, temp_pool); #elif ONLY_SINCE_SVN(1, 5) /* FIXME: Support changelists */ err = svn_client_info2(path, &c_peg_rev, &c_rev, info_receiver, entry_dict, @@ -1834,16 +2009,81 @@ static PyObject *client_info(PyObject *self, PyObject *args, PyObject *kwargs) return entry_dict; } +static PyObject *client_lock(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *kwnames[] = { + "targets", "comment", "steal_lock", + NULL, + }; + apr_array_header_t *targets; + apr_pool_t *temp_pool; + char *comment; + bool steal_lock = false; + ClientObject *client = (ClientObject *)self; + PyObject *py_targets; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|zb", kwnames, + &py_targets, &comment, &steal_lock)) + return NULL; + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + if (!client_list_to_apr_array(temp_pool, py_targets, py_object_to_svn_path_or_url, &targets)) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_client_lock(targets, comment, steal_lock, client->client, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *client_unlock(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *kwnames[] = { + "targets", "break_lock", + NULL, + }; + apr_array_header_t *targets; + apr_pool_t *temp_pool; + bool break_lock = false; + ClientObject *client = (ClientObject *)self; + PyObject *py_targets; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|b", kwnames, + &py_targets, &break_lock)) + return NULL; + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + if (!client_list_to_apr_array(temp_pool, py_targets, py_object_to_svn_path_or_url, &targets)) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_client_unlock(targets, break_lock, client->client, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + static PyMethodDef client_methods[] = { { "add", (PyCFunction)client_add, METH_VARARGS|METH_KEYWORDS, "S.add(path, recursive=True, force=False, no_ignore=False, no_autoprops=False)" }, { "checkout", (PyCFunction)client_checkout, METH_VARARGS|METH_KEYWORDS, "S.checkout(url, path, rev=None, peg_rev=None, recurse=True, ignore_externals=False, allow_unver_obstructions=False)" }, - { "export", (PyCFunction)client_export, METH_VARARGS|METH_KEYWORDS, - "S.export(from, to, rev=None, peg_rev=None, recurse=True, ignore_externals=False, overwrite=False, native_eol=None)" }, + { "export", (PyCFunction)client_export, METH_VARARGS|METH_KEYWORDS, + "S.export(from, to, rev=None, peg_rev=None, recurse=True, ignore_externals=False, overwrite=False, native_eol=None)" }, { "cat", (PyCFunction)client_cat, METH_VARARGS|METH_KEYWORDS, "S.cat(path, output_stream, revision=None, peg_revision=None)" }, - { "commit", (PyCFunction)client_commit, METH_VARARGS|METH_KEYWORDS, "S.commit(targets, recurse=True, keep_locks=True, revprops=None) -> (revnum, date, author)" }, + { "commit", (PyCFunction)client_commit, METH_VARARGS|METH_KEYWORDS, "S.commit(targets, recurse=True, keep_locks=True, revprops=None, keep_changelist=False, commit_as_operations=False, include_file_externals=False, include_dir_externals=False, callback=None) -> (revnum, date, author)" }, { "delete", client_delete, METH_VARARGS, "S.delete(paths, force=False)" }, { "copy", (PyCFunction)client_copy, METH_VARARGS|METH_KEYWORDS, "S.copy(src_path, dest_path, srv_rev=None)" }, { "propset", client_propset, METH_VARARGS, "S.propset(name, value, target, recurse=True, skip_checks=False)" }, @@ -1853,11 +2093,15 @@ static PyMethodDef client_methods[] = { { "update", (PyCFunction)client_update, METH_VARARGS|METH_KEYWORDS, "S.update(path, rev=None, recurse=True, ignore_externals=False) -> list of revnums" }, { "list", (PyCFunction)client_list, METH_VARARGS|METH_KEYWORDS, "S.list(path, peg_revision, depth, dirents=ra.DIRENT_ALL, revision=None) -> list of directory entries" }, { "diff", (PyCFunction)client_diff, METH_VARARGS|METH_KEYWORDS, "S.diff(rev1, rev2, path1=None, path2=None, relative_to_dir=None, diffopts=[], encoding=\"utf-8\", ignore_ancestry=True, no_diff_deleted=True, ignore_content_type=False) -> unified diff as a string" }, - { "mkdir", (PyCFunction)client_mkdir, METH_VARARGS|METH_KEYWORDS, "S.mkdir(paths, make_parents=False, revprops=None) -> (revnum, date, author)" }, + { "mkdir", (PyCFunction)client_mkdir, METH_VARARGS|METH_KEYWORDS, "S.mkdir(paths, make_parents=False, revprops=None, callback=None)" }, { "log", (PyCFunction)client_log, METH_VARARGS|METH_KEYWORDS, "S.log(callback, paths, start_rev=None, end_rev=None, limit=0, peg_revision=None, discover_changed_paths=False, strict_node_history=False, include_merged_revisions=False, revprops=None)" }, { "info", (PyCFunction)client_info, METH_VARARGS|METH_KEYWORDS, "S.info(path, revision=None, peg_revision=None, depth=DEPTH_EMPTY) -> dict of info entries" }, + { "lock", (PyCFunction)client_lock, METH_VARARGS, + "S.lock(targets, comment, steal_lock=False)" }, + { "unlock", (PyCFunction)client_unlock, METH_VARARGS, + "S.lock(targets, break_lock=False)" }, { NULL, } }; @@ -1871,34 +2115,34 @@ static PyGetSetDef client_getset[] = { static PyObject *get_default_ignores(PyObject *self) { - apr_array_header_t *patterns; - apr_pool_t *pool; - int i = 0; - ConfigObject *configobj = (ConfigObject *)self; - PyObject *ret; - - pool = Pool(NULL); - if (pool == NULL) - return NULL; - RUN_SVN_WITH_POOL(pool, svn_wc_get_default_ignores(&patterns, configobj->config, pool)); - ret = PyList_New(patterns->nelts); - for (i = 0; i < patterns->nelts; i++) { - PyObject *item = PyBytes_FromString(APR_ARRAY_IDX(patterns, i, char *)); - if (item == NULL) { - apr_pool_destroy(pool); - Py_DECREF(item); - Py_DECREF(ret); - return NULL; - } + apr_array_header_t *patterns; + apr_pool_t *pool; + int i = 0; + ConfigObject *configobj = (ConfigObject *)self; + PyObject *ret; + + pool = Pool(NULL); + if (pool == NULL) + return NULL; + RUN_SVN_WITH_POOL(pool, svn_wc_get_default_ignores(&patterns, configobj->config, pool)); + ret = PyList_New(patterns->nelts); + for (i = 0; i < patterns->nelts; i++) { + PyObject *item = PyBytes_FromString(APR_ARRAY_IDX(patterns, i, char *)); + if (item == NULL) { + apr_pool_destroy(pool); + Py_DECREF(item); + Py_DECREF(ret); + return NULL; + } if (PyList_SetItem(ret, i, item) != 0) { - apr_pool_destroy(pool); - Py_DECREF(item); - Py_DECREF(ret); - return NULL; - } - } - apr_pool_destroy(pool); - return ret; + apr_pool_destroy(pool); + Py_DECREF(item); + Py_DECREF(ret); + return NULL; + } + } + apr_pool_destroy(pool); + return ret; } static PyMethodDef config_methods[] = { @@ -2134,7 +2378,7 @@ static PyMemberDef wc_info_members[] = { static void wcinfo_dealloc(PyObject *self) { - PyObject_Del(self); + PyObject_Del(self); } PyTypeObject WCInfo_Type = { @@ -2302,9 +2546,9 @@ static PyObject *get_config(PyObject *self, PyObject *args) */ static PyObject *version(PyObject *self) { - const svn_version_t *ver = svn_client_version(); - return Py_BuildValue("(iiis)", ver->major, ver->minor, - ver->patch, ver->tag); + const svn_version_t *ver = svn_client_version(); + return Py_BuildValue("(iiis)", ver->major, ver->minor, + ver->patch, ver->tag); } SVN_VERSION_DEFINE(svn_api_version); @@ -2316,96 +2560,96 @@ SVN_VERSION_DEFINE(svn_api_version); */ static PyObject *api_version(PyObject *self) { - const svn_version_t *ver = &svn_api_version; - return Py_BuildValue("(iiis)", ver->major, ver->minor, - ver->patch, ver->tag); + const svn_version_t *ver = &svn_api_version; + return Py_BuildValue("(iiis)", ver->major, ver->minor, + ver->patch, ver->tag); } static PyMethodDef client_mod_methods[] = { - { "get_config", get_config, METH_VARARGS, "get_config(config_dir=None) -> config" }, - { "api_version", (PyCFunction)api_version, METH_NOARGS, - "api_version() -> (major, minor, patch, tag)\n\n" - "Version of libsvn_client Subvertpy was compiled against." - }, - { "version", (PyCFunction)version, METH_NOARGS, - "version() -> (major, minor, patch, tag)\n\n" - "Version of libsvn_wc currently used." - }, - { NULL } + { "get_config", get_config, METH_VARARGS, "get_config(config_dir=None) -> config" }, + { "api_version", (PyCFunction)api_version, METH_NOARGS, + "api_version() -> (major, minor, patch, tag)\n\n" + "Version of libsvn_client Subvertpy was compiled against." + }, + { "version", (PyCFunction)version, METH_NOARGS, + "version() -> (major, minor, patch, tag)\n\n" + "Version of libsvn_wc currently used." + }, + { NULL } }; static PyObject * moduleinit(void) { - PyObject *mod; + PyObject *mod; - if (PyType_Ready(&Client_Type) < 0) - return NULL; + if (PyType_Ready(&Client_Type) < 0) + return NULL; - if (PyType_Ready(&Config_Type) < 0) - return NULL; + if (PyType_Ready(&Config_Type) < 0) + return NULL; - if (PyType_Ready(&ConfigItem_Type) < 0) - return NULL; + if (PyType_Ready(&ConfigItem_Type) < 0) + return NULL; - if (PyType_Ready(&Info_Type) < 0) - return NULL; + if (PyType_Ready(&Info_Type) < 0) + return NULL; - if (PyType_Ready(&WCInfo_Type) < 0) - return NULL; + if (PyType_Ready(&WCInfo_Type) < 0) + return NULL; - /* Make sure APR is initialized */ - apr_initialize(); + /* Make sure APR is initialized */ + apr_initialize(); #if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "client", /* m_name */ - "Client methods", /* m_doc */ - -1, /* m_size */ - client_mod_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear*/ - NULL, /* m_free */ - }; - mod = PyModule_Create(&moduledef); + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "client", /* m_name */ + "Client methods", /* m_doc */ + -1, /* m_size */ + client_mod_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear*/ + NULL, /* m_free */ + }; + mod = PyModule_Create(&moduledef); #else - mod = Py_InitModule3("client", client_mod_methods, "Client methods"); + mod = Py_InitModule3("client", client_mod_methods, "Client methods"); #endif - if (mod == NULL) - return NULL; + if (mod == NULL) + return NULL; - Py_INCREF(&Client_Type); - PyModule_AddObject(mod, "Client", (PyObject *)&Client_Type); + Py_INCREF(&Client_Type); + PyModule_AddObject(mod, "Client", (PyObject *)&Client_Type); - PyModule_AddObject(mod, "depth_empty", - (PyObject *)PyLong_FromLong(svn_depth_empty)); - PyModule_AddObject(mod, "depth_files", - (PyObject *)PyLong_FromLong(svn_depth_files)); - PyModule_AddObject(mod, "depth_immediates", - (PyObject *)PyLong_FromLong(svn_depth_immediates)); - PyModule_AddObject(mod, "depth_infinity", - (PyObject *)PyLong_FromLong(svn_depth_infinity)); + PyModule_AddObject(mod, "depth_empty", + (PyObject *)PyLong_FromLong(svn_depth_empty)); + PyModule_AddObject(mod, "depth_files", + (PyObject *)PyLong_FromLong(svn_depth_files)); + PyModule_AddObject(mod, "depth_immediates", + (PyObject *)PyLong_FromLong(svn_depth_immediates)); + PyModule_AddObject(mod, "depth_infinity", + (PyObject *)PyLong_FromLong(svn_depth_infinity)); - Py_INCREF(&Config_Type); - PyModule_AddObject(mod, "Config", (PyObject *)&Config_Type); + Py_INCREF(&Config_Type); + PyModule_AddObject(mod, "Config", (PyObject *)&Config_Type); - return mod; + return mod; } #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit_client(void) { - return moduleinit(); + return moduleinit(); } #else PyMODINIT_FUNC initclient(void) { - moduleinit(); + moduleinit(); } #endif diff --git a/subvertpy/editor.c b/subvertpy/editor.c index b425de0c..71b543a5 100644 --- a/subvertpy/editor.c +++ b/subvertpy/editor.c @@ -397,15 +397,15 @@ static PyObject *py_dir_editor_delete_entry(PyObject *self, PyObject *args) static PyObject *py_dir_editor_add_directory(PyObject *self, PyObject *args) { - PyObject *py_path; + PyObject *py_path, *py_copyfrom_path = Py_None; const char *path; - char *copyfrom_path = NULL; + const char *copyfrom_path = NULL; svn_revnum_t copyfrom_rev = -1; void *child_baton; EditorObject *editor = (EditorObject *)self; apr_pool_t *subpool; - if (!PyArg_ParseTuple(args, "O|zl", &py_path, ©from_path, ©from_rev)) + if (!PyArg_ParseTuple(args, "O|Ol", &py_path, &py_copyfrom_path, ©from_rev)) return NULL; if (editor->done) { @@ -423,6 +423,13 @@ static PyObject *py_dir_editor_add_directory(PyObject *self, PyObject *args) return NULL; } + if (py_copyfrom_path != Py_None) { + copyfrom_path = py_object_to_svn_uri(py_copyfrom_path, editor->pool); + if (copyfrom_path == NULL) { + return NULL; + } + } + RUN_SVN(editor->editor->add_directory( path, editor->baton, copyfrom_path == NULL?NULL:svn_uri_canonicalize(copyfrom_path, editor->pool), @@ -563,14 +570,14 @@ static PyObject *py_dir_editor_absent_directory(PyObject *self, PyObject *args) static PyObject *py_dir_editor_add_file(PyObject *self, PyObject *args) { const char *path; - char *copy_path=NULL; - PyObject *py_path; + const char *copy_path=NULL; + PyObject *py_path, *py_copy_path = Py_None; svn_revnum_t copy_rev=-1; void *file_baton = NULL; EditorObject *editor = (EditorObject *)self; apr_pool_t *subpool; - if (!PyArg_ParseTuple(args, "O|zl", &py_path, ©_path, ©_rev)) + if (!PyArg_ParseTuple(args, "O|Ol", &py_path, &py_copy_path, ©_rev)) return NULL; if (editor->done) { @@ -588,9 +595,15 @@ static PyObject *py_dir_editor_add_file(PyObject *self, PyObject *args) return NULL; } + if (py_copy_path != Py_None) { + copy_path = py_object_to_svn_uri(py_copy_path, editor->pool); + if (copy_path == NULL) { + return NULL; + } + } + RUN_SVN(editor->editor->add_file(path, editor->baton, - copy_path == NULL?NULL:svn_uri_canonicalize(copy_path, editor->pool), - copy_rev, editor->pool, &file_baton)); + copy_path, copy_rev, editor->pool, &file_baton)); subpool = Pool(NULL); if (subpool == NULL) diff --git a/subvertpy/properties.py b/subvertpy/properties.py index 567f60cc..8f256f5f 100644 --- a/subvertpy/properties.py +++ b/subvertpy/properties.py @@ -85,10 +85,10 @@ def parse_externals_description(base_url, val): def is_url(u): return ("://" in u) ret = {} - for l in val.splitlines(): - if l == "" or l[0] == "#": + for line in val.splitlines(): + if line == "" or line[0] == "#": continue - pts = l.rsplit(None, 3) + pts = line.rsplit(None, 3) if len(pts) == 4: if pts[0] == "-r": # -r X URL DIR revno = int(pts[1]) @@ -137,8 +137,8 @@ def parse_mergeinfo_property(text): :param text: Property contents """ ret = {} - for l in text.splitlines(): - (path, ranges) = l.rsplit(":", 1) + for line in text.splitlines(): + (path, ranges) = line.rsplit(":", 1) assert path.startswith("/") ret[path] = [] for range in ranges.split(","): diff --git a/subvertpy/ra_svn.py b/subvertpy/ra_svn.py index 3e61e283..eae6a311 100644 --- a/subvertpy/ra_svn.py +++ b/subvertpy/ra_svn.py @@ -502,19 +502,20 @@ class SVNClient(SVNConnection): host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, 0) self._socket = None - err = RuntimeError('no addresses for %s:%s' % (host, port)) + last_err = RuntimeError('no addresses for %s:%s' % (host, port)) for (family, socktype, proto, canonname, sockaddr) in sockaddrs: try: self._socket = socket.socket(family, socktype, proto) self._socket.connect(sockaddr) except socket.error as err: + last_err = err if self._socket is not None: self._socket.close() self._socket = None continue break if self._socket is None: - raise err + raise last_err self._socket.setblocking(True) return (self._socket.recv, self._socket.send) @@ -745,7 +746,7 @@ class SVNClient(SVNConnection): self.send_msg([literal("switch"), args]) self._recv_ack() return Reporter(self, update_editor) - except: + except BaseException: self.busy = False raise @@ -766,7 +767,7 @@ class SVNClient(SVNConnection): self.send_msg([literal("update"), args]) self._recv_ack() return Reporter(self, update_editor) - except: + except BaseException: self.busy = False raise @@ -787,7 +788,7 @@ class SVNClient(SVNConnection): self.send_msg([literal("diff"), args]) self._recv_ack() return Reporter(self, diff_editor) - except: + except BaseException: self.busy = False raise diff --git a/subvertpy/repos.c b/subvertpy/repos.c index e6a41c15..61006ab8 100644 --- a/subvertpy/repos.c +++ b/subvertpy/repos.c @@ -39,7 +39,7 @@ typedef struct { static PyObject *repos_create(PyObject *self, PyObject *args) { - char *path; + const char *path; PyObject *config=Py_None, *fs_config=Py_None, *py_path; svn_repos_t *repos = NULL; apr_pool_t *pool; @@ -64,7 +64,7 @@ static PyObject *repos_create(PyObject *self, PyObject *args) return NULL; } - path = py_object_to_svn_string(py_path, pool); + path = py_object_to_svn_dirent(py_path, pool); if (path == NULL) { apr_pool_destroy(pool); return NULL; @@ -499,6 +499,7 @@ static PyObject *repos_verify(RepositoryObject *self, PyObject *args) Py_RETURN_NONE; } +#if ONLY_SINCE_SVN(1, 6) static svn_error_t *py_pack_notify(void *baton, apr_int64_t shard, svn_fs_pack_notify_action_t action, apr_pool_t *pool) { PyObject *ret; @@ -510,9 +511,11 @@ static svn_error_t *py_pack_notify(void *baton, apr_int64_t shard, svn_fs_pack_n Py_DECREF(ret); return NULL; } +#endif static PyObject *repos_pack(RepositoryObject *self, PyObject *args) { +#if ONLY_SINCE_SVN(1, 6) apr_pool_t *temp_pool; PyObject *notify_func = Py_None; if (!PyArg_ParseTuple(args, "|O", ¬ify_func)) @@ -526,6 +529,10 @@ static PyObject *repos_pack(RepositoryObject *self, PyObject *args) apr_pool_destroy(temp_pool); Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, "pack_fs is only supported in Subversion >= 1.6"); + return NULL; +#endif } static PyMethodDef repos_methods[] = { diff --git a/subvertpy/subr.c b/subvertpy/subr.c new file mode 100644 index 00000000..6bf2aa26 --- /dev/null +++ b/subvertpy/subr.c @@ -0,0 +1,130 @@ +/* + * Copyright © 2017 Jelmer Vernooij <jelmer@jelmer.uk> + * -*- coding: utf-8 -*- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include <Python.h> +#include <apr_general.h> +#include <svn_path.h> +#include <stdbool.h> +#include <apr_md5.h> +#include <apr_sha1.h> + +#include "util.h" + +static PyObject *py_uri_canonicalize(PyObject *self, PyObject *args) +{ + const char *uri; + PyObject *py_uri, *ret; + apr_pool_t *pool; + + if (!PyArg_ParseTuple(args, "O", &py_uri)) + return NULL; + + pool = Pool(NULL); + uri = py_object_to_svn_uri(py_uri, pool); + ret = PyUnicode_FromString(uri); + apr_pool_destroy(pool); + + return ret; +} + +static PyObject *py_dirent_canonicalize(PyObject *self, PyObject *args) +{ + const char *dirent; + PyObject *py_dirent, *ret; + apr_pool_t *pool; + + if (!PyArg_ParseTuple(args, "O", &py_dirent)) + return NULL; + + pool = Pool(NULL); + dirent = py_object_to_svn_dirent(py_dirent, pool); + ret = PyUnicode_FromString(dirent); + apr_pool_destroy(pool); + + return ret; +} + +static PyObject *py_abspath(PyObject *self, PyObject *args) +{ + const char *path; + PyObject *py_path, *ret; + apr_pool_t *pool; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + pool = Pool(NULL); + path = py_object_to_svn_abspath(py_path, pool); + ret = PyUnicode_FromString(path); + apr_pool_destroy(pool); + + return ret; +} + +static PyMethodDef subr_methods[] = { + { "uri_canonicalize", py_uri_canonicalize, METH_VARARGS, "uri_canonicalize(uri) -> uri\n" + "Canonicalize a URI."}, + { "dirent_canonicalize", py_dirent_canonicalize, METH_VARARGS, "dirent_canonicalize(dirent) -> dirent\n" + "Canonicalize a dirent path."}, + { "abspath", py_abspath, METH_VARARGS, "abspath(path) -> path\n" + "Return the absolute version of a path."}, + { NULL } +}; + +static PyObject * +moduleinit(void) +{ + PyObject *mod; + + apr_initialize(); + +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "subr", /* m_name */ + "subr", /* m_doc */ + -1, /* m_size */ + subr_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear*/ + NULL, /* m_free */ + }; + mod = PyModule_Create(&moduledef); +#else + mod = Py_InitModule3("subr", subr_methods, "Subversion subr"); +#endif + if (mod == NULL) + return NULL; + + return mod; +} + +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC +PyInit_subr(void) +{ + return moduleinit(); +} +#else +PyMODINIT_FUNC +initsubr(void) +{ + moduleinit(); +} +#endif diff --git a/subvertpy/tests/__init__.py b/subvertpy/tests/__init__.py index 7fb1e008..71b35763 100644 --- a/subvertpy/tests/__init__.py +++ b/subvertpy/tests/__init__.py @@ -198,6 +198,13 @@ class TestCommitEditor(TestDirEditor): TestDirEditor.close(self) self.editor.close() + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self.close() + return False + class SubversionTestCase(TestCaseInTempDir): """A test case that provides the ability to build Subversion @@ -237,19 +244,13 @@ class SubversionTestCase(TestCaseInTempDir): if sys.platform == 'win32': revprop_hook = os.path.join( abspath, "hooks", "pre-revprop-change.bat") - f = open(revprop_hook, 'w') - try: + with open(revprop_hook, 'w') as f: f.write("exit 0\n") - finally: - f.close() else: revprop_hook = os.path.join( abspath, "hooks", "pre-revprop-change") - f = open(revprop_hook, 'w') - try: + with open(revprop_hook, 'w') as f: f.write("#!/bin/sh\n") - finally: - f.close() os.chmod(revprop_hook, os.stat(revprop_hook).st_mode | 0o111) if sys.platform == 'win32': @@ -305,6 +306,12 @@ class SubversionTestCase(TestCaseInTempDir): """Resolve a conflict set on a local path.""" self.client_ctx.resolve(path, depth, choice) + def client_lock(self, path, comment="A comment", steal_lock=False): + self.client_ctx.lock(path, comment, steal_lock) + + def client_unlock(self, path, steal_lock=False): + self.client_ctx.unlock(path, steal_lock) + def client_commit(self, dir, message=None, recursive=True): """Commit current changes in specified working copy. @@ -313,10 +320,14 @@ class SubversionTestCase(TestCaseInTempDir): olddir = os.path.abspath('.') self.next_message = message os.chdir(dir) - info = self.client_ctx.commit(["."], recursive, False) + info = [] + + def add_info(*args): + info.append(args) + self.client_ctx.commit(["."], recursive, False, callback=add_info) os.chdir(olddir) - assert info is not None - return info + assert len(info) == 1 + return info[0] def client_add(self, relpath, recursive=True): """Add specified files to working copy. @@ -334,7 +345,6 @@ class SubversionTestCase(TestCaseInTempDir): :return: Dictionary """ r = ra.RemoteAccess(url) - assert isinstance(url, str) ret = {} def rcvr(orig_paths, rev, revprops, has_children=None): @@ -343,7 +353,7 @@ class SubversionTestCase(TestCaseInTempDir): revprops.get(properties.PROP_REVISION_AUTHOR), revprops.get(properties.PROP_REVISION_DATE), revprops.get(properties.PROP_REVISION_LOG)) - r.get_log(rcvr, [""], start_revnum, stop_revnum, 0, True, True, + r.get_log(rcvr, [u""], start_revnum, stop_revnum, 0, True, True, revprops=[properties.PROP_REVISION_AUTHOR, properties.PROP_REVISION_DATE, properties.PROP_REVISION_LOG]) @@ -392,11 +402,8 @@ class SubversionTestCase(TestCaseInTempDir): os.makedirs(os.path.dirname(name)) except OSError: pass - f = open(name, 'wb') - try: + with open(name, 'wb') as f: f.write(content) - finally: - f.close() def make_client(self, repospath, clientpath, allow_revprop_changes=True): """Create a repository and a checkout. Return the checkout. @@ -442,6 +449,7 @@ def test_suite(): 'ra', 'repos', 'server', + 'subr', 'wc', ] module_names = ['subvertpy.tests.test_' + name for name in names] diff --git a/subvertpy/tests/test_client.py b/subvertpy/tests/test_client.py index a1a0534d..7fa04c6e 100644 --- a/subvertpy/tests/test_client.py +++ b/subvertpy/tests/test_client.py @@ -217,7 +217,7 @@ class TestClient(SubversionTestCase): self.client.log_msg_func = lambda c: commit_msg_1 self.client.commit(["dc"]) commit_1_dt = datetime.utcnow() - self.client.log(cb, "dc/foo") + self.client.log(cb, "dc/foo", start_rev="HEAD", end_rev=1) self.assertEqual(1, len(log_entries)) self.assertEqual(None, log_entries[0]["changed_paths"]) self.assertEqual(1, log_entries[0]["revision"]) @@ -232,7 +232,9 @@ class TestClient(SubversionTestCase): self.client.commit(["dc"]) commit_2_dt = datetime.utcnow() log_entries = [] - self.client.log(cb, "dc/foo", discover_changed_paths=True) + self.client.log( + cb, "dc/foo", start_rev="HEAD", end_rev=1, + discover_changed_paths=True) self.assertEqual(2, len(log_entries)) self.assertLogEntryChangedPathsEquals(["/foo", "/bar"], log_entries[0]) self.assertEqual(2, log_entries[0]["revision"]) diff --git a/subvertpy/tests/test_marshall.py b/subvertpy/tests/test_marshall.py index 751276f2..47d659dc 100644 --- a/subvertpy/tests/test_marshall.py +++ b/subvertpy/tests/test_marshall.py @@ -28,20 +28,20 @@ from subvertpy.tests import TestCase class TestMarshalling(TestCase): def test_literal_txt(self): - l = literal("foo") - self.assertEqual("foo", l.txt) + line = literal("foo") + self.assertEqual("foo", line.txt) def test_literal_str(self): - l = literal("foo bar") - self.assertEqual("foo bar", l.__str__()) + line = literal("foo bar") + self.assertEqual("foo bar", line.__str__()) def test_literal_rep(self): - l = literal("foo bar") - self.assertEqual("foo bar", l.__repr__()) + line = literal("foo bar") + self.assertEqual("foo bar", line.__repr__()) def test_marshall_error(self): - e = MarshallError("bla bla") - self.assertEqual("bla bla", e.__str__()) + err = MarshallError("bla bla") + self.assertEqual("bla bla", err.__str__()) def test_marshall_int(self): self.assertEqual(b"1 ", marshall(1)) diff --git a/subvertpy/tests/test_ra.py b/subvertpy/tests/test_ra.py index 51ad42b3..cafa7479 100644 --- a/subvertpy/tests/test_ra.py +++ b/subvertpy/tests/test_ra.py @@ -85,9 +85,9 @@ class TestRemoteAccess(SubversionTestCase): def test_get_url(self): if ra.api_version() < (1, 5): - self.assertRaises(NotImplementedError, self.ra.get_url) + self.assertRaises(NotImplementedError, self.ra.get_session_url) else: - self.assertEqual(self.repos_url, self.ra.get_url()) + self.assertEqual(self.repos_url, self.ra.get_session_url()) def test_reparent(self): self.ra.reparent(self.repos_url) diff --git a/subvertpy/tests/test_repos.py b/subvertpy/tests/test_repos.py index ed5f2067..d748df91 100644 --- a/subvertpy/tests/test_repos.py +++ b/subvertpy/tests/test_repos.py @@ -117,7 +117,10 @@ class TestRepository(TestCaseInTempDir): def test_pack_fs(self): r = repos.create(os.path.join(self.test_dir, "foo")) - r.pack_fs() + if repos.api_version() < (1, 6): + self.assertRaises(NotImplementedError, r.pack_fs) + else: + r.pack_fs() def test_paths_changed(self): repos.create(os.path.join(self.test_dir, "foo")) diff --git a/subvertpy/tests/test_subr.py b/subvertpy/tests/test_subr.py new file mode 100644 index 00000000..c471e65f --- /dev/null +++ b/subvertpy/tests/test_subr.py @@ -0,0 +1,64 @@ +# Copyright (C) 2017 Jelmer Vernooij <jelmer@jelmer.uk> + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +"""Subversion subr library tests.""" + +import os +from unittest import TestCase + +from subvertpy.subr import ( + uri_canonicalize, + dirent_canonicalize, + abspath, + ) + + +class UriCanonicalizeTests(TestCase): + + def test_canonicalize(self): + self.assertEqual( + 'https://www.example.com', + uri_canonicalize('https://www.example.com/')) + self.assertEqual( + 'https://www.example.com(bla)', + uri_canonicalize('https://www.example.com(bla)')) + self.assertEqual( + 'https://www.example.com/(bla)', + uri_canonicalize('https://www.example.com/(bla%29')) + + +class DirentCanonicalizeTests(TestCase): + + def test_canonicalize(self): + self.assertEqual( + '/foo/bar', + dirent_canonicalize('/foo/bar')) + self.assertEqual( + '/foo/bar', + dirent_canonicalize('/foo//bar')) + + +class AbspathTests(TestCase): + + def test_abspath(self): + self.assertEqual( + '/foo/bar', + abspath('/foo//bar')) + self.assertEqual( + os.path.join(os.getcwd(), 'bar'), + abspath('bar')) + self.assertEqual( + os.path.join(os.getcwd(), 'bar', 'foo'), + abspath('bar/foo')) diff --git a/subvertpy/tests/test_wc.py b/subvertpy/tests/test_wc.py index 940cfc6e..a501aee1 100644 --- a/subvertpy/tests/test_wc.py +++ b/subvertpy/tests/test_wc.py @@ -15,12 +15,13 @@ """Subversion ra library tests.""" +import hashlib from io import BytesIO import os -from unittest import SkipTest import subvertpy from subvertpy import ( + NODE_DIR, NODE_FILE, wc, ) @@ -42,16 +43,16 @@ class VersionTest(TestCase): self.assertTrue(wc.api_version() <= wc.version()) -class WorkingCopyTests(TestCase): +class AdmTests(TestCase): def test_get_adm_dir(self): - self.assertEqual(b".svn", wc.get_adm_dir()) + self.assertEqual(".svn", wc.get_adm_dir()) def test_set_adm_dir(self): old_dir_name = wc.get_adm_dir() try: wc.set_adm_dir(b"_svn") - self.assertEqual(b"_svn", wc.get_adm_dir()) + self.assertEqual("_svn", wc.get_adm_dir()) finally: wc.set_adm_dir(old_dir_name) @@ -68,11 +69,11 @@ class WorkingCopyTests(TestCase): if wc.api_version() < (1, 5): self.assertRaises( NotImplementedError, wc.match_ignore_list, "foo", []) - return # Skip test - self.assertTrue(wc.match_ignore_list("foo", ["f*"])) - self.assertTrue(wc.match_ignore_list("foo", ["foo"])) - self.assertFalse(wc.match_ignore_list("foo", [])) - self.assertFalse(wc.match_ignore_list("foo", ["bar"])) + else: + self.assertTrue(wc.match_ignore_list("foo", ["f*"])) + self.assertTrue(wc.match_ignore_list("foo", ["foo"])) + self.assertFalse(wc.match_ignore_list("foo", [])) + self.assertFalse(wc.match_ignore_list("foo", ["bar"])) class WcTests(SubversionTestCase): @@ -88,52 +89,64 @@ class WcTests(SubversionTestCase): self.assertEqual((0, 0, 0, 0), ret) -class AdmTests(SubversionTestCase): - - def setUp(self): - super(AdmTests, self).setUp() - if getattr(wc, "WorkingCopy", None) is None: - raise SkipTest( - "Subversion 1.7 API for WorkingCopy not yet supported") +class AdmObjTests(SubversionTestCase): def test_has_binary_prop(self): self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"\x00 \x01"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout") - path = os.path.join(self.test_dir, "checkout/bar") - self.assertFalse(adm.has_binary_prop(path)) + self.client_set_prop('checkout/bar', 'svn:mime-type', 'text/bar') + adm = wc.Adm(None, "checkout") + self.assertFalse(adm.has_binary_prop("checkout/bar")) adm.close() + def test_with(self): + self.make_client("repos", "checkout") + self.build_tree({"checkout/bar": b"\x00 \x01"}) + self.client_add('checkout/bar') + self.client_set_prop('checkout/bar', 'svn:mime-type', 'text/bar') + with wc.Adm(None, "checkout") as adm: + self.assertFalse(adm.has_binary_prop("checkout/bar")) + def test_get_ancestry(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"\x00 \x01"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout") + adm = wc.Adm(None, "checkout") self.assertEqual(("%s/bar" % repos_url, 0), adm.get_ancestry("checkout/bar")) adm.close() def test_maybe_set_repos_root(self): repos_url = self.make_client("repos", "checkout") - adm = wc.WorkingCopy(None, "checkout") + adm = wc.Adm(None, "checkout") adm.maybe_set_repos_root( os.path.join(self.test_dir, "checkout"), repos_url) adm.close() def test_add_repos_file(self): self.make_client("repos", "checkout") - adm = wc.WorkingCopy(None, "checkout", True) - adm.add_repos_file("checkout/bar", BytesIO(b"basecontents"), - BytesIO(b"contents"), {}, {}) - self.assertEqual(b"basecontents", - wc.get_pristine_contents("checkout/bar").read()) + adm = wc.Adm(None, "checkout", True) + if wc.api_version() < (1, 6): + self.assertRaises( + NotImplementedError, + adm.add_repos_file, "checkout/bar", + BytesIO(b"basecontents"), BytesIO(b"contents"), {}, {}) + else: + adm.add_repos_file("checkout/bar", BytesIO(b"basecontents"), + BytesIO(b"contents"), {}, {}) + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") + self.assertEqual(b"basecontents", + wc.get_pristine_contents("checkout/bar").read()) def test_mark_missing_deleted(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"\x00 \x01"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) os.remove("checkout/bar") adm.mark_missing_deleted("checkout/bar") self.assertFalse(os.path.exists("checkout/bar")) @@ -142,25 +155,27 @@ class AdmTests(SubversionTestCase): self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"\x00 \x01"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) adm.remove_from_revision_control("bar") self.assertTrue(os.path.exists("checkout/bar")) def test_relocate(self): self.make_client("repos", "checkout") - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) adm.relocate("checkout", "file://", "http://") def test_translated_stream(self): + self.skipTest("TODO: doesn't yet work") self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"My id: $Id$"}) self.client_add('checkout/bar') self.client_set_prop("checkout/bar", "svn:keywords", "Id\n") self.client_commit("checkout", "foo") - adm = wc.WorkingCopy(None, "checkout", True) - path = os.path.join(self.test_dir, "checkout/bar") - stream = adm.translated_stream(path, path, wc.TRANSLATE_TO_NF) - self.assertTrue(stream.read().startswith(b"My id: $Id: ")) + adm = wc.Adm(None, "checkout", True) + stream = adm.translated_stream( + 'checkout/bar', 'checkout/bar', wc.TRANSLATE_TO_NF) + body = stream.read() + self.assertTrue(body.startswith(b"My id: $Id: "), body) def test_text_modified(self): self.make_client("repos", "checkout") @@ -168,7 +183,7 @@ class AdmTests(SubversionTestCase): self.client_add('checkout/bar') self.client_set_prop("checkout/bar", "svn:keywords", "Id\n") self.client_commit("checkout", "foo") - adm = wc.WorkingCopy(None, "checkout") + adm = wc.Adm(None, "checkout") self.assertFalse(adm.text_modified("checkout/bar")) self.build_tree({"checkout/bar": b"gambon"}) self.assertTrue(adm.text_modified("checkout/bar", True)) @@ -179,7 +194,7 @@ class AdmTests(SubversionTestCase): self.client_add('checkout/bar') self.client_set_prop("checkout/bar", "svn:keywords", "Id\n") self.client_commit("checkout", "foo") - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) self.assertFalse(adm.props_modified("checkout/bar")) adm.prop_set("aprop", "avalue", "checkout/bar") self.assertTrue(adm.props_modified("checkout/bar")) @@ -188,37 +203,35 @@ class AdmTests(SubversionTestCase): self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"file"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) adm.prop_set("aprop", "avalue", "checkout/bar") - self.assertEqual(adm.prop_get("aprop", "checkout/bar"), "avalue") + self.assertEqual(adm.prop_get("aprop", "checkout/bar"), b"avalue") adm.prop_set("aprop", None, "checkout/bar") self.assertEqual(adm.prop_get("aprop", "checkout/bar"), None) def test_committed_queue(self): - if getattr(wc, "CommittedQueue", None) is None: - raise SkipTest("CommittedQueue not available") cq = wc.CommittedQueue() self.make_client("repos", "checkout") - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", "jelmer") def test_entry_not_found(self): self.make_client("repos", "checkout") - adm = wc.WorkingCopy(None, "checkout") + adm = wc.Adm(None, "checkout") self.assertRaises(KeyError, adm.entry, "bar") def test_entry(self): self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"\x00 \x01"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout") + adm = wc.Adm(None, "checkout") entry = adm.entry("checkout/bar") self.assertEqual("bar", entry.name) self.assertEqual(NODE_FILE, entry.kind) self.assertEqual(0, entry.revision) self.client_commit("checkout", "msg") - adm = wc.WorkingCopy(None, "checkout") + adm = wc.Adm(None, "checkout") entry = adm.entry("checkout/bar") self.assertEqual("bar", entry.name) self.assertEqual(NODE_FILE, entry.kind) @@ -233,7 +246,7 @@ class AdmTests(SubversionTestCase): self.make_client("repos", ".") self.build_tree({"bar": None}) self.client_add('bar') - adm = wc.WorkingCopy(None, ".") + adm = wc.Adm(None, ".") self.assertTrue(adm.is_wc_root(self.test_dir)) self.assertFalse(adm.is_wc_root(os.path.join(self.test_dir, "bar"))) @@ -241,23 +254,33 @@ class AdmTests(SubversionTestCase): self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"text"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout") - self.assertEqual(wc.STATUS_ADDED, adm.status('bar').status) + adm = wc.Adm(None, "checkout") + self.assertEqual( + wc.STATUS_ADDED, + adm.status('checkout/bar').status) self.client_commit("checkout", "foo") - adm = wc.WorkingCopy(None, "checkout") - self.assertEqual(wc.STATUS_NORMAL, adm.status('bar').status) + adm = wc.Adm(None, "checkout") + self.assertEqual( + wc.STATUS_NORMAL, + adm.status('checkout/bar').status) def test_transmit_text_deltas(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") self.make_client("repos", ".") self.build_tree({"bar": b"blala"}) self.client_add('bar') - adm = wc.WorkingCopy(None, ".", True) + adm = wc.Adm(None, ".", True) class Editor(object): """Editor""" def __init__(self): self._windows = [] + self._prop = {} + + def change_prop(self, name, value): + self._prop[name] = value def apply_textdelta(self, checksum): def window_handler(window): @@ -267,47 +290,120 @@ class AdmTests(SubversionTestCase): def close(self): pass editor = Editor() - (tmpfile, digest) = adm.transmit_text_deltas("bar", True, editor) + (tmpfile, md5_digest) = adm.transmit_text_deltas("bar", True, editor) self.assertEqual(editor._windows, [(0, 0, 5, 0, [(2, 0, 5)], b'blala'), None]) self.assertIsInstance(tmpfile, str) - self.assertEqual(16, len(digest)) + self.assertEqual(16, len(md5_digest)) + self.assertEqual(hashlib.md5(b'blala').digest(), md5_digest) bar = adm.entry("bar") self.assertEqual(-1, bar.cmt_rev) self.assertEqual(0, bar.revision) + self.assertIn(bar.checksum, (None, hashlib.md5(b'blala').hexdigest())) cq = wc.CommittedQueue() - cq.queue("bar", adm) + cq.queue("bar", adm, wcprop_changes=editor._prop, + md5_digest=md5_digest) adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", "jelmer") bar = adm.entry("bar") self.assertEqual("bar", bar.name) self.assertEqual(NODE_FILE, bar.kind) self.assertEqual(wc.SCHEDULE_NORMAL, bar.schedule) - self.assertIs(None, bar.checksum) + self.assertIn(bar.checksum, (None, hashlib.md5(b'blala').hexdigest())) self.assertEqual(1, bar.cmt_rev) self.assertEqual(1, bar.revision) def test_process_committed_queue(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") self.make_client("repos", "checkout") - self.build_tree({"checkout/bar": b"la"}) + self.build_tree({"checkout/bar": b"blala"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) + + class Editor(object): + """Editor""" + + def __init__(self): + self._windows = [] + self._prop = {} + + def change_prop(self, name, value): + self._prop[name] = value + + def apply_textdelta(self, checksum): + def window_handler(window): + self._windows.append(window) + return window_handler + + def close(self): + pass + + editor = Editor() + (tmpfile, md5_digest) = adm.transmit_text_deltas( + "checkout/bar", True, editor) + self.assertEqual(editor._windows, + [(0, 0, 5, 0, [(2, 0, 5)], b'blala'), None]) + self.assertIsInstance(tmpfile, str) + self.assertEqual(16, len(md5_digest)) + self.assertEqual(hashlib.md5(b'blala').digest(), md5_digest) + cq = wc.CommittedQueue() - cq.queue(os.path.join(self.test_dir, "checkout/bar"), adm) + cq.queue("checkout/bar", adm, wcprop_changes=editor._prop, + md5_digest=md5_digest) adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", "jelmer") bar = adm.entry("checkout/bar") self.assertEqual("bar", bar.name) self.assertEqual(NODE_FILE, bar.kind) - self.assertEqual(wc.SCHEDULE_ADD, bar.schedule) + self.assertEqual(wc.SCHEDULE_NORMAL, bar.schedule) + + def test_process_committed(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") + self.make_client("repos", ".") + self.build_tree({"bar": b"la"}) + self.client_add('bar') + adm = wc.Adm(None, ".", True) + + class Editor(object): + """Editor""" + + def __init__(self): + self._windows = [] + + def apply_textdelta(self, checksum): + def window_handler(window): + self._windows.append(window) + return window_handler + + def close(self): + pass + editor = Editor() + (tmpfile, digest) = adm.transmit_text_deltas("bar", True, editor) + self.assertEqual(editor._windows, + [(0, 0, 2, 0, [(2, 0, 2)], b'la'), None]) + self.assertIsInstance(tmpfile, str) + self.assertEqual(16, len(digest)) + self.assertEqual(hashlib.md5(b'la').digest(), digest) + bar = adm.entry("bar") + self.assertEqual(-1, bar.cmt_rev) + self.assertEqual(0, bar.revision) + + adm.process_committed( + "bar", False, 1, "2010-05-31T08:49:22.430000Z", "jelmer") + bar = adm.entry("bar") + self.assertEqual("bar", bar.name) + self.assertEqual(NODE_FILE, bar.kind) + self.assertEqual(wc.SCHEDULE_NORMAL, bar.schedule) def test_probe_try(self): self.make_client("repos", "checkout") self.build_tree({"checkout/bar": b"la"}) self.client_add('checkout/bar') - adm = wc.WorkingCopy(None, "checkout", True) + adm = wc.Adm(None, "checkout", True) try: self.assertIs(None, adm.probe_try(self.test_dir)) except subvertpy.SubversionException as e: @@ -315,5 +411,155 @@ class AdmTests(SubversionTestCase): if num != subvertpy.ERR_WC_NOT_WORKING_COPY: raise self.assertEqual( - "checkout", + os.path.abspath("checkout"), adm.probe_try(os.path.join("checkout", "bar")).access_path()) + + def test_lock(self): + if wc.api_version() >= (1, 9): + self.skipTest("TODO: doesn't yet work with svn >= 1.9") + self.make_client("repos", "checkout") + self.build_tree({"checkout/bar": b"la"}) + self.client_add('checkout/bar') + adm = wc.Adm(None, "checkout", True) + lock = wc.Lock() + lock.token = b"blah" + adm.add_lock("checkout", lock) + adm.remove_lock("checkout") + + +class ContextTests(SubversionTestCase): + + def setUp(self): + super(ContextTests, self).setUp() + if wc.api_version() < (1, 7): + self.skipTest("context API not available on Subversion < 1.7") + + def test_create(self): + context = wc.Context() + self.assertIsInstance(context, wc.Context) + + def test_locked(self): + context = wc.Context() + self.make_client("repos", "checkout") + self.assertEqual((False, False), context.locked("checkout")) + + def test_check_wc(self): + context = wc.Context() + self.make_client("repos", "checkout") + self.assertIsInstance(context.check_wc("checkout"), int) + + def test_text_modified(self): + context = wc.Context() + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + self.client_add("checkout/bla.txt") + self.assertTrue(context.text_modified("checkout/bla.txt")) + + def test_props_modified(self): + context = wc.Context() + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + self.client_add("checkout/bla.txt") + self.assertFalse(context.props_modified("checkout/bla.txt")) + + def test_conflicted(self): + context = wc.Context() + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + self.client_add("checkout/bla.txt") + self.assertEqual( + (False, False, False), + context.conflicted("checkout/bla.txt")) + + def test_crawl_revisions(self): + context = wc.Context() + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + self.client_add("checkout/bla.txt") + ret = [] + + class Reporter(object): + def set_path(self, *args): + ret.append(args) + + def finish(self): + pass + context.crawl_revisions("checkout", Reporter()) + + self.assertEqual(ret, [('', 0, 0, None, 3)]) + + def test_get_update_editor(self): + self.make_client("repos", "checkout") + context = wc.Context() + editor = context.get_update_editor("checkout", "") + editor.close() + + def test_status(self): + self.make_client("repos", "checkout") + context = wc.Context() + status = context.status("checkout") + self.assertEqual(NODE_DIR, status.kind) + + def test_walk_status(self): + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + self.client_add("checkout/bla.txt") + context = wc.Context() + result = {} + context.walk_status("checkout", result.__setitem__) + self.assertEqual( + set(result.keys()), + {os.path.abspath("checkout"), + os.path.abspath("checkout/bla.txt")}) + + def test_locking(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + self.client_add("checkout/bla.txt") + context = wc.Context() + lock = wc.Lock(token=b'foo') + self.assertEqual((False, False), context.locked("checkout")) + context.add_lock("checkout/", lock) + self.assertEqual((True, True), context.locked("checkout")) + context.remove_lock("checkout/", lock) + + def test_add_from_disk(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") + self.make_client("repos", "checkout") + with open('checkout/bla.txt', 'w') as f: + f.write("modified") + context = wc.Context() + lock = wc.Lock(token=b'foo') + lock.path = os.path.abspath('checkout')+"/" + context.add_lock("checkout", lock) + context.add_from_disk('checkout/bla.txt') + context.remove_lock("checkout", lock) + + def test_get_prop_diffs(self): + self.make_client("repos", "checkout") + context = wc.Context() + (orig_props, propdelta) = context.get_prop_diffs("checkout") + self.assertEqual({}, orig_props) + self.assertEqual([], propdelta) + + def test_process_committed_queue(self): + if wc.api_version() >= (1, 7): + self.skipTest("TODO: doesn't yet work with svn >= 1.7") + self.make_client("repos", "checkout") + adm = wc.Context() + self.build_tree({"checkout/bar": b"blala"}) + self.client_add('checkout/bar') + adm.add_lock("checkout", wc.Lock(token=b'foo')) + cq = wc.CommittedQueue() + cq.queue("checkout/bar", adm) + adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", + "jelmer") diff --git a/subvertpy/util.c b/subvertpy/util.c index 49c68899..5d679838 100644 --- a/subvertpy/util.c +++ b/subvertpy/util.c @@ -44,10 +44,21 @@ void PyErr_SetAprStatus(apr_status_t status) #if ONLY_BEFORE_SVN(1, 7) const char * -svn_uri_canonicalize(const char *uri, +_svn_uri_canonicalize(const char *uri, apr_pool_t *result_pool) { - return svn_path_canonicalize(uri, result_pool); + uri = svn_path_canonicalize(uri, result_pool); + + if (uri == NULL) { + return NULL; + } + + uri = svn_path_uri_decode(uri, result_pool); + if (uri == NULL) { + return NULL; + } + + return svn_path_uri_autoescape(uri, result_pool); } const char * @@ -57,85 +68,132 @@ svn_relpath_canonicalize(const char *relpath, return svn_path_canonicalize(relpath, result_pool); } +const char * +svn_dirent_canonicalize(const char *dirent, + apr_pool_t *result_pool) +{ + return svn_path_canonicalize(dirent, result_pool); +} + #endif -const char *py_object_to_svn_dirent(PyObject *obj, apr_pool_t *pool) +const char *py_object_to_svn_path_or_url(PyObject *obj, apr_pool_t *pool) { - const char *ret; - PyObject *bytes_obj = NULL; + const char *ret; + + if (PyUnicode_Check(obj)) { + obj = PyUnicode_AsUTF8String(obj); + if (obj == NULL) { + return NULL; + } + } else { + Py_INCREF(obj); + } - if (PyUnicode_Check(obj)) { - bytes_obj = obj = PyUnicode_AsUTF8String(obj); - if (obj == NULL) { - return NULL; - } - } + if (!PyBytes_Check(obj)) { + PyErr_SetString(PyExc_TypeError, + "URIs need to be UTF-8 bytestrings or unicode strings"); + Py_DECREF(obj); + return NULL; + } - if (PyBytes_Check(obj)) { -#if ONLY_SINCE_SVN(1, 7) - ret = svn_dirent_canonicalize(PyBytes_AsString(obj), pool); -#else - ret = svn_path_canonicalize(PyBytes_AsString(obj), pool); -#endif - Py_XDECREF(bytes_obj); - return ret; - } else { - PyErr_SetString(PyExc_TypeError, - "URIs need to be UTF-8 bytestrings or unicode strings"); - Py_XDECREF(bytes_obj); - return NULL; - } + ret = PyBytes_AsString(obj); + if (svn_path_is_url(ret)) { + ret = svn_uri_canonicalize(ret, pool); + } else { + ret = svn_dirent_canonicalize(ret, pool); + } + + Py_DECREF(obj); + return ret; } -char *py_object_to_svn_string(PyObject *obj, apr_pool_t *pool) +const char *py_object_to_svn_abspath(PyObject *obj, apr_pool_t *pool) { - char *ret; - PyObject *bytes_obj = NULL; + const char *ret; + + if (PyUnicode_Check(obj)) { + obj = PyUnicode_AsUTF8String(obj); + if (obj == NULL) { + return NULL; + } + } else { + Py_INCREF(obj); + } - if (PyUnicode_Check(obj)) { - bytes_obj = obj = PyUnicode_AsUTF8String(obj); - if (obj == NULL) { - return NULL; - } - } + if (!PyBytes_Check(obj)) { + PyErr_SetString(PyExc_TypeError, + "URIs need to be UTF-8 bytestrings or unicode strings"); + Py_DECREF(obj); + return NULL; + } - if (PyBytes_Check(obj)) { - ret = apr_pstrdup(pool, PyBytes_AsString(obj)); - Py_XDECREF(bytes_obj); - return ret; - } else { - PyErr_SetString(PyExc_TypeError, - "URIs need to be UTF-8 bytestrings or unicode strings"); - Py_XDECREF(bytes_obj); - return NULL; - } + ret = PyBytes_AsString(obj); + ret = apr_pstrdup(pool, ret); + Py_DECREF(obj); + if (ret == NULL) { + return NULL; + } + if (svn_dirent_is_absolute(ret)) { + return svn_dirent_canonicalize(ret, pool); + } else { + const char *absolute; + RUN_SVN(svn_dirent_get_absolute(&absolute, ret, pool)) + return svn_dirent_canonicalize(absolute, pool); + } } -const char *py_object_to_svn_uri(PyObject *obj, apr_pool_t *pool) +const char *py_object_to_svn_dirent(PyObject *obj, apr_pool_t *pool) { - const char *ret; - PyObject *bytes_obj = NULL; + const char *ret; + + if (PyUnicode_Check(obj)) { + obj = PyUnicode_AsUTF8String(obj); + if (obj == NULL) { + return NULL; + } + } else { + Py_INCREF(obj); + } - if (PyUnicode_Check(obj)) { - bytes_obj = obj = PyUnicode_AsUTF8String(obj); - if (obj == NULL) { - return NULL; - } - } + if (PyBytes_Check(obj)) { + ret = svn_dirent_canonicalize(PyBytes_AsString(obj), pool); + Py_DECREF(obj); + return ret; + } else { + PyErr_SetString(PyExc_TypeError, + "URIs need to be UTF-8 bytestrings or unicode strings"); + Py_DECREF(obj); + return NULL; + } +} - if (PyBytes_Check(obj)) { - ret = svn_uri_canonicalize(PyBytes_AsString(obj), pool); - Py_XDECREF(bytes_obj); - return ret; - } else { - PyErr_SetString(PyExc_TypeError, - "URIs need to be UTF-8 bytestrings or unicode strings"); - Py_XDECREF(bytes_obj); - return NULL; - } +char *py_object_to_svn_string(PyObject *obj, apr_pool_t *pool) +{ + char *ret; + + if (PyUnicode_Check(obj)) { + obj = PyUnicode_AsUTF8String(obj); + if (obj == NULL) { + return NULL; + } + } else { + Py_INCREF(obj); + } + + if (PyBytes_Check(obj)) { + ret = apr_pstrdup(pool, PyBytes_AsString(obj)); + Py_DECREF(obj); + return ret; + } else { + PyErr_SetString(PyExc_TypeError, + "URIs need to be UTF-8 bytestrings or unicode strings"); + Py_DECREF(obj); + return NULL; + } } -const char *py_object_to_svn_relpath(PyObject *obj, apr_pool_t *pool) +const char *py_object_to_svn_uri(PyObject *obj, apr_pool_t *pool) { const char *ret; @@ -145,72 +203,97 @@ const char *py_object_to_svn_relpath(PyObject *obj, apr_pool_t *pool) return NULL; } } else { - Py_INCREF(obj); - } + Py_INCREF(obj); + } if (PyBytes_Check(obj)) { - ret = svn_relpath_canonicalize(PyBytes_AsString(obj), pool); + ret = svn_uri_canonicalize(PyBytes_AsString(obj), pool); Py_DECREF(obj); return ret; } else { PyErr_SetString(PyExc_TypeError, - "relative paths need to be UTF-8 bytestrings or unicode strings"); + "URIs need to be UTF-8 bytestrings or unicode strings"); Py_DECREF(obj); return NULL; } } +const char *py_object_to_svn_relpath(PyObject *obj, apr_pool_t *pool) +{ + const char *ret; + + if (PyUnicode_Check(obj)) { + obj = PyUnicode_AsUTF8String(obj); + if (obj == NULL) { + return NULL; + } + } else { + Py_INCREF(obj); + } + + if (PyBytes_Check(obj)) { + ret = svn_relpath_canonicalize(PyBytes_AsString(obj), pool); + Py_DECREF(obj); + return ret; + } else { + PyErr_SetString(PyExc_TypeError, + "relative paths need to be UTF-8 bytestrings or unicode strings"); + Py_DECREF(obj); + return NULL; + } +} + apr_pool_t *Pool(apr_pool_t *parent) { - apr_status_t status; - apr_pool_t *ret; - ret = NULL; - status = apr_pool_create(&ret, parent); - if (status != 0) { - PyErr_SetAprStatus(status); - return NULL; - } - return ret; + apr_status_t status; + apr_pool_t *ret; + ret = NULL; + status = apr_pool_create(&ret, parent); + if (status != 0) { + PyErr_SetAprStatus(status); + return NULL; + } + return ret; } PyTypeObject *PyErr_GetSubversionExceptionTypeObject(void) { - PyObject *coremod, *excobj; - coremod = PyImport_ImportModule("subvertpy"); + PyObject *coremod, *excobj; + coremod = PyImport_ImportModule("subvertpy"); - if (coremod == NULL) { - return NULL; - } + if (coremod == NULL) { + return NULL; + } - excobj = PyObject_GetAttrString(coremod, "SubversionException"); - Py_DECREF(coremod); + excobj = PyObject_GetAttrString(coremod, "SubversionException"); + Py_DECREF(coremod); - if (excobj == NULL) { - PyErr_BadInternalCall(); - return NULL; - } + if (excobj == NULL) { + PyErr_BadInternalCall(); + return NULL; + } - return (PyTypeObject *)excobj; + return (PyTypeObject *)excobj; } PyTypeObject *PyErr_GetGaiExceptionTypeObject(void) { - PyObject *socketmod, *excobj; - socketmod = PyImport_ImportModule("socket"); + PyObject *socketmod, *excobj; + socketmod = PyImport_ImportModule("socket"); - if (socketmod == NULL) { - return NULL; - } + if (socketmod == NULL) { + return NULL; + } - excobj = PyObject_GetAttrString(socketmod, "gaierror"); - Py_DECREF(socketmod); + excobj = PyObject_GetAttrString(socketmod, "gaierror"); + Py_DECREF(socketmod); - if (excobj == NULL) { - PyErr_BadInternalCall(); - return NULL; - } + if (excobj == NULL) { + PyErr_BadInternalCall(); + return NULL; + } - return (PyTypeObject *)excobj; + return (PyTypeObject *)excobj; } PyObject *PyErr_NewSubversionException(svn_error_t *error) @@ -229,9 +312,7 @@ PyObject *PyErr_NewSubversionException(svn_error_t *error) if (error->child != NULL) { PyTypeObject *cls = PyErr_GetSubversionExceptionTypeObject(); PyObject *args = PyErr_NewSubversionException(error->child); - child = cls->tp_new(cls, args, NULL); - if (cls->tp_init != NULL) - cls->tp_init(child, args, NULL); + child = PyObject_CallObject((PyObject *)cls, args); Py_DECREF(cls); Py_DECREF(args); } else { @@ -240,7 +321,7 @@ PyObject *PyErr_NewSubversionException(svn_error_t *error) } #if ONLY_SINCE_SVN(1, 4) - message = svn_err_best_message(error, buf, sizeof(buf)); + message = svn_err_best_message(error, buf, sizeof(buf)-1); #else message = error->message; #endif @@ -436,14 +517,12 @@ PyObject *prop_hash_to_dict(apr_hash_t *props) py_key = Py_None; Py_INCREF(py_key); } else { - py_key = PyUnicode_FromString(key); + py_key = PyUnicode_FromStringAndSize(key, klen); } if (PyDict_SetItem(py_props, py_key, py_val) != 0) { Py_DECREF(py_key); Py_DECREF(py_val); - Py_DECREF(py_props); - apr_pool_destroy(pool); - return NULL; + goto fail_item; } Py_DECREF(py_key); Py_DECREF(py_val); @@ -478,17 +557,11 @@ apr_hash_t *prop_dict_to_hash(apr_pool_t *pool, PyObject *py_props) } while (PyDict_Next(py_props, &idx, &k, &v)) { - char *key; - if (PyUnicode_Check(k)) { - k = PyUnicode_AsUTF8String(k); - } else { - Py_INCREF(k); - } + char *key, *val; + Py_ssize_t val_size; - if (!PyBytes_Check(k)) { - PyErr_SetString(PyExc_TypeError, - "property name should be unicode or byte string"); - Py_DECREF(k); + key = py_object_to_svn_string(k, pool); + if (key == NULL) { return NULL; } @@ -498,39 +571,26 @@ apr_hash_t *prop_dict_to_hash(apr_pool_t *pool, PyObject *py_props) Py_INCREF(v); } - if (!PyBytes_Check(v)) { - PyErr_SetString(PyExc_TypeError, - "property value should be unicode or byte string"); - Py_DECREF(k); - Py_DECREF(v); + if (PyBytes_AsStringAndSize(v, &val, &val_size) == -1) { return NULL; } - key = apr_pmemdup(pool, PyBytes_AsString(k), PyBytes_Size(k)); - if (key == NULL) { - PyErr_SetString(PyExc_TypeError, - "property value should be unicode or byte string"); - Py_DECREF(k); - Py_DECREF(v); - return NULL; - } + val_string = svn_string_ncreate(val, val_size, pool); - val_string = svn_string_ncreate(PyBytes_AsString(v), - PyBytes_Size(v), pool); - apr_hash_set(hash_props, key, PyBytes_Size(k), val_string); - Py_DECREF(k); Py_DECREF(v); + + apr_hash_set(hash_props, key, strlen(key), val_string); } return hash_props; } #if PY_MAJOR_VERSION >= 3 -#define SOURCEPATH_FORMAT3 "(Czl)" -#define SOURCEPATH_FORMAT4 "(Czli)" +#define SOURCEPATH_FORMAT3 "(CNl)" +#define SOURCEPATH_FORMAT4 "(CNli)" #else -#define SOURCEPATH_FORMAT3 "(czl)" -#define SOURCEPATH_FORMAT4 "(czli)" +#define SOURCEPATH_FORMAT3 "(cNl)" +#define SOURCEPATH_FORMAT4 "(cNli)" #endif PyObject *pyify_changed_paths(apr_hash_t *changed_paths, bool node_kind, apr_pool_t *pool) @@ -551,13 +611,21 @@ PyObject *pyify_changed_paths(apr_hash_t *changed_paths, bool node_kind, apr_poo } for (idx = apr_hash_first(pool, changed_paths); idx != NULL; idx = apr_hash_next(idx)) { + PyObject *py_copyfrom_path, *py_key; + apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); + if (val->copyfrom_path == NULL) { + py_copyfrom_path = Py_None; + Py_INCREF(Py_None); + } else { + py_copyfrom_path = PyUnicode_FromString(val->copyfrom_path); + } if (node_kind) { - pyval = Py_BuildValue(SOURCEPATH_FORMAT4, val->action, val->copyfrom_path, + pyval = Py_BuildValue(SOURCEPATH_FORMAT4, val->action, py_copyfrom_path, val->copyfrom_rev, svn_node_unknown); } else { - pyval = Py_BuildValue(SOURCEPATH_FORMAT3, val->action, val->copyfrom_path, + pyval = Py_BuildValue(SOURCEPATH_FORMAT3, val->action, py_copyfrom_path, val->copyfrom_rev); } if (pyval == NULL) { @@ -570,11 +638,21 @@ PyObject *pyify_changed_paths(apr_hash_t *changed_paths, bool node_kind, apr_poo Py_DECREF(py_changed_paths); return NULL; } - if (PyDict_SetItemString(py_changed_paths, key, pyval) != 0) { + + py_key = PyUnicode_FromString(key); + if (py_key == NULL) { + Py_DECREF(pyval); + Py_DECREF(py_changed_paths); + return NULL; + } + + if (PyDict_SetItem(py_changed_paths, py_key, pyval) != 0) { Py_DECREF(py_changed_paths); + Py_DECREF(py_key); Py_DECREF(pyval); return NULL; } + Py_DECREF(py_key); Py_DECREF(pyval); } } @@ -601,8 +679,15 @@ PyObject *pyify_changed_paths2(apr_hash_t *changed_paths, apr_pool_t *pool) } for (idx = apr_hash_first(pool, changed_paths); idx != NULL; idx = apr_hash_next(idx)) { + PyObject *py_key, *py_copyfrom_path; apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); - pyval = Py_BuildValue(SOURCEPATH_FORMAT4, val->action, val->copyfrom_path, + if (val->copyfrom_path == NULL) { + py_copyfrom_path = Py_None; + Py_INCREF(Py_None); + } else { + py_copyfrom_path = PyUnicode_FromString(val->copyfrom_path); + } + pyval = Py_BuildValue(SOURCEPATH_FORMAT4, val->action, py_copyfrom_path, val->copyfrom_rev, val->node_kind); if (pyval == NULL) { Py_DECREF(py_changed_paths); @@ -614,11 +699,20 @@ PyObject *pyify_changed_paths2(apr_hash_t *changed_paths, apr_pool_t *pool) Py_DECREF(pyval); return NULL; } - if (PyDict_SetItemString(py_changed_paths, key, pyval) != 0) { + py_key = PyUnicode_FromString(key); + if (py_key == NULL) { + Py_DECREF(py_changed_paths); Py_DECREF(pyval); + return NULL; + } + + if (PyDict_SetItem(py_changed_paths, py_key, pyval) != 0) { + Py_DECREF(pyval); + Py_DECREF(py_key); Py_DECREF(py_changed_paths); return NULL; } + Py_DECREF(py_key); Py_DECREF(pyval); } } @@ -840,6 +934,7 @@ static apr_hash_t *get_default_config(void) pool = Pool(NULL); RUN_SVN_WITH_POOL(pool, svn_config_get_config(&default_config, NULL, pool)); + /* TODO(jelmer): Deal with pool */ initialised = true; } @@ -1068,3 +1163,70 @@ PyTypeObject Stream_Type = { .tp_new = stream_init, /* tp_new tp_new */ }; + +PyObject *dirent_hash_to_dict(apr_hash_t *dirents, unsigned int dirent_fields, apr_pool_t *temp_pool) +{ + svn_dirent_t *dirent; + apr_ssize_t klen; + const char *key; + apr_hash_index_t *idx; + PyObject *py_dirents = PyDict_New(); + + if (py_dirents == NULL) { + return NULL; + } + idx = apr_hash_first(temp_pool, dirents); + while (idx != NULL) { + PyObject *item, *pykey; + apr_hash_this(idx, (const void **)&key, &klen, (void **)&dirent); + item = py_dirent(dirent, dirent_fields); + if (item == NULL) { + Py_DECREF(py_dirents); + return NULL; + } + if (key == NULL) { + pykey = Py_None; + Py_INCREF(pykey); + } else { + pykey = PyUnicode_FromStringAndSize(key, klen); + } + if (PyDict_SetItem(py_dirents, pykey, item) != 0) { + Py_DECREF(item); + Py_DECREF(pykey); + Py_DECREF(py_dirents); + return NULL; + } + Py_DECREF(pykey); + Py_DECREF(item); + idx = apr_hash_next(idx); + } + return py_dirents; +} + +PyObject *propchanges_to_list(const apr_array_header_t *propchanges) +{ + int i; + svn_prop_t el; + PyObject *py_propchanges = PyList_New(propchanges->nelts); + PyObject *pyval; + if (py_propchanges == NULL) { + return NULL; + } + for (i = 0; i < propchanges->nelts; i++) { + el = APR_ARRAY_IDX(propchanges, i, svn_prop_t); + if (el.value != NULL) + pyval = Py_BuildValue("(sz#)", el.name, el.value->data, el.value->len); + else + pyval = Py_BuildValue("(sO)", el.name, Py_None); + if (pyval == NULL) { + Py_DECREF(py_propchanges); + return NULL; + } + if (PyList_SetItem(py_propchanges, i, pyval) != 0) { + Py_DECREF(py_propchanges); + return NULL; + } + } + + return py_propchanges; +} diff --git a/subvertpy/util.h b/subvertpy/util.h index bbbcf9b3..3ed0545b 100644 --- a/subvertpy/util.h +++ b/subvertpy/util.h @@ -21,6 +21,7 @@ #define _SUBVERTPY_UTIL_H_ #include <svn_version.h> +#include <svn_io.h> /* for svn_stream_t */ #if SVN_VER_MAJOR != 1 #error "only svn 1.x is supported" @@ -48,7 +49,7 @@ apr_hash_t *prop_dict_to_hash(apr_pool_t *pool, PyObject *py_props); svn_error_t *py_svn_log_wrapper( void *baton, apr_hash_t *changed_paths, long revision, const char *author, const char *date, const char *message, apr_pool_t *pool); -svn_error_t *py_svn_error(void); +__attribute__((warn_unused_result)) svn_error_t *py_svn_error(void); void PyErr_SetSubversionException(svn_error_t *error); PyTypeObject *PyErr_GetSubversionExceptionTypeObject(void); @@ -86,6 +87,7 @@ PyObject *PyErr_NewSubversionException(svn_error_t *error); apr_hash_t *config_hash_from_object(PyObject *config, apr_pool_t *pool); void PyErr_SetAprStatus(apr_status_t status); PyObject *py_dirent(const svn_dirent_t *dirent, int dirent_fields); +PyObject *dirent_hash_to_dict(apr_hash_t *dirents, unsigned int dirent_fields, apr_pool_t *temp_pool); PyObject *PyOS_tmpfile(void); PyObject *pyify_changed_paths(apr_hash_t *changed_paths, bool node_kind, apr_pool_t *pool); bool pyify_log_message( @@ -139,18 +141,28 @@ extern PyTypeObject Stream_Type; #if ONLY_BEFORE_SVN(1, 7) const char * -svn_uri_canonicalize(const char *uri, +_svn_uri_canonicalize(const char *uri, apr_pool_t *result_pool); const char * svn_relpath_canonicalize(const char *relpath, apr_pool_t *result_pool); + +const char * +svn_dirent_canonicalize(const char *dirent, + apr_pool_t *result_pool); +#define svn_uri_canonicalize _svn_uri_canonicalize +#define svn_dirent_get_absolute svn_path_get_absolute +#define svn_dirent_is_absolute svn_path_is_url #endif const char *py_object_to_svn_uri(PyObject *obj, apr_pool_t *pool); const char *py_object_to_svn_dirent(PyObject *obj, apr_pool_t *pool); const char *py_object_to_svn_relpath(PyObject *obj, apr_pool_t *pool); +const char *py_object_to_svn_path_or_url(PyObject *obj, apr_pool_t *pool); char *py_object_to_svn_string(PyObject *obj, apr_pool_t *pool); -#define py_object_from_svn_abspath PyBytes_FromString +const char *py_object_to_svn_abspath(PyObject *obj, apr_pool_t *pool); +#define py_object_from_svn_abspath PyUnicode_FromString +PyObject *propchanges_to_list(const apr_array_header_t *propchanges); #if PY_MAJOR_VERSION >= 3 #define PyRepr_FromFormat PyUnicode_FromFormat diff --git a/subvertpy/wc.c b/subvertpy/wc.c index 7a635b25..4b6350c4 100644 --- a/subvertpy/wc.c +++ b/subvertpy/wc.c @@ -25,6 +25,7 @@ #include <stdbool.h> #include <apr_md5.h> #include <apr_sha1.h> +#include <fcntl.h> #include "util.h" #include "editor.h" @@ -34,59 +35,51 @@ #define T_BOOL T_BYTE #endif -#if ONLY_SINCE_SVN(1, 5) -#define REPORTER_T svn_ra_reporter3_t -#else -#define REPORTER_T svn_ra_reporter2_t -#endif - -static PyTypeObject Entry_Type; -static PyTypeObject Status_Type; -static PyTypeObject Adm_Type; +typedef struct { + PyObject_HEAD + svn_lock_t lock; + apr_pool_t *pool; +} LockObject; -static PyObject *py_entry(const svn_wc_entry_t *entry); -static PyObject *py_status(const svn_wc_status2_t *status); +#if ONLY_SINCE_SVN(1, 7) +typedef struct { + PyObject_VAR_HEAD + apr_pool_t *pool; + svn_wc_context_t *context; +} ContextObject; +static PyTypeObject Context_Type; +#endif #if ONLY_BEFORE_SVN(1, 5) struct svn_wc_committed_queue_t { - apr_pool_t *pool; - apr_array_header_t *queue; - svn_boolean_t have_recursive; + apr_pool_t *pool; + apr_array_header_t *queue; + svn_boolean_t have_recursive; }; -#if SVN_VER_MINOR < 5 -typedef struct svn_wc_committed_queue_t svn_wc_committed_queue_t; -#endif - typedef struct { - const char *path; - svn_wc_adm_access_t *adm_access; - svn_boolean_t recurse; - svn_boolean_t remove_lock; - apr_array_header_t *wcprop_changes; - unsigned char *digest; + const char *path; + svn_wc_adm_access_t *adm_access; + svn_boolean_t recurse; + svn_boolean_t remove_lock; + apr_array_header_t *wcprop_changes; + unsigned char *digest; } committed_queue_item_t; -#if SVN_VER_MINOR < 5 -static -#endif svn_wc_committed_queue_t *svn_wc_committed_queue_create(apr_pool_t *pool) { - svn_wc_committed_queue_t *q; + svn_wc_committed_queue_t *q; - q = apr_palloc(pool, sizeof(*q)); - q->pool = pool; - q->queue = apr_array_make(pool, 1, sizeof(committed_queue_item_t *)); - q->have_recursive = FALSE; + q = apr_palloc(pool, sizeof(*q)); + q->pool = pool; + q->queue = apr_array_make(pool, 1, sizeof(committed_queue_item_t *)); + q->have_recursive = FALSE; - return q; + return q; } -#if SVN_VER_MINOR < 5 -static -#endif svn_error_t *svn_wc_queue_committed(svn_wc_committed_queue_t **queue, const char *path, svn_wc_adm_access_t *adm_access, @@ -122,129 +115,159 @@ svn_error_t *svn_wc_queue_committed(svn_wc_committed_queue_t **queue, #endif typedef struct { - PyObject_VAR_HEAD - apr_pool_t *pool; - svn_wc_committed_queue_t *queue; + PyObject_VAR_HEAD + apr_pool_t *pool; + svn_wc_committed_queue_t *queue; } CommittedQueueObject; -static PyTypeObject CommittedQueue_Type; - -#if ONLY_SINCE_SVN(1, 5) -static svn_error_t *py_ra_report_set_path(void *baton, const char *path, svn_revnum_t revision, svn_depth_t depth, int start_empty, const char *lock_token, apr_pool_t *pool) -{ - PyObject *self = (PyObject *)baton, *py_lock_token, *ret; - PyGILState_STATE state = PyGILState_Ensure(); - if (lock_token == NULL) { - py_lock_token = Py_None; - Py_INCREF(py_lock_token); - } else { - py_lock_token = PyBytes_FromString(lock_token); - } - ret = PyObject_CallMethod(self, "set_path", "slbOi", path, revision, start_empty, py_lock_token, depth); - Py_DECREF(py_lock_token); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; -} - -static svn_error_t *py_ra_report_link_path(void *report_baton, const char *path, const char *url, svn_revnum_t revision, svn_depth_t depth, int start_empty, const char *lock_token, apr_pool_t *pool) -{ - PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token; - PyGILState_STATE state = PyGILState_Ensure(); - if (lock_token == NULL) { - py_lock_token = Py_None; - Py_INCREF(py_lock_token); - } else { - py_lock_token = PyBytes_FromString(lock_token); - } - ret = PyObject_CallMethod(self, "link_path", "sslbOi", path, url, revision, start_empty, py_lock_token, depth); - Py_DECREF(py_lock_token); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; -} - -#else -static svn_error_t *py_ra_report_set_path(void *baton, const char *path, svn_revnum_t revision, int start_empty, const char *lock_token, apr_pool_t *pool) +svn_wc_committed_queue_t *PyObject_GetCommittedQueue(PyObject *obj) { - PyObject *self = (PyObject *)baton, *py_lock_token, *ret; - PyGILState_STATE state = PyGILState_Ensure(); - if (lock_token == NULL) { - py_lock_token = Py_None; - Py_INCREF(py_lock_token); - } else { - py_lock_token = PyBytes_FromString(lock_token); - } - ret = PyObject_CallMethod(self, "set_path", "slbOi", path, revision, start_empty, py_lock_token, svn_depth_infinity); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; + return ((CommittedQueueObject *)obj)->queue; } -static svn_error_t *py_ra_report_link_path(void *report_baton, const char *path, const char *url, svn_revnum_t revision, int start_empty, const char *lock_token, apr_pool_t *pool) -{ - PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token; - PyGILState_STATE state = PyGILState_Ensure(); - if (lock_token == NULL) { - py_lock_token = Py_None; - Py_INCREF(py_lock_token); - } else { - py_lock_token = PyBytes_FromString(lock_token); - } - ret = PyObject_CallMethod(self, "link_path", "sslbOi", path, url, revision, start_empty, py_lock_token, svn_depth_infinity); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; +#if ONLY_SINCE_SVN(1, 5) +static svn_error_t *py_ra_report3_set_path(void *baton, const char *path, + svn_revnum_t revision, + svn_depth_t depth, int start_empty, + const char *lock_token, apr_pool_t *pool) +{ + PyObject *self = (PyObject *)baton, *py_lock_token, *ret; + PyGILState_STATE state = PyGILState_Ensure(); + if (lock_token == NULL) { + py_lock_token = Py_None; + Py_INCREF(py_lock_token); + } else { + py_lock_token = PyBytes_FromString(lock_token); + } + ret = PyObject_CallMethod(self, "set_path", "slbOi", path, revision, + start_empty, py_lock_token, depth); + Py_DECREF(py_lock_token); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; +} + +static svn_error_t *py_ra_report3_link_path(void *report_baton, + const char *path, const char *url, + svn_revnum_t revision, + svn_depth_t depth, int start_empty, + const char *lock_token, apr_pool_t *pool) +{ + PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token; + PyGILState_STATE state = PyGILState_Ensure(); + if (lock_token == NULL) { + py_lock_token = Py_None; + Py_INCREF(py_lock_token); + } else { + py_lock_token = PyBytes_FromString(lock_token); + } + ret = PyObject_CallMethod(self, "link_path", "sslbOi", path, url, revision, + start_empty, py_lock_token, depth); + Py_DECREF(py_lock_token); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; } - #endif -static svn_error_t *py_ra_report_delete_path(void *baton, const char *path, apr_pool_t *pool) -{ - PyObject *self = (PyObject *)baton, *ret; - PyGILState_STATE state = PyGILState_Ensure(); - ret = PyObject_CallMethod(self, "delete_path", "s", path); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; +static svn_error_t *py_ra_report2_set_path(void *baton, const char *path, + svn_revnum_t revision, + int start_empty, const char *lock_token, + apr_pool_t *pool) +{ + PyObject *self = (PyObject *)baton, *py_lock_token, *ret; + PyGILState_STATE state = PyGILState_Ensure(); + if (lock_token == NULL) { + py_lock_token = Py_None; + Py_INCREF(py_lock_token); + } else { + py_lock_token = PyBytes_FromString(lock_token); + } + ret = PyObject_CallMethod(self, "set_path", "slbOi", path, revision, + start_empty, py_lock_token, svn_depth_infinity); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; +} + +static svn_error_t *py_ra_report2_link_path(void *report_baton, + const char *path, const char *url, + svn_revnum_t revision, + int start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token; + PyGILState_STATE state = PyGILState_Ensure(); + if (lock_token == NULL) { + py_lock_token = Py_None; + Py_INCREF(py_lock_token); + } else { + py_lock_token = PyBytes_FromString(lock_token); + } + ret = PyObject_CallMethod(self, "link_path", "sslbOi", path, url, revision, + start_empty, py_lock_token, svn_depth_infinity); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; +} + +static svn_error_t *py_ra_report_delete_path(void *baton, const char *path, + apr_pool_t *pool) +{ + PyObject *self = (PyObject *)baton, *ret; + PyGILState_STATE state = PyGILState_Ensure(); + ret = PyObject_CallMethod(self, "delete_path", "s", path); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; } static svn_error_t *py_ra_report_finish(void *baton, apr_pool_t *pool) { - PyObject *self = (PyObject *)baton, *ret; - PyGILState_STATE state = PyGILState_Ensure(); - ret = PyObject_CallMethod(self, "finish", ""); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; + PyObject *self = (PyObject *)baton, *ret; + PyGILState_STATE state = PyGILState_Ensure(); + ret = PyObject_CallMethod(self, "finish", ""); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; } static svn_error_t *py_ra_report_abort(void *baton, apr_pool_t *pool) { - PyObject *self = (PyObject *)baton, *ret; - PyGILState_STATE state = PyGILState_Ensure(); - ret = PyObject_CallMethod(self, "abort", ""); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; + PyObject *self = (PyObject *)baton, *ret; + PyGILState_STATE state = PyGILState_Ensure(); + ret = PyObject_CallMethod(self, "abort", ""); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; } -static const REPORTER_T py_ra_reporter = { - py_ra_report_set_path, - py_ra_report_delete_path, - py_ra_report_link_path, - py_ra_report_finish, - py_ra_report_abort, +#if ONLY_SINCE_SVN(1, 5) +const svn_ra_reporter3_t py_ra_reporter3 = { + py_ra_report3_set_path, + py_ra_report_delete_path, + py_ra_report3_link_path, + py_ra_report_finish, + py_ra_report_abort, }; +#endif +const svn_ra_reporter2_t py_ra_reporter2 = { + py_ra_report2_set_path, + py_ra_report_delete_path, + py_ra_report2_link_path, + py_ra_report_finish, + py_ra_report_abort, +}; /** @@ -273,57 +296,6 @@ static PyObject *api_version(PyObject *self) ver->patch, ver->tag); } -static svn_error_t *py_wc_found_entry(const char *path, const svn_wc_entry_t *entry, void *walk_baton, apr_pool_t *pool) -{ - PyObject *fn, *ret; - PyObject *callbacks = (PyObject *)walk_baton; - PyGILState_STATE state = PyGILState_Ensure(); - if (PyTuple_Check(callbacks)) { - fn = PyTuple_GET_ITEM(callbacks, 0); - } else { - fn = (PyObject *)walk_baton; - } - ret = PyObject_CallFunction(fn, "sO", path, py_entry(entry)); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - return NULL; -} - -#if ONLY_SINCE_SVN(1, 5) - -svn_error_t *py_wc_handle_error(const char *path, svn_error_t *err, void *walk_baton, apr_pool_t *pool) -{ - PyObject *fn, *ret; - PyObject *py_err; - PyGILState_STATE state; - PyObject *callbacks = (PyObject *)walk_baton; - if (PyTuple_Check(callbacks)) { - fn = PyTuple_GET_ITEM(callbacks, 1); - } else { - return err; - } - state = PyGILState_Ensure(); - py_err = PyErr_NewSubversionException(err); - ret = PyObject_CallFunction(fn, "sO", path, py_err); - CB_CHECK_PYRETVAL(ret); - Py_DECREF(ret); - PyGILState_Release(state); - Py_DECREF(py_err); - return NULL; -} - -static svn_wc_entry_callbacks2_t py_wc_entry_callbacks2 = { - py_wc_found_entry, - py_wc_handle_error, -}; -#else -static svn_wc_entry_callbacks_t py_wc_entry_callbacks = { - py_wc_found_entry -}; - -#endif - void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool) { @@ -332,877 +304,16 @@ void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *p return; if (notify->err != NULL) { + PyGILState_STATE state = PyGILState_Ensure(); PyObject *excval = PyErr_NewSubversionException(notify->err); ret = PyObject_CallFunction(func, "O", excval); Py_DECREF(excval); Py_XDECREF(ret); /* If ret was NULL, the cancel func should abort the operation. */ + PyGILState_Release(state); } } - -typedef struct { - PyObject_VAR_HEAD - apr_pool_t *pool; - svn_wc_entry_t entry; -} EntryObject; - -static void entry_dealloc(PyObject *self) -{ - apr_pool_destroy(((EntryObject *)self)->pool); - PyObject_Del(self); -} - -static PyMemberDef entry_members[] = { - { "name", T_STRING, offsetof(EntryObject, entry.name), READONLY, - "Name of the file"}, - { "copyfrom_url", T_STRING, offsetof(EntryObject, entry.copyfrom_url), READONLY, - "Copyfrom location" }, - { "copyfrom_rev", T_LONG, offsetof(EntryObject, entry.copyfrom_rev), READONLY, - "Copyfrom revision" }, - { "uuid", T_STRING, offsetof(EntryObject, entry.uuid), READONLY, - "UUID of repository" }, - { "url", T_STRING, offsetof(EntryObject, entry.url), READONLY, - "URL in repository" }, - { "repos", T_STRING, offsetof(EntryObject, entry.repos), READONLY, - "Canonical repository URL" }, - { "schedule", T_INT, offsetof(EntryObject, entry.schedule), READONLY, - "Scheduling (add, replace, delete, etc)" }, - { "kind", T_INT, offsetof(EntryObject, entry.kind), READONLY, - "Kind of file (file, dir, etc)" }, - { "revision", T_LONG, offsetof(EntryObject, entry.revision), READONLY, - "Base revision", }, - { "cmt_rev", T_LONG, offsetof(EntryObject, entry.cmt_rev), READONLY, - "Last revision this was changed" }, - { "checksum", T_STRING, offsetof(EntryObject, entry.checksum), READONLY, - "Hex MD5 checksum for the untranslated text base file" }, - { "cmt_date", T_LONG, offsetof(EntryObject, entry.cmt_date), READONLY, - "Last date this was changed" }, - { "cmt_author", T_STRING, offsetof(EntryObject, entry.cmt_author), READONLY, - "Last commit author of this item" }, - { NULL, } -}; - -static PyTypeObject Entry_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "wc.Entry", /* const char *tp_name; For printing, in format "<module>.<name>" */ - sizeof(EntryObject), - 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ - - /* Methods to implement standard operations */ - - entry_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ - NULL, /* getattrfunc tp_getattr; */ - NULL, /* setattrfunc tp_setattr; */ - NULL, /* cmpfunc tp_compare; */ - NULL, /* reprfunc tp_repr; */ - - /* Method suites for standard classes */ - - NULL, /* PyNumberMethods *tp_as_number; */ - NULL, /* PySequenceMethods *tp_as_sequence; */ - NULL, /* PyMappingMethods *tp_as_mapping; */ - - /* More standard operations (here for binary compatibility) */ - - NULL, /* hashfunc tp_hash; */ - NULL, /* ternaryfunc tp_call; */ - NULL, /* reprfunc tp_str; */ - NULL, /* getattrofunc tp_getattro; */ - NULL, /* setattrofunc tp_setattro; */ - - /* Functions to access object as input/output buffer */ - NULL, /* PyBufferProcs *tp_as_buffer; */ - - /* Flags to define presence of optional/expanded features */ - 0, /* long tp_flags; */ - - NULL, /* const char *tp_doc; Documentation string */ - - /* Assigned meaning in release 2.0 */ - /* call function for all accessible objects */ - NULL, /* traverseproc tp_traverse; */ - - /* delete references to contained objects */ - NULL, /* inquiry tp_clear; */ - - /* Assigned meaning in release 2.1 */ - /* rich comparisons */ - NULL, /* richcmpfunc tp_richcompare; */ - - /* weak reference enabler */ - 0, /* Py_ssize_t tp_weaklistoffset; */ - - /* Added in release 2.2 */ - /* Iterators */ - NULL, /* getiterfunc tp_iter; */ - NULL, /* iternextfunc tp_iternext; */ - - /* Attribute descriptor and subclassing stuff */ - NULL, /* struct PyMethodDef *tp_methods; */ - entry_members, /* struct PyMemberDef *tp_members; */ - -}; - -static PyObject *py_entry(const svn_wc_entry_t *entry) -{ - EntryObject *ret; - - ret = PyObject_New(EntryObject, &Entry_Type); - if (ret == NULL) - return NULL; - - ret->pool = Pool(NULL); - if (ret->pool == NULL) - return NULL; - ret->entry = *svn_wc_entry_dup(entry, ret->pool); - return (PyObject *)ret; -} - -typedef struct { - PyObject_VAR_HEAD - apr_pool_t *pool; - svn_wc_status2_t status; - PyObject *entry; -} StatusObject; - -static void status_dealloc(PyObject *self) -{ - apr_pool_destroy(((StatusObject *)self)->pool); - Py_XDECREF(((StatusObject *)self)->entry); - PyObject_Del(self); -} - -static PyMemberDef status_members[] = { - { "entry", T_OBJECT, offsetof(StatusObject, entry), READONLY, - "Can be NULL if not under version control." }, - { "locked", T_BOOL, offsetof(StatusObject, status.locked), READONLY, - "a directory can be 'locked' if a working copy update was interrupted." }, - { "copied", T_BOOL, offsetof(StatusObject, status.copied), READONLY, - "a file or directory can be 'copied' if it's scheduled for addition-with-history (or part of a subtree that is scheduled as such.)." }, - { "switched", T_BOOL, offsetof(StatusObject, status.switched), READONLY, - "a file or directory can be 'switched' if the switch command has been used." }, - { "url", T_STRING, offsetof(StatusObject, status.url), READONLY, - "URL (actual or expected) in repository" }, - { "revision", T_LONG, offsetof(StatusObject, status.ood_last_cmt_rev), READONLY, - "Set to the youngest committed revision, or SVN_INVALID_REVNUM if not out of date.", }, - { "kind", T_INT, offsetof(StatusObject, status.ood_kind), READONLY, - "Set to the node kind of the youngest commit, or svn_node_none if not out of date.", }, - { "status", T_INT, offsetof(StatusObject, status.text_status), READONLY, - "The status of the entry.", }, - { NULL, } -}; - -static PyTypeObject Status_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "wc.Status", /* const char *tp_name; For printing, in format "<module>.<name>" */ - sizeof(StatusObject), - 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ - - /* Methods to implement standard operations */ - - status_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ - NULL, /* getattrfunc tp_getattr; */ - NULL, /* setattrfunc tp_setattr; */ - NULL, /* cmpfunc tp_compare; */ - NULL, /* reprfunc tp_repr; */ - - /* Method suites for standard classes */ - - NULL, /* PyNumberMethods *tp_as_number; */ - NULL, /* PySequenceMethods *tp_as_sequence; */ - NULL, /* PyMappingMethods *tp_as_mapping; */ - - /* More standard operations (here for binary compatibility) */ - - NULL, /* hashfunc tp_hash; */ - NULL, /* ternaryfunc tp_call; */ - NULL, /* reprfunc tp_str; */ - NULL, /* getattrofunc tp_getattro; */ - NULL, /* setattrofunc tp_setattro; */ - - /* Functions to access object as input/output buffer */ - NULL, /* PyBufferProcs *tp_as_buffer; */ - - /* Flags to define presence of optional/expanded features */ - 0, /* long tp_flags; */ - - "Working copy status object", /* const char *tp_doc; Documentation string */ - - /* Assigned meaning in release 2.0 */ - /* call function for all accessible objects */ - NULL, /* traverseproc tp_traverse; */ - - /* delete references to contained objects */ - NULL, /* inquiry tp_clear; */ - - /* Assigned meaning in release 2.1 */ - /* rich comparisons */ - NULL, /* richcmpfunc tp_richcompare; */ - - /* weak reference enabler */ - 0, /* Py_ssize_t tp_weaklistoffset; */ - - /* Added in release 2.2 */ - /* Iterators */ - NULL, /* getiterfunc tp_iter; */ - NULL, /* iternextfunc tp_iternext; */ - - /* Attribute descriptor and subclassing stuff */ - NULL, /* struct PyMethodDef *tp_methods; */ - status_members, /* struct PyMemberDef *tp_members; */ - -}; - -static PyObject *py_status(const svn_wc_status2_t *status) -{ - StatusObject *ret; - svn_wc_status2_t *dup_status; - - ret = PyObject_New(StatusObject, &Status_Type); - if (ret == NULL) - return NULL; - - ret->pool = Pool(NULL); - if (ret->pool == NULL) { - PyObject_Del(ret); - return NULL; - } - - dup_status = svn_wc_dup_status2(status, ret->pool); - if (dup_status == NULL) - { - PyErr_NoMemory(); - return NULL; - } - ret->status = *dup_status; - - ret->entry = py_entry(ret->status.entry); - return (PyObject *)ret; -} - -typedef struct { - PyObject_VAR_HEAD - svn_wc_adm_access_t *adm; - apr_pool_t *pool; -} AdmObject; - -#define ADM_CHECK_CLOSED(adm_obj) \ - if (adm_obj->adm == NULL) { \ - PyErr_SetString(PyExc_RuntimeError, "WorkingCopy instance already closed"); \ - return NULL; \ - } - -static PyObject *adm_init(PyTypeObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *associated, *py_path; - const char *path; - bool write_lock=false; - int depth=0; - svn_wc_adm_access_t *parent_wc; - svn_error_t *err; - AdmObject *ret; - char *kwnames[] = { "associated", "path", "write_lock", "depth", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bi", kwnames, - &associated, &py_path, &write_lock, &depth)) - return NULL; - - ret = PyObject_New(AdmObject, &Adm_Type); - if (ret == NULL) - return NULL; - - ret->pool = Pool(NULL); - if (ret->pool == NULL) { - return NULL; - } - if (associated == Py_None) { - parent_wc = NULL; - } else { - parent_wc = ((AdmObject *)associated)->adm; - } - - path = py_object_to_svn_dirent(py_path, ret->pool); - if (path == NULL) { - Py_DECREF(ret); - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - err = svn_wc_adm_open3(&ret->adm, parent_wc, - path, - write_lock, depth, py_cancel_check, NULL, - ret->pool); - Py_END_ALLOW_THREADS - - if (err != NULL) { - handle_svn_error(err); - svn_error_clear(err); - Py_DECREF(ret); - return NULL; - } - - return (PyObject *)ret; -} - -static PyObject *adm_access_path(PyObject *self) -{ - AdmObject *admobj = (AdmObject *)self; - ADM_CHECK_CLOSED(admobj); - return py_object_from_svn_abspath(svn_wc_adm_access_path(admobj->adm)); -} - -static PyObject *adm_locked(PyObject *self) -{ - AdmObject *admobj = (AdmObject *)self; - ADM_CHECK_CLOSED(admobj); - return PyBool_FromLong(svn_wc_adm_locked(admobj->adm)); -} - -static PyObject *adm_prop_get(PyObject *self, PyObject *args) -{ - char *name; - const char *path; - AdmObject *admobj = (AdmObject *)self; - const svn_string_t *value; - apr_pool_t *temp_pool; - PyObject *ret, *py_path; - - if (!PyArg_ParseTuple(args, "sO", &name, &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_get(&value, name, path, admobj->adm, temp_pool)); - if (value == NULL || value->data == NULL) { - ret = Py_None; - Py_INCREF(ret); - } else { - ret = PyBytes_FromStringAndSize(value->data, value->len); - } - apr_pool_destroy(temp_pool); - return ret; -} - -static PyObject *adm_prop_set(PyObject *self, PyObject *args) -{ - char *name, *value; - const char *path; - AdmObject *admobj = (AdmObject *)self; - bool skip_checks=false; - apr_pool_t *temp_pool; - int vallen; - svn_string_t *cvalue; - PyObject *py_path; - PyObject *notify_func = Py_None; - - if (!PyArg_ParseTuple(args, "sz#O|bO", &name, &value, &vallen, &py_path, &skip_checks, - ¬ify_func)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - if (value == NULL) { - cvalue = NULL; - } else { - cvalue = svn_string_ncreate(value, vallen, temp_pool); - } -#if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set3(name, cvalue, path, admobj->adm, - skip_checks, py_wc_notify_func, (void *)notify_func, - temp_pool)); -#else - RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set2(name, cvalue, path, admobj->adm, - skip_checks, temp_pool)); -#endif - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *adm_entries_read(PyObject *self, PyObject *args) -{ - apr_hash_t *entries; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - bool show_hidden=false; - apr_hash_index_t *idx; - const char *key; - apr_ssize_t klen; - svn_wc_entry_t *entry; - PyObject *py_entries, *obj; - - if (!PyArg_ParseTuple(args, "|b", &show_hidden)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - RUN_SVN_WITH_POOL(temp_pool, svn_wc_entries_read(&entries, admobj->adm, - show_hidden, temp_pool)); - py_entries = PyDict_New(); - if (py_entries == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - idx = apr_hash_first(temp_pool, entries); - while (idx != NULL) { - apr_hash_this(idx, (const void **)&key, &klen, (void **)&entry); - if (entry == NULL) { - obj = Py_None; - Py_INCREF(obj); - } else { - obj = py_entry(entry); - } - PyDict_SetItemString(py_entries, key, obj); - Py_DECREF(obj); - idx = apr_hash_next(idx); - } - apr_pool_destroy(temp_pool); - return py_entries; -} - -static PyObject *adm_walk_entries(PyObject *self, PyObject *args) -{ - const char *path; - PyObject *callbacks; - bool show_hidden=false; - apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - svn_depth_t depth = svn_depth_infinity; - PyObject *py_path; - - if (!PyArg_ParseTuple(args, "OO|bi", &py_path, &callbacks, &show_hidden, &depth)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - -#if ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries3( - path, admobj->adm, - &py_wc_entry_callbacks2, (void *)callbacks, - depth, show_hidden, py_cancel_check, NULL, - temp_pool)); -#else - if (depth != svn_depth_infinity) { - PyErr_SetString(PyExc_NotImplementedError, - "depth != infinity not supported for svn < 1.5"); - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries2( - path, admobj->adm, - &py_wc_entry_callbacks, (void *)callbacks, - show_hidden, py_cancel_check, NULL, - temp_pool)); -#endif - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *adm_entry(PyObject *self, PyObject *args) -{ - const char *path; - PyObject *py_path; - bool show_hidden=false; - apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - const svn_wc_entry_t *entry; - PyObject *ret; - - if (!PyArg_ParseTuple(args, "O|b", &py_path, &show_hidden)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_entry(&entry, path, admobj->adm, show_hidden, temp_pool)); - - if (entry == NULL) { - PyErr_Format(PyExc_KeyError, "No such entry '%s'", path); - ret = NULL; - } else { - ret = py_entry(entry); - } - - apr_pool_destroy(temp_pool); - return ret; -} - -static PyObject *adm_get_prop_diffs(PyObject *self, PyObject *args) -{ - const char *path; - apr_pool_t *temp_pool; - apr_array_header_t *propchanges; - apr_hash_t *original_props; - PyObject *py_path; - AdmObject *admobj = (AdmObject *)self; - svn_prop_t el; - int i; - PyObject *py_propchanges, *py_orig_props, *pyval; - - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_prop_diffs(&propchanges, &original_props, - path, admobj->adm, temp_pool)); - py_propchanges = PyList_New(propchanges->nelts); - if (py_propchanges == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - for (i = 0; i < propchanges->nelts; i++) { - el = APR_ARRAY_IDX(propchanges, i, svn_prop_t); - if (el.value != NULL) - pyval = Py_BuildValue("(sz#)", el.name, el.value->data, el.value->len); - else - pyval = Py_BuildValue("(sO)", el.name, Py_None); - if (pyval == NULL) { - apr_pool_destroy(temp_pool); - Py_DECREF(py_propchanges); - return NULL; - } - if (PyList_SetItem(py_propchanges, i, pyval) != 0) { - Py_DECREF(py_propchanges); - apr_pool_destroy(temp_pool); - return NULL; - } - } - py_orig_props = prop_hash_to_dict(original_props); - apr_pool_destroy(temp_pool); - if (py_orig_props == NULL) { - Py_DECREF(py_propchanges); - return NULL; - } - return Py_BuildValue("(NN)", py_propchanges, py_orig_props); -} - -static PyObject *adm_add(PyObject *self, PyObject *args, PyObject *kwargs) -{ - const char *path; - char *copyfrom_url=NULL; - svn_revnum_t copyfrom_rev=-1; - char *kwnames[] = { "path", "copyfrom_url", "copyfrom_rev", "notify_func", "depth", NULL }; - PyObject *notify_func=Py_None, *py_path; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - svn_depth_t depth = svn_depth_infinity; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|zlOi", kwnames, &py_path, - ©from_url, ©from_rev, ¬ify_func, &depth)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - -#if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_add3( - path, admobj->adm, - depth, svn_uri_canonicalize(copyfrom_url, temp_pool), - copyfrom_rev, py_cancel_check, NULL, - py_wc_notify_func, - (void *)notify_func, - temp_pool)); -#else - if (depth != svn_depth_infinity) { - PyErr_SetString(PyExc_NotImplementedError, "depth != infinity not supported on svn < 1.6"); - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_wc_add2( - path, admobj->adm, copyfrom_url, - copyfrom_rev, py_cancel_check, - py_wc_notify_func, - (void *)notify_func, - temp_pool)); -#endif - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *adm_copy(PyObject *self, PyObject *args) -{ - AdmObject *admobj = (AdmObject *)self; - char *src, *dst; - PyObject *notify_func=Py_None; - apr_pool_t *temp_pool; - - if (!PyArg_ParseTuple(args, "ss|O", &src, &dst, ¬ify_func)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - RUN_SVN_WITH_POOL(temp_pool, svn_wc_copy2(src, admobj->adm, dst, - py_cancel_check, NULL, - py_wc_notify_func, (void *)notify_func, - temp_pool)); - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *adm_delete(PyObject *self, PyObject *args, PyObject *kwargs) -{ - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - char *kwnames[] = { "path", "notify_func", "keep_local", - NULL }; - const char *path; - PyObject *py_path; - PyObject *notify_func=Py_None; - bool keep_local = false; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Ob:delete", kwnames, - &py_path, ¬ify_func, &keep_local)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - -#if ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete3(path, admobj->adm, - py_cancel_check, NULL, - py_wc_notify_func, (void *)notify_func, - keep_local, - temp_pool)); -#else - if (keep_local) { - PyErr_SetString(PyExc_NotImplementedError, - "keep_local not supported on Subversion < 1.5"); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete2(path, admobj->adm, - py_cancel_check, NULL, - py_wc_notify_func, (void *)notify_func, - temp_pool)); -#endif - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *adm_crawl_revisions(PyObject *self, PyObject *args, PyObject *kwargs) -{ - const char *path; - PyObject *reporter; - bool restore_files=true, recurse=true, use_commit_times=true; - PyObject *notify_func=Py_None; - apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - svn_wc_traversal_info_t *traversal_info; - bool depth_compatibility_trick = false; - bool honor_depth_exclude = false; - char *kwnames[] = { "path", "reporter", "restore_files", "recurse", "use_commit_times", "notify_func", "depth_compatibility_trick", "honor_depth_exclude,", NULL }; - PyObject *py_path; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bbbObb", kwnames, &py_path, - &reporter, &restore_files, &recurse, &use_commit_times, - ¬ify_func, &depth_compatibility_trick, &honor_depth_exclude)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - traversal_info = svn_wc_init_traversal_info(temp_pool); -#if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions4(path, admobj->adm, - &py_ra_reporter, (void *)reporter, - restore_files, recurse?svn_depth_infinity:svn_depth_files, - honor_depth_exclude?TRUE:FALSE, - depth_compatibility_trick?TRUE:FALSE, use_commit_times, - py_wc_notify_func, (void *)notify_func, - traversal_info, temp_pool)); -#elif ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions3(path, admobj->adm, - &py_ra_reporter, (void *)reporter, - restore_files, recurse?svn_depth_infinity:svn_depth_files, - depth_compatibility_trick, use_commit_times, - py_wc_notify_func, (void *)notify_func, - traversal_info, temp_pool)); -#else - RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions2(path, admobj->adm, - &py_ra_reporter, (void *)reporter, - restore_files, recurse, use_commit_times, - py_wc_notify_func, (void *)notify_func, - traversal_info, temp_pool)); -#endif - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static void wc_done_handler(void *self) -{ - AdmObject *admobj = (AdmObject *)self; - - Py_DECREF(admobj); -} - -static PyObject *adm_get_update_editor(PyObject *self, PyObject *args) -{ - char *target; - bool use_commit_times=true, recurse=true; - PyObject * notify_func=Py_None; - char *diff3_cmd=NULL; - const svn_delta_editor_t *editor; - AdmObject *admobj = (AdmObject *)self; - void *edit_baton; - apr_pool_t *pool; - svn_revnum_t *latest_revnum; - svn_error_t *err; - bool allow_unver_obstructions = false; - bool depth_is_sticky = false; - - if (!PyArg_ParseTuple(args, "s|bbOzbb", &target, &use_commit_times, - &recurse, ¬ify_func, &diff3_cmd, &depth_is_sticky, - &allow_unver_obstructions)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - pool = Pool(NULL); - if (pool == NULL) - return NULL; - latest_revnum = (svn_revnum_t *)apr_palloc(pool, sizeof(svn_revnum_t)); - Py_BEGIN_ALLOW_THREADS -#if ONLY_SINCE_SVN(1, 5) - /* FIXME: Support all values of depth */ - /* FIXME: Support fetch_func */ - /* FIXME: Support conflict func */ - err = svn_wc_get_update_editor3(latest_revnum, admobj->adm, target, - use_commit_times, recurse?svn_depth_infinity:svn_depth_files, - depth_is_sticky?TRUE:FALSE, allow_unver_obstructions?TRUE:FALSE, - py_wc_notify_func, (void *)notify_func, - py_cancel_check, NULL, - NULL, NULL, NULL, NULL, - diff3_cmd, NULL, &editor, &edit_baton, - NULL, pool); -#else - if (allow_unver_obstructions) { - PyErr_SetString(PyExc_NotImplementedError, - "allow_unver_obstructions is not supported in svn < 1.5"); - apr_pool_destroy(pool); - PyEval_RestoreThread(_save); - return NULL; - } - if (depth_is_sticky) { - PyErr_SetString(PyExc_NotImplementedError, - "depth_is_sticky is not supported in svn < 1.5"); - apr_pool_destroy(pool); - PyEval_RestoreThread(_save); - return NULL; - } - err = svn_wc_get_update_editor2(latest_revnum, admobj->adm, target, - use_commit_times, recurse, py_wc_notify_func, (void *)notify_func, - py_cancel_check, NULL, diff3_cmd, &editor, &edit_baton, - NULL, pool); -#endif - Py_END_ALLOW_THREADS - if (err != NULL) { - handle_svn_error(err); - svn_error_clear(err); - apr_pool_destroy(pool); - return NULL; - } - Py_INCREF(admobj); - return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, - wc_done_handler, admobj, NULL); -} - -static bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_array_header_t **ret) +bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_array_header_t **ret) { PyObject *key, *val; Py_ssize_t idx; @@ -1240,384 +351,62 @@ static bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_arra return true; } -static PyObject *adm_has_binary_prop(PyObject *self, PyObject *args) -{ - const char *path; - svn_boolean_t binary; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - PyObject *py_path; - - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_has_binary_prop(&binary, path, admobj->adm, temp_pool)); - - apr_pool_destroy(temp_pool); - - return PyBool_FromLong(binary); -} - -static PyObject *adm_process_committed(PyObject *self, PyObject *args, PyObject *kwargs) -{ - const char *path; - char *rev_date = NULL, *rev_author = NULL; - bool recurse, remove_lock = false; - unsigned char *digest = NULL; - svn_revnum_t new_revnum; - PyObject *py_wcprop_changes = Py_None, *py_path; - apr_array_header_t *wcprop_changes = NULL; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - int digest_len; - bool remove_changelist = false; - char *kwnames[] = { "path", "recurse", "new_revnum", "rev_date", "rev_author", - "wcprop_changes", "remove_lock", "digest", "remove_changelist", NULL }; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oblzz|Obz#b", kwnames, - &py_path, &recurse, &new_revnum, &rev_date, - &rev_author, &py_wcprop_changes, - &remove_lock, &digest, &digest_len, &remove_changelist)) - return NULL; - - PyErr_WarnEx(PyExc_DeprecationWarning, "process_committed is deprecated. Use process_committed_queue instead.", 2); - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - if (!py_dict_to_wcprop_changes(py_wcprop_changes, temp_pool, &wcprop_changes)) { - apr_pool_destroy(temp_pool); - return NULL; - } - #if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed4( - path, admobj->adm, recurse, new_revnum, - rev_date, rev_author, wcprop_changes, - remove_lock, remove_changelist?TRUE:FALSE, digest, temp_pool)); -#else - if (remove_changelist) { - PyErr_SetString(PyExc_NotImplementedError, "remove_changelist only supported in svn < 1.6"); - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(path, admobj->adm, recurse, new_revnum, - rev_date, rev_author, wcprop_changes, - remove_lock, digest, temp_pool)); -#endif - - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *adm_close(PyObject *self) -{ - AdmObject *admobj = (AdmObject *)self; - if (admobj->adm != NULL) { -#if ONLY_SINCE_SVN(1, 6) - apr_pool_t *temp_pool = Pool(NULL); - Py_BEGIN_ALLOW_THREADS - svn_wc_adm_close2(admobj->adm, temp_pool); - apr_pool_destroy(temp_pool); -#else - Py_BEGIN_ALLOW_THREADS - svn_wc_adm_close(admobj->adm); -#endif - Py_END_ALLOW_THREADS - admobj->adm = NULL; - } - - Py_RETURN_NONE; -} - -static void adm_dealloc(PyObject *self) -{ - apr_pool_destroy(((AdmObject *)self)->pool); - PyObject_Del(self); -} - -static PyObject *adm_repr(PyObject *self) -{ - AdmObject *admobj = (AdmObject *)self; - - if (admobj->adm == NULL) { - return PyRepr_FromFormat("<wc.WorkingCopy (closed) at 0x%p>", admobj); - } else { - return PyRepr_FromFormat("<wc.WorkingCopy at '%s'>", - svn_wc_adm_access_path(admobj->adm)); - } -} - -static PyObject *adm_remove_lock(PyObject *self, PyObject *args) -{ - const char *path; - PyObject *py_path; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_remove_lock(path, admobj->adm, temp_pool)) - - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *get_ancestry(PyObject *self, PyObject *args) -{ - const char *path; - char *url; - svn_revnum_t rev; - apr_pool_t *temp_pool; - PyObject *py_path; - AdmObject *admobj = (AdmObject *)self; - - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_ancestry(&url, &rev, path, admobj->adm, temp_pool)); - - apr_pool_destroy(temp_pool); - - return Py_BuildValue("(si)", url, rev); -} - -static PyObject *maybe_set_repos_root(PyObject *self, PyObject *args) -{ - char *path, *repos; - apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - - if (!PyArg_ParseTuple(args, "ss", &path, &repos)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_maybe_set_repos_root(admobj->adm, path, repos, temp_pool)); - - Py_RETURN_NONE; -} - -static PyObject *add_repos_file(PyObject *self, PyObject *args, PyObject *kwargs) -{ - char *kwnames[] = { "dst_path", "new_base_contents", "new_contents", - "new_base_props", "new_props", "copyfrom_url", "copyfrom_rev", - "notify", NULL }; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - char *dst_path, *copyfrom_url = NULL; - svn_revnum_t copyfrom_rev = -1; - PyObject *py_new_base_contents, *py_new_contents, *py_new_base_props, - *py_new_props, *notify = Py_None; - svn_stream_t *new_contents, *new_base_contents; - apr_hash_t *new_props, *new_base_props; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sOOOO|zlO", kwnames, - &dst_path, &py_new_base_contents, &py_new_contents, &py_new_base_props, - &py_new_props, ©from_url, ©from_rev, ¬ify)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - - new_base_props = prop_dict_to_hash(temp_pool, py_new_base_props); - - new_props = prop_dict_to_hash(temp_pool, py_new_props); - - new_base_contents = new_py_stream(temp_pool, py_new_base_contents); - - new_contents = new_py_stream(temp_pool, py_new_contents); - -#if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_add_repos_file3(dst_path, admobj->adm, - new_base_contents, - new_contents, - new_base_props, - new_props, - copyfrom_url, copyfrom_rev, - py_cancel_check, NULL, - py_wc_notify_func, notify, temp_pool)); -#else - PyErr_SetString(PyExc_NotImplementedError, - "add_repos_file3 not supported on svn < 1.6"); - apr_pool_destroy(temp_pool); -#endif - - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *mark_missing_deleted(PyObject *self, PyObject *args) -{ - const char *path; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - PyObject *py_path; - - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_mark_missing_deleted(path, admobj->adm, temp_pool)); - - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -static PyObject *remove_from_revision_control(PyObject *self, PyObject *args) -{ - char *name; - bool destroy_wf = false, instant_error = false; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - - if (!PyArg_ParseTuple(args, "s|bb", &name, &destroy_wf, &instant_error)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_remove_from_revision_control(admobj->adm, name, - destroy_wf?TRUE:FALSE, instant_error?TRUE:FALSE, py_cancel_check, NULL, temp_pool)); - - apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -} - -#if ONLY_SINCE_SVN(1, 6) -static svn_error_t *wc_validator3(void *baton, const char *uuid, const char *url, const char *root_url, apr_pool_t *pool) +svn_error_t *wc_validator3(void *baton, const char *uuid, const char *url, const char *root_url, apr_pool_t *pool) { PyObject *py_validator = baton, *ret; + PyGILState_STATE state; if (py_validator == Py_None) { return NULL; } - + state = PyGILState_Ensure(); ret = PyObject_CallFunction(py_validator, "sss", uuid, url, root_url); if (ret == NULL) { + PyGILState_Release(state); return py_svn_error(); } Py_DECREF(ret); + PyGILState_Release(state); return NULL; } -#else +#endif -static svn_error_t *wc_validator2(void *baton, const char *uuid, const char *url, svn_boolean_t root, apr_pool_t *pool) +svn_error_t *wc_validator2(void *baton, const char *uuid, const char *url, svn_boolean_t root, apr_pool_t *pool) { PyObject *py_validator = baton, *ret; + PyGILState_STATE state; if (py_validator == Py_None) { return NULL; } + state = PyGILState_Ensure(); ret = PyObject_CallFunction(py_validator, "ssO", uuid, url, Py_None); if (ret == NULL) { + PyGILState_Release(state); return py_svn_error(); } Py_DECREF(ret); + PyGILState_Release(state); return NULL; } -#endif - -static PyObject *relocate(PyObject *self, PyObject *args) +static PyObject *get_actual_target(PyObject *self, PyObject *args) { const char *path; - char *from, *to; - AdmObject *admobj = (AdmObject *)self; + const char *anchor = NULL, *target = NULL; apr_pool_t *temp_pool; - bool recurse = true; - PyObject *py_validator = Py_None, *py_path; + PyObject *ret, *py_path; - if (!PyArg_ParseTuple(args, "Oss|bO:relocate", &py_path, &from, &to, &recurse, - &py_validator)) + if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; - ADM_CHECK_CLOSED(admobj); - temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; @@ -1629,392 +418,276 @@ static PyObject *relocate(PyObject *self, PyObject *args) return NULL; } -#if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_relocate3(path, admobj->adm, from, to, recurse?TRUE:FALSE, wc_validator3, py_validator, temp_pool)); -#else - RUN_SVN_WITH_POOL(temp_pool, svn_wc_relocate2(path, admobj->adm, from, to, recurse?TRUE:FALSE, wc_validator2, py_validator, temp_pool)); -#endif + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_get_actual_target(path, + &anchor, &target, temp_pool)); + + ret = Py_BuildValue("(ss)", anchor, target); apr_pool_destroy(temp_pool); - Py_RETURN_NONE; + return ret; } -static PyObject *crop_tree(PyObject *self, PyObject *args) +/** + * Determine the revision status of a specified working copy. + * + * :return: Tuple with minimum and maximum revnums found, whether the + * working copy was switched and whether it was modified. + */ +static PyObject *revision_status(PyObject *self, PyObject *args, PyObject *kwargs) { - char *target; - svn_depth_t depth; - PyObject *notify; + char *kwnames[] = { "wc_path", "trail_url", "committed", NULL }; + const char *wc_path; + char *trail_url=NULL; + bool committed=false; + PyObject *ret, *py_wc_path; + svn_wc_revision_status_t *revstatus; apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - if (!PyArg_ParseTuple(args, "si|O", &target, &depth, ¬ify)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|zb", kwnames, &py_wc_path, + &trail_url, &committed)) return NULL; - ADM_CHECK_CLOSED(admobj); - -#if ONLY_SINCE_SVN(1, 6) temp_pool = Pool(NULL); - if (temp_pool == NULL) + if (temp_pool == NULL) { return NULL; + } - RUN_SVN_WITH_POOL(temp_pool, svn_wc_crop_tree(admobj->adm, - target, depth, py_wc_notify_func, notify, - py_cancel_check, NULL, temp_pool)); - + wc_path = py_object_to_svn_dirent(py_wc_path, temp_pool); + if (wc_path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_revision_status( + &revstatus, wc_path, trail_url, + committed, py_cancel_check, NULL, temp_pool)); + ret = Py_BuildValue("(llbb)", revstatus->min_rev, revstatus->max_rev, + revstatus->switched, revstatus->modified); apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; -#else - PyErr_SetString(PyExc_NotImplementedError, - "crop_tree only available on subversion < 1.6"); - return NULL; -#endif + return ret; } -static PyObject *translated_stream(PyObject *self, PyObject *args) +static PyObject *is_normal_prop(PyObject *self, PyObject *args) { - char *path, *versioned_file; - StreamObject *ret; - svn_stream_t *stream; - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *stream_pool; - int flags; - - if (!PyArg_ParseTuple(args, "ssi", &path, &versioned_file, &flags)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - -#if ONLY_SINCE_SVN(1, 5) - stream_pool = Pool(NULL); - if (stream_pool == NULL) - return NULL; - - RUN_SVN_WITH_POOL(stream_pool, - svn_wc_translated_stream(&stream, path, versioned_file, admobj->adm, - flags, stream_pool)); + char *name; - ret = PyObject_New(StreamObject, &Stream_Type); - if (ret == NULL) + if (!PyArg_ParseTuple(args, "s", &name)) return NULL; - ret->pool = stream_pool; - ret->closed = FALSE; - ret->stream = stream; - - return (PyObject *)ret; -#else - PyErr_SetString(PyExc_NotImplementedError, - "translated_stream() is only available on Subversion >= 1.5"); - return NULL; -#endif + return PyBool_FromLong(svn_wc_is_normal_prop(name)); } -static PyObject *adm_text_modified(PyObject *self, PyObject *args) +static PyObject *is_adm_dir(PyObject *self, PyObject *args) { - const char *path; - bool force_comparison = false; - apr_pool_t *temp_pool; + const char *name; + PyObject *py_name; + apr_pool_t *pool; svn_boolean_t ret; - AdmObject *admobj = (AdmObject *)self; - PyObject *py_path; - if (!PyArg_ParseTuple(args, "O|b", &py_path, &force_comparison)) + if (!PyArg_ParseTuple(args, "O", &py_name)) return NULL; - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { + pool = Pool(NULL); + if (pool == NULL) return NULL; - } - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } + name = py_object_to_svn_string(py_name, pool); + if (name == NULL) { + return NULL; + } - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_text_modified_p(&ret, path, force_comparison?TRUE:FALSE, admobj->adm, - temp_pool)); + ret = svn_wc_is_adm_dir(name, pool); - apr_pool_destroy(temp_pool); + apr_pool_destroy(pool); return PyBool_FromLong(ret); } -static PyObject *adm_props_modified(PyObject *self, PyObject *args) +static PyObject *is_wc_prop(PyObject *self, PyObject *args) { - const char *path; - apr_pool_t *temp_pool; - svn_boolean_t ret; - AdmObject *admobj = (AdmObject *)self; - PyObject *py_path; + char *name; - if (!PyArg_ParseTuple(args, "O", &py_path)) + if (!PyArg_ParseTuple(args, "s", &name)) return NULL; - ADM_CHECK_CLOSED(admobj); + return PyBool_FromLong(svn_wc_is_wc_prop(name)); +} - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } +static PyObject *is_entry_prop(PyObject *self, PyObject *args) +{ + char *name; - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); + if (!PyArg_ParseTuple(args, "s", &name)) return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_props_modified_p(&ret, path, admobj->adm, temp_pool)); - - apr_pool_destroy(temp_pool); + return PyBool_FromLong(svn_wc_is_entry_prop(name)); +} - return PyBool_FromLong(ret); +static PyObject *get_adm_dir(PyObject *self) +{ + apr_pool_t *pool; + PyObject *ret; + const char *dir; + pool = Pool(NULL); + if (pool == NULL) + return NULL; + dir = svn_wc_get_adm_dir(pool); + ret = py_object_from_svn_abspath(dir); + apr_pool_destroy(pool); + return ret; } -static PyObject *adm_process_committed_queue(PyObject *self, PyObject *args) +static PyObject *set_adm_dir(PyObject *self, PyObject *args) { apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - svn_revnum_t revnum; - char *date, *author; - CommittedQueueObject *py_queue; + char *name; + PyObject *py_name; - if (!PyArg_ParseTuple(args, "O!lss", &CommittedQueue_Type, &py_queue, - &revnum, &date, &author)) + if (!PyArg_ParseTuple(args, "O", &py_name)) return NULL; - ADM_CHECK_CLOSED(admobj); - temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; - -#if ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed_queue(py_queue->queue, admobj->adm, revnum, date, author, temp_pool)); -#else - { - int i; - for (i = 0; i < py_queue->queue->queue->nelts; i++) { - committed_queue_item_t *cqi = APR_ARRAY_IDX(py_queue->queue->queue, i, - committed_queue_item_t *); - - RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(cqi->path, admobj->adm, - cqi->recurse, revnum, date, author, cqi->wcprop_changes, - cqi->remove_lock, cqi->digest, temp_pool)); - } + name = py_object_to_svn_string(py_name, temp_pool); + if (name == NULL) { + apr_pool_destroy(temp_pool); + return NULL; } -#endif + RUN_SVN_WITH_POOL(temp_pool, svn_wc_set_adm_dir(name, temp_pool)); apr_pool_destroy(temp_pool); - Py_RETURN_NONE; } -static PyObject *get_actual_target(PyObject *self, PyObject *args) +static PyObject *get_pristine_copy_path(PyObject *self, PyObject *args) { + apr_pool_t *pool; + const char *pristine_path; const char *path; - const char *anchor = NULL, *target = NULL; - apr_pool_t *temp_pool; - PyObject *ret, *py_path; + PyObject *py_path; + PyObject *ret; if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; - temp_pool = Pool(NULL); - if (temp_pool == NULL) { + pool = Pool(NULL); + if (pool == NULL) return NULL; - } - path = py_object_to_svn_dirent(py_path, temp_pool); + path = py_object_to_svn_abspath(py_path, pool); if (path == NULL) { - apr_pool_destroy(temp_pool); + apr_pool_destroy(pool); return NULL; } - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_get_actual_target(path, - &anchor, &target, temp_pool)); - - ret = Py_BuildValue("(ss)", anchor, target); - - apr_pool_destroy(temp_pool); - + PyErr_WarnEx(PyExc_DeprecationWarning, "get_pristine_copy_path is deprecated. Use get_pristine_contents instead.", 2); + RUN_SVN_WITH_POOL(pool, + svn_wc_get_pristine_copy_path(path, + &pristine_path, pool)); + ret = py_object_from_svn_abspath(pristine_path); + apr_pool_destroy(pool); return ret; } -static PyObject *is_wc_root(PyObject *self, PyObject *args) +static PyObject *get_pristine_contents(PyObject *self, PyObject *args) { const char *path; - svn_boolean_t wc_root; apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; PyObject *py_path; +#if ONLY_SINCE_SVN(1, 6) + StreamObject *ret; + apr_pool_t *stream_pool; + svn_stream_t *stream; +#else +#if PY_MAJOR_VERSION >= 3 + int fd; +#endif + PyObject *ret; + const char *pristine_path; +#endif if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { +#if ONLY_SINCE_SVN(1, 6) + stream_pool = Pool(NULL); + if (stream_pool == NULL) return NULL; - } - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); + temp_pool = Pool(stream_pool); + if (temp_pool == NULL) { + apr_pool_destroy(stream_pool); return NULL; } - - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_is_wc_root(&wc_root, path, admobj->adm, temp_pool)); - - apr_pool_destroy(temp_pool); - - return PyBool_FromLong(wc_root); -} - -static PyObject *transmit_text_deltas(PyObject *self, PyObject *args) -{ - const char *path; - const char *tempfile; - bool fulltext; - PyObject *editor_obj, *py_digest, *py_path; - unsigned char digest[APR_MD5_DIGESTSIZE]; - apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - PyObject *ret; - - if (!PyArg_ParseTuple(args, "ObO", &py_path, &fulltext, &editor_obj)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - +#else temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; } +#endif - path = py_object_to_svn_dirent(py_path, temp_pool); + path = py_object_to_svn_abspath(py_path, temp_pool); if (path == NULL) { apr_pool_destroy(temp_pool); return NULL; } - Py_INCREF(editor_obj); - - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_transmit_text_deltas2(&tempfile, digest, - path, admobj->adm, fulltext?TRUE:FALSE, - &py_editor, editor_obj, temp_pool)); - - py_digest = PyBytes_FromStringAndSize((char *)digest, APR_MD5_DIGESTSIZE); - if (py_digest == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - ret = Py_BuildValue("sN", tempfile, py_digest); - if (ret == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(stream_pool, svn_wc_get_pristine_contents(&stream, path, stream_pool, temp_pool)); apr_pool_destroy(temp_pool); - return ret; -} - -static PyObject *transmit_prop_deltas(PyObject *self, PyObject *args) -{ - const char *path; - PyObject *editor_obj; - apr_pool_t *temp_pool; - AdmObject *admobj = (AdmObject *)self; - PyObject *py_path; - EntryObject *py_entry; + if (stream == NULL) { + apr_pool_destroy(stream_pool); + Py_RETURN_NONE; + } - if (!PyArg_ParseTuple(args, "OO!O", &py_path, &Entry_Type, &py_entry, &editor_obj)) + ret = PyObject_New(StreamObject, &Stream_Type); + if (ret == NULL) return NULL; - ADM_CHECK_CLOSED(admobj); + ret->pool = stream_pool; + ret->closed = FALSE; + ret->stream = stream; + return (PyObject *)ret; +#else temp_pool = Pool(NULL); - if (temp_pool == NULL) { + if (temp_pool == NULL) return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { + RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_pristine_copy_path(path, &pristine_path, temp_pool)); +#if PY_MAJOR_VERSION >= 3 + fd = open(pristine_path, O_RDONLY); + if (fd < 0) { + PyErr_SetFromErrno(PyExc_IOError); apr_pool_destroy(temp_pool); return NULL; } - - Py_INCREF(editor_obj); - - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_transmit_prop_deltas(path, - admobj->adm, &(py_entry->entry), &py_editor, editor_obj, NULL, temp_pool)); - + ret = PyFile_FromFd(fd, pristine_path, "rb", -1, NULL, NULL, NULL, true); +#else + ret = PyFile_FromString((char *)pristine_path, "rb"); +#endif apr_pool_destroy(temp_pool); - - Py_RETURN_NONE; + return ret; +#endif } -static PyObject *retrieve(PyObject *self, PyObject *args) +static PyObject *ensure_adm(PyObject *self, PyObject *args, PyObject *kwargs) { const char *path; - svn_wc_adm_access_t *result; + char *uuid, *url = NULL; PyObject *py_path; - AdmObject *admobj = (AdmObject *)self, *ret; + char *repos = NULL; + long rev = -1; apr_pool_t *pool; + char *kwnames[] = { "path", "uuid", "url", "repos", "rev", "depth", NULL }; + int depth = svn_depth_infinity; - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - pool = Pool(NULL); - if (pool == NULL) - return NULL; - - path = py_object_to_svn_dirent(py_path, pool); - if (path == NULL) { - apr_pool_destroy(pool); - return NULL; - } - - RUN_SVN_WITH_POOL(pool, svn_wc_adm_retrieve(&result, admobj->adm, - path, pool)); - - ret = PyObject_New(AdmObject, &Adm_Type); - if (ret == NULL) - return NULL; - - ret->pool = pool; - ret->adm = result; - - return (PyObject *)ret; -} - -static PyObject *probe_retrieve(PyObject *self, PyObject *args) -{ - const char *path; - svn_wc_adm_access_t *result; - AdmObject *admobj = (AdmObject *)self, *ret; - apr_pool_t *pool; - PyObject *py_path; - - if (!PyArg_ParseTuple(args, "O", &py_path)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oss|sli", kwnames, + &py_path, &uuid, &url, &repos, &rev, &depth)) return NULL; - ADM_CHECK_CLOSED(admobj); - pool = Pool(NULL); if (pool == NULL) { return NULL; @@ -2026,34 +699,35 @@ static PyObject *probe_retrieve(PyObject *self, PyObject *args) return NULL; } - RUN_SVN_WITH_POOL(pool, svn_wc_adm_probe_retrieve(&result, admobj->adm, - path, pool)); - - ret = PyObject_New(AdmObject, &Adm_Type); - if (ret == NULL) +#if ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(pool, + svn_wc_ensure_adm3(path, + uuid, url, repos, rev, depth, pool)); +#else + if (depth != svn_depth_infinity) { + PyErr_SetString(PyExc_NotImplementedError, + "depth != infinity not supported with svn < 1.5"); + apr_pool_destroy(pool); return NULL; - - ret->pool = pool; - ret->adm = result; - - return (PyObject *)ret; + } + RUN_SVN_WITH_POOL(pool, + svn_wc_ensure_adm2(path, + uuid, url, repos, rev, pool)); +#endif + apr_pool_destroy(pool); + Py_RETURN_NONE; } -static PyObject *probe_try(PyObject *self, PyObject *args) +static PyObject *check_wc(PyObject *self, PyObject *args) { const char *path; - svn_wc_adm_access_t *result = NULL; - AdmObject *admobj = (AdmObject *)self, *ret; apr_pool_t *pool; - int levels_to_lock = -1; - bool writelock = false; + int wc_format; PyObject *py_path; - if (!PyArg_ParseTuple(args, "O|bi", &py_path, &writelock, &levels_to_lock)) + if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; - ADM_CHECK_CLOSED(admobj); - pool = Pool(NULL); if (pool == NULL) { return NULL; @@ -2065,47 +739,23 @@ static PyObject *probe_try(PyObject *self, PyObject *args) return NULL; } - RUN_SVN_WITH_POOL(pool, svn_wc_adm_probe_try3(&result, admobj->adm, - path, writelock, levels_to_lock, - py_cancel_check, NULL, pool)); - - if (result == NULL) { - apr_pool_destroy(pool); - Py_RETURN_NONE; - } - - ret = PyObject_New(AdmObject, &Adm_Type); - if (ret == NULL) - return NULL; - - ret->pool = pool; - ret->adm = result; - - return (PyObject *)ret; + RUN_SVN_WITH_POOL(pool, svn_wc_check_wc(path, &wc_format, pool)); + apr_pool_destroy(pool); + return PyLong_FromLong(wc_format); } -static PyObject *resolved_conflict(PyObject *self, PyObject *args) +static PyObject *cleanup_wc(PyObject *self, PyObject *args, PyObject *kwargs) { - AdmObject *admobj = (AdmObject *)self; - apr_pool_t *temp_pool; - bool resolve_props, resolve_tree, resolve_text; - int depth; -#if ONLY_SINCE_SVN(1, 5) - svn_wc_conflict_choice_t conflict_choice; -#else - int conflict_choice; -#endif - PyObject *notify_func = Py_None; const char *path; + char *diff3_cmd = NULL; + char *kwnames[] = { "path", "diff3_cmd", NULL }; + apr_pool_t *temp_pool; PyObject *py_path; - if (!PyArg_ParseTuple(args, "Obbbii|O", &py_path, &resolve_text, - &resolve_props, &resolve_tree, &depth, - &conflict_choice, ¬ify_func)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|z", kwnames, + &py_path, &diff3_cmd)) return NULL; - ADM_CHECK_CLOSED(admobj); - temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; @@ -2117,275 +767,85 @@ static PyObject *resolved_conflict(PyObject *self, PyObject *args) return NULL; } -#if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, - svn_wc_resolved_conflict4(path, admobj->adm, resolve_text?TRUE:FALSE, - resolve_props?TRUE:FALSE, resolve_tree?TRUE:FALSE, depth, - conflict_choice, py_wc_notify_func, - (void *)notify_func, py_cancel_check, - NULL, temp_pool)); -#elif ONLY_SINCE_SVN(1, 5) - if (resolve_tree) { - PyErr_SetString(PyExc_NotImplementedError, - "resolve_tree not supported with svn < 1.6"); - apr_pool_destroy(temp_pool); - return NULL; - } else { - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_resolved_conflict3(path, admobj->adm, resolve_text?TRUE:FALSE, - resolve_props?TRUE:FALSE, depth, - conflict_choice, py_wc_notify_func, - (void *)notify_func, py_cancel_check, - NULL, temp_pool)); - } -#else - if (resolve_tree) { - PyErr_SetString(PyExc_NotImplementedError, - "resolve_tree not supported with svn < 1.6"); - apr_pool_destroy(temp_pool); - return NULL; - } else if (depth != svn_depth_infinity && depth != svn_depth_files) { - PyErr_SetString(PyExc_NotImplementedError, - "only infinity and files values for depth are supported"); - apr_pool_destroy(temp_pool); - return NULL; - } else if (conflict_choice != 0) { - PyErr_SetString(PyExc_NotImplementedError, - "conflict choice not supported with svn < 1.5"); - apr_pool_destroy(temp_pool); - return NULL; - } else { - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_resolved_conflict2(path, admobj->adm, resolve_text?TRUE:FALSE, - resolve_props?TRUE:FALSE, - (depth == svn_depth_infinity), - py_wc_notify_func, - (void *)notify_func, py_cancel_check, - NULL, - temp_pool)); - } -#endif + svn_wc_cleanup2(path, diff3_cmd, py_cancel_check, NULL, + temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } -static PyObject *conflicted(PyObject *self, PyObject *args) +static PyObject *match_ignore_list(PyObject *self, PyObject *args) { - const char *path; +#if ONLY_SINCE_SVN(1, 5) + char *str; + PyObject *py_list; + apr_array_header_t *list; apr_pool_t *temp_pool; - PyObject *ret; - AdmObject *admobj = (AdmObject *)self; - svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; - PyObject *py_path; + svn_boolean_t ret; - if (!PyArg_ParseTuple(args, "O", &py_path)) + if (!PyArg_ParseTuple(args, "sO", &str, &py_list)) return NULL; - ADM_CHECK_CLOSED(admobj); - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { + if (!string_list_to_apr_array(temp_pool, py_list, &list)) { apr_pool_destroy(temp_pool); return NULL; } -#if ONLY_SINCE_SVN(1, 6) - RUN_SVN_WITH_POOL(temp_pool, svn_wc_conflicted_p2(&text_conflicted, - &prop_conflicted, &tree_conflicted, path, admobj->adm, temp_pool)); - - ret = Py_BuildValue("(bbb)", text_conflicted, prop_conflicted, tree_conflicted); -#else - RUN_SVN_WITH_POOL(temp_pool, svn_wc_conflicted_p(&text_conflicted, - &prop_conflicted, path, admobj->adm, temp_pool)); - - ret = Py_BuildValue("(bbO)", text_conflicted, prop_conflicted, Py_None); -#endif + ret = svn_wc_match_ignore_list(str, list, temp_pool); apr_pool_destroy(temp_pool); - return ret; + return PyBool_FromLong(ret); +#else + PyErr_SetNone(PyExc_NotImplementedError); + return NULL; +#endif } -/** - * Determine the status of a file in the specified working copy. - * - * :return: A status object. - */ -static PyObject *ra_status(PyObject *self, PyObject *args) -{ - const char *path; - svn_wc_status2_t *st; - apr_pool_t *temp_pool; - PyObject *ret; - AdmObject *admobj = (AdmObject *)self; - PyObject *py_path; - - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; - - ADM_CHECK_CLOSED(admobj); - - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } - - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_status2(&st, path, admobj->adm, temp_pool)); - - ret = py_status(st); - - apr_pool_destroy(temp_pool); - - return (PyObject*)ret; -} - -static PyMethodDef adm_methods[] = { - { "prop_set", adm_prop_set, METH_VARARGS, "S.prop_set(name, value, path, skip_checks=False)" }, - { "access_path", (PyCFunction)adm_access_path, METH_NOARGS, - "S.access_path() -> path\n" - "Returns the base path for this working copy handle." }, - { "prop_get", adm_prop_get, METH_VARARGS, "S.prop_get(name, path) -> value" }, - { "entries_read", adm_entries_read, METH_VARARGS, "S.entries_read(include_hidden=False) -> dict" }, - { "walk_entries", adm_walk_entries, METH_VARARGS, - "S.walk_entries(path, callback, show_hidden=False)\n" - "callback should be a function that takes a path and a wc entry" }, - { "locked", (PyCFunction)adm_locked, METH_NOARGS, - "S.locked() -> bool" }, - { "get_prop_diffs", adm_get_prop_diffs, METH_VARARGS, - "S.get_prop_diffs(path) -> (propchanges, originalprops)" }, - { "add", (PyCFunction)adm_add, METH_VARARGS|METH_KEYWORDS, "S.add(path, copyfrom_url=None, copyfrom_rev=-1, notify_func=None)" }, - { "copy", adm_copy, METH_VARARGS, "S.copy(src_path, dest_path, notify_func=None)" }, - { "delete", (PyCFunction)adm_delete, METH_VARARGS|METH_KEYWORDS, "S.delete(path, notify_func=None, keep_local=False)" }, - { "crawl_revisions", (PyCFunction)adm_crawl_revisions, METH_VARARGS|METH_KEYWORDS, - "S.crawl_revisions(path, reporter, restore_files=True, recurse=True, use_commit_times=True, notify_func=None) -> None" }, - { "get_update_editor", adm_get_update_editor, METH_VARARGS, NULL }, - { "close", (PyCFunction)adm_close, METH_NOARGS, - "S.close()" }, - { "entry", (PyCFunction)adm_entry, METH_VARARGS, - "s.entry(path, show_hidden=False) -> entry" }, - { "process_committed", (PyCFunction)adm_process_committed, METH_VARARGS|METH_KEYWORDS, "S.process_committed(path, recurse, new_revnum, rev_date, rev_author, wcprop_changes=None, remove_lock=False, digest=None)" }, - { "process_committed_queue", (PyCFunction)adm_process_committed_queue, METH_VARARGS, "S.process_committed_queue(queue, new_revnum, rev_date, rev_author)" }, - { "remove_lock", (PyCFunction)adm_remove_lock, METH_VARARGS, "S.remove_lock(path)" }, - { "has_binary_prop", (PyCFunction)adm_has_binary_prop, METH_VARARGS, "S.has_binary_prop(path) -> bool" }, - { "text_modified", (PyCFunction)adm_text_modified, METH_VARARGS, "S.text_modified(filename, force_comparison=False) -> bool" }, - { "props_modified", (PyCFunction)adm_props_modified, METH_VARARGS, "S.props_modified(filename) -> bool" }, - { "get_ancestry", (PyCFunction)get_ancestry, METH_VARARGS, - "S.get_ancestry(path) -> (url, rev)" }, - { "maybe_set_repos_root", (PyCFunction)maybe_set_repos_root, METH_VARARGS, "S.maybe_set_repos_root(path, repos)" }, - { "add_repos_file", (PyCFunction)add_repos_file, METH_KEYWORDS, - "S.add_repos_file(dst_path, new_base_contents, new_contents, new_base_props, new_props, copyfrom_url=None, copyfrom_rev=-1, notify_func=None)" }, - { "mark_missing_deleted", (PyCFunction)mark_missing_deleted, METH_VARARGS, - "S.mark_missing_deleted(path)" }, - { "remove_from_revision_control", (PyCFunction)remove_from_revision_control, METH_VARARGS, - "S.remove_from_revision_control(name, destroy_wf=False, instant_error=False)" }, - { "relocate", (PyCFunction)relocate, METH_VARARGS, - "S.relocate(path, from, to, recurse=TRUE, validator=None)" }, - { "crop_tree", (PyCFunction)crop_tree, METH_VARARGS, - "S.crop_tree(target, depth, notify_func=None, cancel=None)" }, - { "translated_stream", (PyCFunction)translated_stream, METH_VARARGS, - "S.translated_stream(path, versioned_file, flags) -> stream" }, - { "is_wc_root", (PyCFunction)is_wc_root, METH_VARARGS, - "S.is_wc_root(path) -> wc_root" }, - { "transmit_text_deltas", (PyCFunction)transmit_text_deltas, METH_VARARGS, - "S.transmit_text_deltas(fulltext, editor) -> (tempfile, digest)" }, - { "transmit_prop_deltas", (PyCFunction)transmit_prop_deltas, METH_VARARGS, - "S.transmit_prop_deltas(path, entry, editor)" }, - { "probe_retrieve", (PyCFunction)probe_retrieve, METH_VARARGS, - "S.probe_retrieve(path) -> WorkingCopy" }, - { "retrieve", (PyCFunction)retrieve, METH_VARARGS, - "S.retrieve(path) -> WorkingCopy" }, - { "probe_try", (PyCFunction)probe_try, METH_VARARGS, - "S.probe_try(path, write_lock=False, levels_to_lock=-1)" }, - { "conflicted", (PyCFunction)conflicted, METH_VARARGS, - "S.conflicted(path) -> (text_conflicted, prop_conflicted, tree_conflicted)" }, - { "resolved_conflict", (PyCFunction)resolved_conflict, METH_VARARGS, - "S.resolved_conflict(path, resolve_text, resolve_props, resolve_tree, depth, conflict_choice, notify_func=None, cancel=None)" }, - { "status", (PyCFunction)ra_status, METH_VARARGS, "status(wc_path) -> Status" }, - { NULL, } -}; - -static PyTypeObject Adm_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "wc.WorkingCopy", /* const char *tp_name; For printing, in format "<module>.<name>" */ - sizeof(AdmObject), - 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ - - /* Methods to implement standard operations */ - - adm_dealloc, /* destructor tp_dealloc; */ - NULL, /* printfunc tp_print; */ - NULL, /* getattrfunc tp_getattr; */ - NULL, /* setattrfunc tp_setattr; */ - NULL, /* cmpfunc tp_compare; */ - adm_repr, /* reprfunc tp_repr; */ - - /* Method suites for standard classes */ - - NULL, /* PyNumberMethods *tp_as_number; */ - NULL, /* PySequenceMethods *tp_as_sequence; */ - NULL, /* PyMappingMethods *tp_as_mapping; */ - - /* More standard operations (here for binary compatibility) */ - - NULL, /* hashfunc tp_hash; */ - NULL, /* ternaryfunc tp_call; */ - adm_repr, /* reprfunc tp_repr; */ - NULL, /* getattrofunc tp_getattro; */ - NULL, /* setattrofunc tp_setattro; */ - - /* Functions to access object as input/output buffer */ - NULL, /* PyBufferProcs *tp_as_buffer; */ - - /* Flags to define presence of optional/expanded features */ - 0, /* long tp_flags; */ - - "Local working copy", /* const char *tp_doc; Documentation string */ - - /* Assigned meaning in release 2.0 */ - /* call function for all accessible objects */ - NULL, /* traverseproc tp_traverse; */ - - /* delete references to contained objects */ - NULL, /* inquiry tp_clear; */ - - /* Assigned meaning in release 2.1 */ - /* rich comparisons */ - NULL, /* richcmpfunc tp_richcompare; */ - - /* weak reference enabler */ - 0, /* Py_ssize_t tp_weaklistoffset; */ - - /* Added in release 2.2 */ - /* Iterators */ - NULL, /* getiterfunc tp_iter; */ - NULL, /* iternextfunc tp_iternext; */ - - /* Attribute descriptor and subclassing stuff */ - adm_methods, /* struct PyMethodDef *tp_methods; */ - NULL, /* struct PyMemberDef *tp_members; */ - NULL, /* struct PyGetSetDef *tp_getset; */ - NULL, /* struct _typeobject *tp_base; */ - NULL, /* PyObject *tp_dict; */ - NULL, /* descrgetfunc tp_descr_get; */ - NULL, /* descrsetfunc tp_descr_set; */ - 0, /* Py_ssize_t tp_dictoffset; */ - NULL, /* initproc tp_init; */ - NULL, /* allocfunc tp_alloc; */ - adm_init, /* newfunc tp_new; */ +static PyMethodDef wc_methods[] = { + { "check_wc", check_wc, METH_VARARGS, "check_wc(path) -> version\n" + "Check whether path contains a Subversion working copy\n" + "return the workdir version"}, + { "cleanup", (PyCFunction)cleanup_wc, + METH_VARARGS|METH_KEYWORDS, "cleanup(path, diff3_cmd=None)\n" }, + { "ensure_adm", (PyCFunction)ensure_adm, METH_KEYWORDS|METH_VARARGS, + "ensure_adm(path, uuid, url, repos=None, rev=None)" }, + { "get_adm_dir", (PyCFunction)get_adm_dir, METH_NOARGS, + "get_adm_dir() -> name" }, + { "set_adm_dir", (PyCFunction)set_adm_dir, METH_VARARGS, + "set_adm_dir(name)" }, + { "get_pristine_copy_path", get_pristine_copy_path, METH_VARARGS, + "get_pristine_copy_path(path) -> path" }, + { "get_pristine_contents", get_pristine_contents, METH_VARARGS, + "get_pristine_contents(path) -> stream" }, + { "is_adm_dir", is_adm_dir, METH_VARARGS, + "is_adm_dir(name) -> bool" }, + { "is_normal_prop", is_normal_prop, METH_VARARGS, + "is_normal_prop(name) -> bool" }, + { "is_entry_prop", is_entry_prop, METH_VARARGS, + "is_entry_prop(name) -> bool" }, + { "is_wc_prop", is_wc_prop, METH_VARARGS, + "is_wc_prop(name) -> bool" }, + { "revision_status", (PyCFunction)revision_status, + METH_KEYWORDS|METH_VARARGS, + "revision_status(wc_path, trail_url=None, committed=False)" + "-> (min_rev, max_rev, switched, modified)" }, + { "version", (PyCFunction)version, METH_NOARGS, + "version() -> (major, minor, patch, tag)\n\n" + "Version of libsvn_wc currently used." + }, + { "api_version", (PyCFunction)api_version, METH_NOARGS, + "api_version() -> (major, minor, patch, tag)\n\n" + "Version of libsvn_wc Subvertpy was compiled against." }, + { "match_ignore_list", (PyCFunction)match_ignore_list, METH_VARARGS, + "match_ignore_list(str, patterns) -> bool" }, + { "get_actual_target", (PyCFunction)get_actual_target, METH_VARARGS, + "get_actual_target(path) -> (anchor, target)" }, + { NULL, } }; static void committed_queue_dealloc(PyObject *self) @@ -2428,48 +888,39 @@ static PyObject *committed_queue_init(PyTypeObject *self, PyObject *args, PyObje static PyObject *committed_queue_queue(CommittedQueueObject *self, PyObject *args, PyObject *kwargs) { - char *path; - AdmObject *admobj; - PyObject *py_wcprop_changes = Py_None; + const char *path; + PyObject *admobj; + PyObject *py_wcprop_changes = Py_None, *py_path; + svn_wc_adm_access_t *adm = NULL; bool remove_lock = false, remove_changelist = false; char *md5_digest = NULL, *sha1_digest = NULL; bool recurse = false; - apr_pool_t *temp_pool; apr_array_header_t *wcprop_changes; int md5_digest_len, sha1_digest_len; +#if ONLY_SINCE_SVN(1, 7) + svn_wc_context_t *context = NULL; +#endif char *kwnames[] = { "path", "adm", "recurse", "wcprop_changes", "remove_lock", "remove_changelist", "md5_digest", "sha1_digest", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO!|bObbz#z#", kwnames, - &path, &Adm_Type, &admobj, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bObbz#z#", kwnames, + &py_path, &admobj, &recurse, &py_wcprop_changes, &remove_lock, &remove_changelist, &md5_digest, &md5_digest_len, &sha1_digest, &sha1_digest_len)) return NULL; - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - if (!py_dict_to_wcprop_changes(py_wcprop_changes, self->pool, &wcprop_changes)) { - apr_pool_destroy(temp_pool); return NULL; } - path = apr_pstrdup(self->pool, path); + path = py_object_to_svn_abspath(py_path, self->pool); if (path == NULL) { - PyErr_NoMemory(); return NULL; } if (md5_digest != NULL) { if (md5_digest_len != APR_MD5_DIGESTSIZE) { PyErr_SetString(PyExc_ValueError, "Invalid size for md5 digest"); - apr_pool_destroy(temp_pool); - return NULL; - } - md5_digest = apr_pstrdup(temp_pool, md5_digest); - if (md5_digest == NULL) { - PyErr_NoMemory(); return NULL; } } @@ -2477,42 +928,65 @@ static PyObject *committed_queue_queue(CommittedQueueObject *self, PyObject *arg if (sha1_digest != NULL) { if (sha1_digest_len != APR_SHA1_DIGESTSIZE) { PyErr_SetString(PyExc_ValueError, "Invalid size for sha1 digest"); - apr_pool_destroy(temp_pool); - return NULL; - } - sha1_digest = apr_pstrdup(temp_pool, sha1_digest); - if (sha1_digest == NULL) { - PyErr_NoMemory(); return NULL; } } + if (PyObject_IsInstance(admobj, (PyObject *)&Adm_Type)) { + adm = PyObject_GetAdmAccess(admobj); +#if ONLY_SINCE_SVN(1, 7) + } else if (PyObject_IsInstance(admobj, (PyObject *)&Context_Type)) { + context = ((ContextObject*)admobj)->context; +#endif + } else { + PyErr_SetString(PyExc_TypeError, "Second arguments needs to be Adm or Context"); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 7) + if (adm != NULL) { +#endif #if ONLY_SINCE_SVN(1, 6) { - svn_checksum_t svn_checksum, *svn_checksum_p = &svn_checksum; + svn_checksum_t *svn_checksum_p; - if (sha1_digest != NULL) { - svn_checksum.digest = (unsigned char *)sha1_digest; - svn_checksum.kind = svn_checksum_sha1; - } else if (md5_digest != NULL) { - svn_checksum.digest = (unsigned char *)md5_digest; - svn_checksum.kind = svn_checksum_md5; + if (md5_digest != NULL) { + svn_checksum_p = apr_palloc(self->pool, sizeof(svn_checksum_t)); + svn_checksum_p->digest = apr_pmemdup( + self->pool, (unsigned char *)md5_digest, APR_MD5_DIGESTSIZE); + svn_checksum_p->kind = svn_checksum_md5; } else { svn_checksum_p = NULL; } - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_queue_committed2(self->queue, path, admobj->adm, recurse?TRUE:FALSE, + RUN_SVN( + svn_wc_queue_committed2(self->queue, path, adm, recurse?TRUE:FALSE, wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE, - svn_checksum_p, temp_pool)); + svn_checksum_p, self->pool)); } #else - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_queue_committed(&self->queue, path, admobj->adm, recurse?TRUE:FALSE, + RUN_SVN( + svn_wc_queue_committed(&self->queue, path, adm, recurse?TRUE:FALSE, wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE, - (unsigned char *)md5_digest, temp_pool)); + (unsigned char *)md5_digest, self->pool)); #endif +#if ONLY_SINCE_SVN(1, 7) + } else { + svn_checksum_t *svn_checksum_p; - apr_pool_destroy(temp_pool); + if (sha1_digest != NULL) { + svn_checksum_p = apr_palloc(self->pool, sizeof(svn_checksum_t)); + svn_checksum_p->digest = apr_pmemdup( + self->pool, (unsigned char *)sha1_digest, APR_SHA1_DIGESTSIZE); + svn_checksum_p->kind = svn_checksum_sha1; + } else { + svn_checksum_p = NULL; + } + RUN_SVN( + svn_wc_queue_committed3(self->queue, context, path, recurse?TRUE:FALSE, + wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE, + svn_checksum_p, self->pool)); + } +#endif Py_RETURN_NONE; } @@ -2523,7 +997,7 @@ static PyMethodDef committed_queue_methods[] = { { NULL } }; -static PyTypeObject CommittedQueue_Type = { +PyTypeObject CommittedQueue_Type = { PyVarObject_HEAD_INIT(NULL, 0) "wc.CommittedQueue", /* const char *tp_name; For printing, in format "<module>.<name>" */ sizeof(CommittedQueueObject), @@ -2593,393 +1067,1062 @@ static PyTypeObject CommittedQueue_Type = { committed_queue_init, /* newfunc tp_new; */ }; -/** - * Determine the revision status of a specified working copy. - * - * :return: Tuple with minimum and maximum revnums found, whether the - * working copy was switched and whether it was modified. - */ -static PyObject *revision_status(PyObject *self, PyObject *args, PyObject *kwargs) +svn_lock_t *py_object_to_svn_lock(PyObject *py_lock, apr_pool_t *pool) { - char *kwnames[] = { "wc_path", "trail_url", "committed", NULL }; - const char *wc_path; - char *trail_url=NULL; - bool committed=false; - PyObject *ret, *py_wc_path; - svn_wc_revision_status_t *revstatus; - apr_pool_t *temp_pool; + LockObject* lockobj = (LockObject *)py_lock; + if (!PyObject_IsInstance(py_lock, (PyObject *)&Lock_Type)) { + PyErr_SetString(PyExc_TypeError, "Expected Lock object"); + return NULL; + } + return &lockobj->lock; +} - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|zb", kwnames, &py_wc_path, - &trail_url, &committed)) - return NULL; +#if ONLY_SINCE_SVN(1, 7) +static PyTypeObject Context_Type; - temp_pool = Pool(NULL); - if (temp_pool == NULL) { - return NULL; - } +static PyObject *py_wc_context_locked(PyObject *self, PyObject *args) +{ + PyObject* py_path; + const char *path; + apr_pool_t *pool; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + svn_boolean_t locked_here, locked; - wc_path = py_object_to_svn_dirent(py_wc_path, temp_pool); - if (wc_path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_revision_status( - &revstatus, wc_path, trail_url, - committed, py_cancel_check, NULL, temp_pool)); - ret = Py_BuildValue("(llbb)", revstatus->min_rev, revstatus->max_rev, - revstatus->switched, revstatus->modified); - apr_pool_destroy(temp_pool); - return ret; + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_locked2(&locked_here, &locked, wc_context, path, pool)); + + apr_pool_destroy(pool); + + return Py_BuildValue("(bb)", locked_here?true:false, locked?true:false); } -static PyObject *is_normal_prop(PyObject *self, PyObject *args) +static PyObject *py_wc_context_check_wc(PyObject *self, PyObject *args) { - char *name; + PyObject* py_path; + const char *path; + apr_pool_t *pool; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + int wc_format; - if (!PyArg_ParseTuple(args, "s", &name)) - return NULL; + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; - return PyBool_FromLong(svn_wc_is_normal_prop(name)); + pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_check_wc2(&wc_format, wc_context, path, pool)); + + apr_pool_destroy(pool); + +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(wc_format); +#else + return PyInt_FromLong(wc_format); +#endif } -static PyObject *is_adm_dir(PyObject *self, PyObject *args) +static PyObject *py_wc_context_text_modified_p2(PyObject *self, PyObject *args) { - char *name; - apr_pool_t *pool; - svn_boolean_t ret; + PyObject* py_path; + const char *path; + apr_pool_t *pool; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + svn_boolean_t modified; - if (!PyArg_ParseTuple(args, "s", &name)) - return NULL; + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; - pool = Pool(NULL); - if (pool == NULL) - return NULL; + pool = Pool(NULL); - ret = svn_wc_is_adm_dir(name, pool); + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } - apr_pool_destroy(pool); + RUN_SVN_WITH_POOL(pool, svn_wc_text_modified_p2(&modified, wc_context, + path, FALSE, pool)); - return PyBool_FromLong(ret); + apr_pool_destroy(pool); + + return PyBool_FromLong(modified); } -static PyObject *is_wc_prop(PyObject *self, PyObject *args) +static PyObject *py_wc_context_props_modified_p2(PyObject *self, PyObject *args) { - char *name; + PyObject* py_path; + const char *path; + apr_pool_t *pool; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + svn_boolean_t modified; - if (!PyArg_ParseTuple(args, "s", &name)) - return NULL; + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; - return PyBool_FromLong(svn_wc_is_wc_prop(name)); + pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_props_modified_p2(&modified, wc_context, + path, pool)); + + apr_pool_destroy(pool); + + return PyBool_FromLong(modified); } -static PyObject *is_entry_prop(PyObject *self, PyObject *args) +static PyObject *py_wc_context_conflicted(PyObject *self, PyObject *args) { - char *name; + PyObject* py_path; + const char *path; + apr_pool_t *pool; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + svn_boolean_t text_conflicted, props_conflicted, tree_conflicted; - if (!PyArg_ParseTuple(args, "s", &name)) - return NULL; + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; - return PyBool_FromLong(svn_wc_is_entry_prop(name)); -} + pool = Pool(NULL); -static PyObject *get_adm_dir(PyObject *self) + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_conflicted_p3( + &text_conflicted, &props_conflicted, &tree_conflicted, wc_context, + path, pool)); + + apr_pool_destroy(pool); + + return Py_BuildValue("(bbb)", text_conflicted, props_conflicted, tree_conflicted); +} + +static PyObject *py_wc_context_crawl_revisions(PyObject *self, PyObject *args, PyObject *kwargs) { - apr_pool_t *pool; - PyObject *ret; - const char *dir; - pool = Pool(NULL); - if (pool == NULL) - return NULL; - dir = svn_wc_get_adm_dir(pool); - ret = py_object_from_svn_abspath(dir); - apr_pool_destroy(pool); - return ret; + PyObject* py_path, *py_reporter; + const char *path; + apr_pool_t *pool; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + char *kwnames[] = { "path", "reporter", "restore_files", "depth", + "honor_depth_exclude", "depth_compatibility_trick", "use_commit_times", + "cancel", "notify", NULL }; + bool restore_files = false; + int depth = svn_depth_infinity; + bool honor_depth_exclude = true; + bool depth_compatibility_trick = false; + bool use_commit_times = false; + PyObject *notify = Py_None; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bibbbOO", kwnames, + &py_path, &py_reporter, &restore_files, + &depth, &honor_depth_exclude, + &depth_compatibility_trick, + &use_commit_times, ¬ify)) { + return NULL; + } + + pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_crawl_revisions5( + wc_context, path, &py_ra_reporter3, py_reporter, restore_files, + depth, honor_depth_exclude, depth_compatibility_trick, + use_commit_times, py_cancel_check, NULL, py_wc_notify_func, notify, pool)); + + apr_pool_destroy(pool); + + Py_RETURN_NONE; +} + +static void context_done_handler(void *self) +{ + PyObject *selfobj = (PyObject *)self; + + Py_DECREF(selfobj); +} + +static PyObject *py_wc_context_get_update_editor(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *kwnames[] = { + "anchor_abspath", "target_basename", "use_commit_times", "depth", + "depth_is_sticky", "allow_unver_obstructions", "adds_as_modification", + "server_performs_filtering", "clean_checkout", "diff3_cmd", + "preserved_exts", "dirents_func", "conflict_func", "external_func", + "notify_func", NULL }; + const svn_delta_editor_t *editor; + void *edit_baton; + const char *anchor_abspath; + char *target_basename; + char *diff3_cmd = NULL; + svn_wc_context_t *wc_context = ((ContextObject *)self)->context; + bool use_commit_times = false; + int depth = svn_depth_infinity; + bool depth_is_sticky = false; + bool allow_unver_obstructions = true; + bool adds_as_modification = false; + bool server_performs_filtering = false; + bool clean_checkout = false; + apr_array_header_t *preserved_exts = NULL; + PyObject *py_preserved_exts = Py_None; + PyObject *dirents_func = Py_None; + PyObject *conflict_func = Py_None; + PyObject *external_func = Py_None; + PyObject *notify_func = Py_None; + PyObject *py_anchor_abspath; + apr_pool_t *result_pool, *scratch_pool; + svn_error_t *err; + svn_revnum_t target_revision; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|bibbbbbzOOOOO", kwnames, + &py_anchor_abspath, &target_basename, + &use_commit_times, &depth, + &depth_is_sticky, + &allow_unver_obstructions, + &adds_as_modification, + &server_performs_filtering, + &clean_checkout, &py_preserved_exts, + &dirents_func, &conflict_func, + &external_func, ¬ify_func)) { + return NULL; + } + + if (conflict_func != Py_None) { + // TODO + PyErr_SetString(PyExc_NotImplementedError, + "conflict_func is not currently supported"); + return NULL; + } + + if (external_func != Py_None) { + // TODO + PyErr_SetString(PyExc_NotImplementedError, + "external_func is not currently supported"); + return NULL; + } + + if (dirents_func != Py_None) { + // TODO + PyErr_SetString(PyExc_NotImplementedError, + "dirents_func is not currently supported"); + return NULL; + } + + scratch_pool = Pool(NULL); + + anchor_abspath = py_object_to_svn_abspath(py_anchor_abspath, scratch_pool); + + if (py_preserved_exts != Py_None) { + if (!string_list_to_apr_array(scratch_pool, py_preserved_exts, &preserved_exts)) { + apr_pool_destroy(scratch_pool); + return NULL; + } + } + + result_pool = Pool(NULL); + + Py_BEGIN_ALLOW_THREADS + err = svn_wc_get_update_editor4( + &editor, &edit_baton, &target_revision, wc_context, + anchor_abspath, target_basename, use_commit_times, depth, + depth_is_sticky, allow_unver_obstructions, adds_as_modification, + server_performs_filtering, clean_checkout, diff3_cmd, + preserved_exts, NULL, dirents_func, NULL, conflict_func, NULL, + external_func, py_cancel_check, NULL, py_wc_notify_func, + notify_func, result_pool, scratch_pool); + Py_END_ALLOW_THREADS + + apr_pool_destroy(scratch_pool); + + if (err != NULL) { + handle_svn_error(err); + svn_error_clear(err); + apr_pool_destroy(result_pool); + return NULL; + } + + /* TODO: Also return target_revision ? */ + Py_INCREF(self); + return new_editor_object(NULL, editor, edit_baton, result_pool, &Editor_Type, + context_done_handler, self, NULL); +} + +static PyObject *py_wc_context_ensure_adm(PyObject *self, PyObject *args, + PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + char *kwnames[] = { + "local_abspath", "url", "repos_root_url", "repos_uuid", + "revnum", "depth", NULL }; + char *local_abspath; + char *url; + char *repos_root_url; + char *repos_uuid; + int revnum; + int depth = svn_depth_infinity; + apr_pool_t *pool; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ssssi|i", kwnames, + &local_abspath, &url, &repos_root_url, + &repos_uuid, &revnum, &depth)) { + return NULL; + } + + pool = Pool(NULL); + + RUN_SVN_WITH_POOL(pool, svn_wc_ensure_adm4(context_obj->context, + local_abspath, url, + repos_root_url, repos_uuid, + revnum, depth, pool)); + + apr_pool_destroy(pool); + + Py_RETURN_NONE; } -static PyObject *set_adm_dir(PyObject *self, PyObject *args) +typedef struct { + PyObject_VAR_HEAD + apr_pool_t *pool; + svn_wc_status3_t status; +} Status3Object; + +static void status_dealloc(PyObject *self) { - apr_pool_t *temp_pool; - char *name; - PyObject *py_name; + apr_pool_t *pool = ((Status3Object *)self)->pool; + if (pool != NULL) + apr_pool_destroy(pool); + PyObject_Del(self); +} - if (!PyArg_ParseTuple(args, "O", &py_name)) - return NULL; +static PyMemberDef status_members[] = { + { "kind", T_INT, offsetof(Status3Object, status.kind), READONLY, + "The kind of node as recorded in the working copy." }, + { "depth", T_INT, offsetof(Status3Object, status.depth), READONLY, + "The depth of the node as recorded in the working copy." }, + { "filesize", T_LONG, offsetof(Status3Object, status.filesize), READONLY, + "The actual size of the working file on disk, or SVN_INVALID_FILESIZE" + "if unknown (or if the item isn't a file at all)" }, + { "versioned", T_BOOL, offsetof(Status3Object, status.versioned), READONLY, + "If the path is under version control, versioned is TRUE, " + "otherwise FALSE." }, + { "repos_uuid", T_STRING, offsetof(Status3Object, status.repos_uuid), READONLY, + "UUID of repository" }, + { "repos_root_url", T_STRING, offsetof(Status3Object, status.repos_root_url), READONLY, + "Repository root URL" }, + { "repos_relpath", T_STRING, offsetof(Status3Object, status.repos_relpath), READONLY, + "Relative path in repository" }, + /* TODO */ + { NULL } +}; - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - name = py_object_to_svn_string(py_name, temp_pool); - if (name == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } - RUN_SVN_WITH_POOL(temp_pool, svn_wc_set_adm_dir(name, temp_pool)); - apr_pool_destroy(temp_pool); - Py_RETURN_NONE; -} +static PyTypeObject Status3_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "wc.Status", /* const char *tp_name; For printing, in format "<module>.<name>" */ + sizeof(Status3Object), + 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ -static PyObject *get_pristine_copy_path(PyObject *self, PyObject *args) -{ - apr_pool_t *pool; - const char *pristine_path; - const char *path; - PyObject *py_path; - PyObject *ret; + /* Methods to implement standard operations */ - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; + status_dealloc, /* destructor tp_dealloc; */ + NULL, /* printfunc tp_print; */ + NULL, /* getattrfunc tp_getattr; */ + NULL, /* setattrfunc tp_setattr; */ + NULL, /* cmpfunc tp_compare; */ + NULL, /* reprfunc tp_repr; */ - pool = Pool(NULL); - if (pool == NULL) - return NULL; + /* Method suites for standard classes */ - path = py_object_to_svn_dirent(py_path, pool); - if (path == NULL) { - apr_pool_destroy(pool); - return NULL; - } + NULL, /* PyNumberMethods *tp_as_number; */ + NULL, /* PySequenceMethods *tp_as_sequence; */ + NULL, /* PyMappingMethods *tp_as_mapping; */ - PyErr_WarnEx(PyExc_DeprecationWarning, "get_pristine_copy_path is deprecated. Use get_pristine_contents instead.", 2); - RUN_SVN_WITH_POOL(pool, - svn_wc_get_pristine_copy_path(path, - &pristine_path, pool)); - ret = py_object_from_svn_abspath(pristine_path); - apr_pool_destroy(pool); - return ret; -} + /* More standard operations (here for binary compatibility) */ -static PyObject *get_pristine_contents(PyObject *self, PyObject *args) -{ - const char *path; - apr_pool_t *temp_pool; - PyObject *py_path; -#if ONLY_SINCE_SVN(1, 6) - apr_pool_t *stream_pool; - StreamObject *ret; - svn_stream_t *stream; -#else - PyObject *ret; - const char *pristine_path; -#endif + NULL, /* hashfunc tp_hash; */ + NULL, /* ternaryfunc tp_call; */ + NULL, /* reprfunc tp_str; */ + NULL, /* getattrofunc tp_getattro; */ + NULL, /* setattrofunc tp_setattro; */ - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; + /* Functions to access object as input/output buffer */ + NULL, /* PyBufferProcs *tp_as_buffer; */ -#if ONLY_SINCE_SVN(1, 6) - stream_pool = Pool(NULL); - if (stream_pool == NULL) - return NULL; + /* Flags to define presence of optional/expanded features */ + 0, /* long tp_flags; */ - temp_pool = Pool(stream_pool); - if (temp_pool == NULL) { - apr_pool_destroy(stream_pool); - return NULL; - } + NULL, /* const char *tp_doc; Documentation string */ - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); - return NULL; - } + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + NULL, /* traverseproc tp_traverse; */ - RUN_SVN_WITH_POOL(stream_pool, svn_wc_get_pristine_contents(&stream, path, stream_pool, temp_pool)); - apr_pool_destroy(temp_pool); + /* delete references to contained objects */ + NULL, /* inquiry tp_clear; */ - if (stream == NULL) { - apr_pool_destroy(stream_pool); - Py_RETURN_NONE; - } + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + NULL, /* richcmpfunc tp_richcompare; */ - ret = PyObject_New(StreamObject, &Stream_Type); - if (ret == NULL) - return NULL; + /* weak reference enabler */ + 0, /* Py_ssize_t tp_weaklistoffset; */ - ret->pool = stream_pool; - ret->closed = FALSE; - ret->stream = stream; + /* Added in release 2.2 */ + /* Iterators */ + NULL, /* getiterfunc tp_iter; */ + NULL, /* iternextfunc tp_iternext; */ - return (PyObject *)ret; + /* Attribute descriptor and subclassing stuff */ + NULL, /* struct PyMethodDef *tp_methods; */ + status_members, /* struct PyMemberDef *tp_members; */ + NULL, /* struct PyGetSetDef *tp_getsetters; */ +}; + +static PyObject *py_wc_status(PyObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + char *kwnames[] = {"path", NULL}; + PyObject *py_path; + Status3Object *ret; + const char *path; + apr_pool_t *scratch_pool, *result_pool; + svn_wc_status3_t* status; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwnames, &py_path)) { + return NULL; + } + + result_pool = Pool(NULL); + if (result_pool == NULL) { + return NULL; + } + scratch_pool = Pool(result_pool); + if (scratch_pool == NULL) { + apr_pool_destroy(result_pool); + return NULL; + } + + path = py_object_to_svn_abspath(py_path, scratch_pool); + if (path == NULL) { + apr_pool_destroy(result_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(result_pool, + svn_wc_status3(&status, context_obj->context, path, + result_pool, scratch_pool)); + + apr_pool_destroy(scratch_pool); + + ret = PyObject_New(Status3Object, &Status3_Type); + if (ret == NULL) { + apr_pool_destroy(result_pool); + return NULL; + } + ret->pool = result_pool; + ret->status = *status; + return (PyObject *)ret; +} + +static svn_error_t *py_status_receiver(void *baton, const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + Status3Object *py_status; + PyObject *ret; + PyGILState_STATE state; + + if (baton == Py_None) + return NULL; + + state = PyGILState_Ensure(); + + py_status = PyObject_New(Status3Object, &Status3_Type); + if (py_status == NULL) { + PyGILState_Release(state); + return py_svn_error(); + } + py_status->pool = Pool(NULL); + py_status->status = *svn_wc_dup_status3(status, py_status->pool); + + ret = PyObject_CallFunction((PyObject *)baton, "sO", local_abspath, py_status); + Py_DECREF(py_status); + + if (ret == NULL) { + PyGILState_Release(state); + return py_svn_error(); + } + + Py_DECREF(ret); + PyGILState_Release(state); + + return NULL; +} + +static PyObject *py_wc_walk_status(PyObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + char *kwnames[] = {"path", "receiver", "depth", "get_all", "no_ignore", + "ignore_text_mode", "ignore_patterns", NULL}; + PyObject *py_path; + const char *path; + int depth = svn_depth_infinity; + bool get_all = true; + bool no_ignore = false; + bool ignore_text_mode = false; + PyObject *py_ignore_patterns = Py_None; + PyObject *status_func; + apr_array_header_t *ignore_patterns; + apr_pool_t *pool; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|ibbbOO", kwnames, + &py_path, &status_func, &depth, &get_all, &no_ignore, + &ignore_text_mode, &py_ignore_patterns)) { + return NULL; + } + + pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + if (py_ignore_patterns == Py_None) { + ignore_patterns = NULL; + } else { + if (!string_list_to_apr_array(pool, py_ignore_patterns, &ignore_patterns)) { + apr_pool_destroy(pool); + return NULL; + } + } + + RUN_SVN_WITH_POOL(pool, + svn_wc_walk_status(context_obj->context, path, depth, + get_all, no_ignore, ignore_text_mode, + ignore_patterns, py_status_receiver, + status_func, py_cancel_check, NULL, + pool)); + + apr_pool_destroy(pool); + + Py_RETURN_NONE; +} + +static PyObject *py_wc_add_lock(PyObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + PyObject *py_path, *py_lock; + svn_lock_t *lock; + char *kwnames[] = { "path", "lock", NULL }; + const char *path; + apr_pool_t *scratch_pool; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO!", kwnames, &py_path, &Lock_Type, + &py_lock)) { + return NULL; + } + + scratch_pool = Pool(NULL); + if (scratch_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, scratch_pool); + if (path == NULL) { + apr_pool_destroy(scratch_pool); + return NULL; + } + + lock = py_object_to_svn_lock(py_lock, scratch_pool); + if (lock == NULL) { + apr_pool_destroy(scratch_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(scratch_pool, + svn_wc_add_lock2(context_obj->context, path, lock, scratch_pool)); + + apr_pool_destroy(scratch_pool); + + Py_RETURN_NONE; +} + +static PyObject *py_wc_remove_lock(PyObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + char *kwnames[] = { "path", NULL }; + PyObject *py_path; + const char *path; + apr_pool_t *scratch_pool; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwnames, &py_path)) { + return NULL; + } + + scratch_pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, scratch_pool); + if (path == NULL) { + apr_pool_destroy(scratch_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(scratch_pool, + svn_wc_remove_lock2(context_obj->context, path, + scratch_pool)); + + apr_pool_destroy(scratch_pool); + Py_RETURN_NONE; +} + +static PyObject *py_wc_add_from_disk(PyObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + char *kwnames[] = {"path", "props", "skip_checks", "notify", NULL }; + PyObject *py_path; + const char *path; + bool skip_checks = false; + PyObject *py_props = Py_None; + PyObject *notify_func = Py_None; + apr_pool_t *pool; + apr_hash_t *props; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|ObO", kwnames, + &py_path, &py_props, &skip_checks, ¬ify_func)) { + return NULL; + } + + pool = Pool(NULL); + if (pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + if (py_props == Py_None) { + props = NULL; + } else { + props = prop_dict_to_hash(pool, py_props); + if (props == NULL) { + apr_pool_destroy(pool); + return NULL; + } + } + +#if ONLY_SINCE_SVN(1, 9) + RUN_SVN_WITH_POOL( + pool, svn_wc_add_from_disk3( + context_obj->context, path, props, skip_checks, + notify_func == Py_None?NULL:py_wc_notify_func, + notify_func, pool)); #else - temp_pool = Pool(NULL); - if (temp_pool == NULL) - return NULL; - RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_pristine_copy_path(path, &pristine_path, temp_pool)); - ret = PyFile_FromString((char *)pristine_path, "rb"); - apr_pool_destroy(temp_pool); - return ret; + if (props != NULL) { + PyErr_SetString(PyExc_NotImplementedError, + "props argument only supported on svn >= 1.9"); + apr_pool_destroy(pool); + return NULL; + } + + if (skip_checks) { + PyErr_SetString(PyExc_NotImplementedError, + "skip_checks argument only supported on svn >= 1.9"); + apr_pool_destroy(pool); + return NULL; + } + RUN_SVN_WITH_POOL( + pool, svn_wc_add_from_disk( + context_obj->context, path, + notify_func == Py_None?NULL:py_wc_notify_func, + notify_func, pool)); #endif + + apr_pool_destroy(pool); + + Py_RETURN_NONE; } -static PyObject *ensure_adm(PyObject *self, PyObject *args, PyObject *kwargs) +static PyObject *py_wc_get_prop_diffs(PyObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *context_obj = (ContextObject *)self; + PyObject *py_path, *py_orig_props, *py_propchanges; + apr_pool_t *pool; + char *kwnames[] = {"path", NULL}; + apr_hash_t *original_props; + apr_array_header_t *propchanges; + const char *path; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwnames, &py_path)) { + return NULL; + } + + pool = Pool(NULL); + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_get_prop_diffs2(&propchanges, + &original_props, + context_obj->context, + path, pool, pool)); + + py_orig_props = prop_hash_to_dict(original_props); + if (py_orig_props == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + py_propchanges = propchanges_to_list(propchanges); + if (py_propchanges == NULL) { + apr_pool_destroy(pool); + Py_DECREF(py_propchanges); + return NULL; + } + + apr_pool_destroy(pool); + + return Py_BuildValue("NN", py_orig_props, py_propchanges); +} + +static PyObject *py_wc_context_process_committed_queue(PyObject *self, PyObject *args) +{ + apr_pool_t *temp_pool; + ContextObject *contextobj = (ContextObject *)self; + svn_revnum_t revnum; + char *date, *author; + PyObject *py_queue; + + if (!PyArg_ParseTuple(args, "O!lss", &CommittedQueue_Type, &py_queue, + &revnum, &date, &author)) + return NULL; + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + svn_wc_committed_queue_t *committed_queue = PyObject_GetCommittedQueue(py_queue); + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_process_committed_queue2(committed_queue, + contextobj->context, + revnum, date, author, + py_cancel_check, NULL, + temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + + + +static PyMethodDef context_methods[] = { + { "locked", py_wc_context_locked, METH_VARARGS, + "locked(path) -> (locked_here, locked)\n" + "Check whether a patch is locked."}, + { "check_wc", py_wc_context_check_wc, METH_VARARGS, + "check_wc(path) -> wc_format\n" + "Check format version of a working copy." }, + { "text_modified", py_wc_context_text_modified_p2, METH_VARARGS, + "text_modified(path) -> bool\n" + "Check whether text of a file is modified against base." }, + { "props_modified", py_wc_context_props_modified_p2, METH_VARARGS, + "props_modified(path) -> bool\n" + "Check whether props of a file are modified against base." }, + { "conflicted", py_wc_context_conflicted, METH_VARARGS, + "conflicted(path) -> (text_conflicted, prop_conflicted, " + "tree_conflicted)\n" + "Check whether a path is conflicted." }, + { "crawl_revisions", (PyCFunction)py_wc_context_crawl_revisions, + METH_VARARGS|METH_KEYWORDS, + "crawl_revisions(path, reporter, restore_files, depth, " + "honor_depth_exclude, depth_compatibility_trick, " + "use_commit_time, notify)\n" + "Do a depth-first crawl of the working copy." }, + { "get_update_editor", + (PyCFunction)py_wc_context_get_update_editor, + METH_VARARGS|METH_KEYWORDS, + "get_update_editor(anchor_abspath, target_basename, use_commit_time, " + "depth, depth_is_sticky, allow_unver_obstructions, " + "adds_as_modification, server_performs_filtering, clean_checkout, " + "diff3_cmd, dirent_func=None, conflict_func=None, " + "external_func=None) -> target_revnum" }, + { "ensure_adm", + (PyCFunction)py_wc_context_ensure_adm, + METH_VARARGS|METH_KEYWORDS, + "ensure_adm(local_abspath, url, repos_root_url, repos_uuid, revnum, depth)" }, + { "process_committed_queue", + (PyCFunction)py_wc_context_process_committed_queue, + METH_VARARGS|METH_KEYWORDS, + "" }, + { "status", + (PyCFunction)py_wc_status, + METH_VARARGS|METH_KEYWORDS, + "status(path) -> status" }, + { "walk_status", + (PyCFunction)py_wc_walk_status, + METH_VARARGS|METH_KEYWORDS, + "walk_status(path, receiver, depth=DEPTH_INFINITY, get_all=True, " + "no_ignore=False, ignore_text_mode=False, ignore_patterns=None)\n" }, + { "add_lock", + (PyCFunction)py_wc_add_lock, + METH_VARARGS|METH_KEYWORDS, + "add_lock(path, lock)" }, + { "remove_lock", + (PyCFunction)py_wc_remove_lock, + METH_VARARGS|METH_KEYWORDS, + "remove_lock(path)" }, + { "add_from_disk", + (PyCFunction)py_wc_add_from_disk, + METH_VARARGS|METH_KEYWORDS, + "add_from_disk(local_abspath, props=None, skip_checks=False, notify=None)" }, + { "get_prop_diffs", + (PyCFunction)py_wc_get_prop_diffs, + METH_VARARGS|METH_KEYWORDS, + "get_prop_diffs(path) -> (changes orig_props)" }, + { NULL } +}; + +static void context_dealloc(PyObject *self) { - const char *path; - char *uuid, *url; - PyObject *py_path; - char *repos=NULL; - svn_revnum_t rev=-1; - apr_pool_t *pool; - char *kwnames[] = { "path", "uuid", "url", "repos", "rev", "depth", NULL }; - svn_depth_t depth = svn_depth_infinity; + ContextObject *context_obj = (ContextObject *)self; + svn_wc_context_destroy(context_obj->context); + apr_pool_destroy(context_obj->pool); + PyObject_Del(self); +} - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oss|sli", kwnames, - &py_path, &uuid, &url, &repos, &rev, &depth)) - return NULL; +static PyObject *context_init(PyTypeObject *self, PyObject *args, PyObject *kwargs) +{ + ContextObject *ret; + char *kwnames[] = { NULL }; + svn_config_t *config = NULL; - pool = Pool(NULL); - if (pool == NULL) { - return NULL; - } + // TODO(jelmer): Support passing in config + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwnames)) + return NULL; - path = py_object_to_svn_dirent(py_path, pool); - if (path == NULL) { - apr_pool_destroy(pool); - return NULL; - } + ret = PyObject_New(ContextObject, &Context_Type); + if (ret == NULL) + return NULL; -#if ONLY_SINCE_SVN(1, 5) - RUN_SVN_WITH_POOL(pool, - svn_wc_ensure_adm3(path, - uuid, url, repos, rev, depth, pool)); -#else - if (depth != svn_depth_infinity) { - PyErr_SetString(PyExc_NotImplementedError, - "depth != infinity not supported with svn < 1.5"); - apr_pool_destroy(pool); - return NULL; - } - RUN_SVN_WITH_POOL(pool, - svn_wc_ensure_adm2(path, - uuid, url, repos, rev, pool)); -#endif - apr_pool_destroy(pool); - Py_RETURN_NONE; + ret->pool = Pool(NULL); + if (ret->pool == NULL) + return NULL; + RUN_SVN_WITH_POOL(ret->pool, svn_wc_context_create(&ret->context, config, + ret->pool, ret->pool)); + + return (PyObject *)ret; } -static PyObject *check_wc(PyObject *self, PyObject *args) -{ - const char *path; - apr_pool_t *pool; - int wc_format; - PyObject *py_path; +static PyTypeObject Context_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "wc.Context", /* const char *tp_name; For printing, in format "<module>.<name>" */ + sizeof(ContextObject), + 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ - if (!PyArg_ParseTuple(args, "O", &py_path)) - return NULL; + /* Methods to implement standard operations */ - pool = Pool(NULL); - if (pool == NULL) { - return NULL; - } + context_dealloc, /* destructor tp_dealloc; */ + NULL, /* printfunc tp_print; */ + NULL, /* getattrfunc tp_getattr; */ + NULL, /* setattrfunc tp_setattr; */ + NULL, /* cmpfunc tp_compare; */ + NULL, /* reprfunc tp_repr; */ - path = py_object_to_svn_dirent(py_path, pool); - if (path == NULL) { - apr_pool_destroy(pool); - return NULL; - } + /* Method suites for standard classes */ - RUN_SVN_WITH_POOL(pool, svn_wc_check_wc(path, &wc_format, pool)); - apr_pool_destroy(pool); - return PyLong_FromLong(wc_format); + NULL, /* PyNumberMethods *tp_as_number; */ + NULL, /* PySequenceMethods *tp_as_sequence; */ + NULL, /* PyMappingMethods *tp_as_mapping; */ + + /* More standard operations (here for binary compatibility) */ + + NULL, /* hashfunc tp_hash; */ + NULL, /* ternaryfunc tp_call; */ + NULL, /* reprfunc tp_str; */ + NULL, /* getattrofunc tp_getattro; */ + NULL, /* setattrofunc tp_setattro; */ + + /* Functions to access object as input/output buffer */ + NULL, /* PyBufferProcs *tp_as_buffer; */ + + /* Flags to define presence of optional/expanded features */ + 0, /* long tp_flags; */ + + "Context", /* const char *tp_doc; Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + NULL, /* traverseproc tp_traverse; */ + + /* delete references to contained objects */ + NULL, /* inquiry tp_clear; */ + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + NULL, /* richcmpfunc tp_richcompare; */ + + /* weak reference enabler */ + 0, /* Py_ssize_t tp_weaklistoffset; */ + + /* Added in release 2.2 */ + /* Iterators */ + NULL, /* getiterfunc tp_iter; */ + NULL, /* iternextfunc tp_iternext; */ + + /* Attribute descriptor and subclassing stuff */ + context_methods, /* struct PyMethodDef *tp_methods; */ + NULL, /* struct PyMemberDef *tp_members; */ + NULL, /* struct PyGetSetDef *tp_getset; */ + NULL, /* struct _typeobject *tp_base; */ + NULL, /* PyObject *tp_dict; */ + NULL, /* descrgetfunc tp_descr_get; */ + NULL, /* descrsetfunc tp_descr_set; */ + 0, /* Py_ssize_t tp_dictoffset; */ + NULL, /* initproc tp_init; */ + NULL, /* allocfunc tp_alloc; */ + context_init, /* newfunc tp_new; */ +}; + +#endif + +static void lock_dealloc(PyObject *self) +{ + LockObject *lockself = (LockObject *)self; + + apr_pool_destroy(lockself->pool); + + PyObject_Del(self); } -static PyObject *cleanup_wc(PyObject *self, PyObject *args, PyObject *kwargs) +static PyObject *lock_init(PyTypeObject *type, PyObject *args, PyObject *kwargs) { - const char *path; - char *diff3_cmd = NULL; - char *kwnames[] = { "path", "diff3_cmd", NULL }; - apr_pool_t *temp_pool; - PyObject *py_path; + char *kwnames[] = { "token", NULL }; + LockObject *ret; + char *token = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|z", kwnames, - &py_path, &diff3_cmd)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|z", kwnames, &token)) return NULL; - temp_pool = Pool(NULL); - if (temp_pool == NULL) { + ret = PyObject_New(LockObject, &Lock_Type); + if (ret == NULL) return NULL; - } - path = py_object_to_svn_dirent(py_path, temp_pool); - if (path == NULL) { - apr_pool_destroy(temp_pool); + ret->pool = Pool(NULL); + if (ret->pool == NULL) return NULL; - } + ret->lock = *svn_lock_create(ret->pool); + if (token != NULL) { + ret->lock.token = apr_pstrdup(ret->pool, token); + } - RUN_SVN_WITH_POOL(temp_pool, - svn_wc_cleanup2(path, diff3_cmd, py_cancel_check, NULL, - temp_pool)); - apr_pool_destroy(temp_pool); + return (PyObject *)ret; +} - Py_RETURN_NONE; +static PyObject *lock_get_path(PyObject *self, void *closure) { + LockObject *lock_obj = (LockObject *)self; + + if (lock_obj->lock.path == NULL) { + Py_RETURN_NONE; + } + + return PyUnicode_FromString(lock_obj->lock.path); } -static PyObject *match_ignore_list(PyObject *self, PyObject *args) -{ -#if ONLY_SINCE_SVN(1, 5) - char *str; - PyObject *py_list; - apr_array_header_t *list; - apr_pool_t *temp_pool; - svn_boolean_t ret; +static int lock_set_path(PyObject *self, PyObject *value, void *closure) { + LockObject *lock_obj = (LockObject *)self; + char *path; - if (!PyArg_ParseTuple(args, "sO", &str, &py_list)) - return NULL; + path = PyBytes_AsString(value); + if (path == NULL) { + return -1; + } - temp_pool = Pool(NULL); + lock_obj->lock.path = py_object_to_svn_string(value, lock_obj->pool); + return 0; +} - if (!string_list_to_apr_array(temp_pool, py_list, &list)) { - apr_pool_destroy(temp_pool); - return NULL; - } +static PyObject *lock_get_token(PyObject *self, void *closure) { + LockObject *lock_obj = (LockObject *)self; - ret = svn_wc_match_ignore_list(str, list, temp_pool); + if (lock_obj->lock.token == NULL) { + Py_RETURN_NONE; + } - apr_pool_destroy(temp_pool); + return PyBytes_FromString(lock_obj->lock.token); +} - return PyBool_FromLong(ret); -#else - PyErr_SetNone(PyExc_NotImplementedError); - return NULL; -#endif +static int lock_set_token(PyObject *self, PyObject *value, void *closure) { + LockObject *lock_obj = (LockObject *)self; + char *token; + + token = PyBytes_AsString(value); + if (token == NULL) { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + + lock_obj->lock.token = apr_pstrdup(lock_obj->pool, PyBytes_AsString(value)); + return 0; } -static PyMethodDef wc_methods[] = { - { "check_wc", check_wc, METH_VARARGS, "check_wc(path) -> version\n" - "Check whether path contains a Subversion working copy\n" - "return the workdir version"}, - { "cleanup", (PyCFunction)cleanup_wc, METH_VARARGS|METH_KEYWORDS, "cleanup(path, diff3_cmd=None)\n" }, - { "ensure_adm", (PyCFunction)ensure_adm, METH_KEYWORDS|METH_VARARGS, - "ensure_adm(path, uuid, url, repos=None, rev=None)" }, - { "get_adm_dir", (PyCFunction)get_adm_dir, METH_NOARGS, - "get_adm_dir() -> name" }, - { "set_adm_dir", (PyCFunction)set_adm_dir, METH_VARARGS, - "set_adm_dir(name)" }, - { "get_pristine_copy_path", get_pristine_copy_path, METH_VARARGS, - "get_pristine_copy_path(path) -> path" }, - { "get_pristine_contents", get_pristine_contents, METH_VARARGS, - "get_pristine_contents(path) -> stream" }, - { "is_adm_dir", is_adm_dir, METH_VARARGS, - "is_adm_dir(name) -> bool" }, - { "is_normal_prop", is_normal_prop, METH_VARARGS, - "is_normal_prop(name) -> bool" }, - { "is_entry_prop", is_entry_prop, METH_VARARGS, - "is_entry_prop(name) -> bool" }, - { "is_wc_prop", is_wc_prop, METH_VARARGS, - "is_wc_prop(name) -> bool" }, - { "revision_status", (PyCFunction)revision_status, METH_KEYWORDS|METH_VARARGS, "revision_status(wc_path, trail_url=None, committed=False) -> (min_rev, max_rev, switched, modified)" }, - { "version", (PyCFunction)version, METH_NOARGS, - "version() -> (major, minor, patch, tag)\n\n" - "Version of libsvn_wc currently used." - }, - { "api_version", (PyCFunction)api_version, METH_NOARGS, - "api_version() -> (major, minor, patch, tag)\n\n" - "Version of libsvn_wc Subvertpy was compiled against." - }, - { "match_ignore_list", (PyCFunction)match_ignore_list, METH_VARARGS, - "match_ignore_list(str, patterns) -> bool" }, - { "get_actual_target", (PyCFunction)get_actual_target, METH_VARARGS, - "get_actual_target(path) -> (anchor, target)" }, - { NULL, } +static PyGetSetDef lock_getsetters[] = { + { "path", lock_get_path, lock_set_path, + "the path this lock applies to"}, + { "token", lock_get_token, lock_set_token, + "unique URI representing lock"}, + { NULL }, +}; + +PyTypeObject Lock_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "wc.Lock", /* const char *tp_name; For printing, in format "<module>.<name>" */ + sizeof(LockObject), + 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ + + /* Methods to implement standard operations */ + + .tp_dealloc = lock_dealloc, /* destructor tp_dealloc; */ + + .tp_doc = "Lock", /* const char *tp_doc; Documentation string */ + + .tp_methods = NULL, /* struct PyMethodDef *tp_methods; */ + + .tp_new = lock_init, /* tp_new tp_new */ + + .tp_getset = lock_getsetters, }; static PyObject * @@ -2990,12 +2133,17 @@ moduleinit(void) if (PyType_Ready(&Entry_Type) < 0) return NULL; - if (PyType_Ready(&Status_Type) < 0) + if (PyType_Ready(&Status2_Type) < 0) return NULL; if (PyType_Ready(&Adm_Type) < 0) return NULL; +#if ONLY_SINCE_SVN(1, 7) + if (PyType_Ready(&Context_Type) < 0) + return NULL; +#endif + if (PyType_Ready(&Editor_Type) < 0) return NULL; @@ -3014,6 +2162,14 @@ moduleinit(void) if (PyType_Ready(&CommittedQueue_Type) < 0) return NULL; +#if ONLY_SINCE_SVN(1, 7) + if (PyType_Ready(&Status3_Type) < 0) + return NULL; +#endif + + if (PyType_Ready(&Lock_Type) < 0) + return NULL; + apr_initialize(); #if PY_MAJOR_VERSION >= 3 @@ -3089,15 +2245,18 @@ moduleinit(void) PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MERGED", svn_wc_conflict_choose_merged); #endif -#if ONLY_BEFORE_SVN(1, 7) - /* Subversion 1.7 has a couple of significant behaviour changes that break subvertpy. - * We haven't updated the code to deal with these changes in behaviour yet. - * */ - PyModule_AddObject(mod, "WorkingCopy", (PyObject *)&Adm_Type); + PyModule_AddObject(mod, "Adm", (PyObject *)&Adm_Type); Py_INCREF(&Adm_Type); + PyModule_AddObject(mod, "Lock", (PyObject *)&Lock_Type); + Py_INCREF(&Lock_Type); + PyModule_AddObject(mod, "CommittedQueue", (PyObject *)&CommittedQueue_Type); Py_INCREF(&CommittedQueue_Type); + +#if ONLY_SINCE_SVN(1, 7) + PyModule_AddObject(mod, "Context", (PyObject *)&Context_Type); + Py_INCREF(&Context_Type); #endif return mod; diff --git a/subvertpy/wc.h b/subvertpy/wc.h index 854611dd..ccd50d48 100644 --- a/subvertpy/wc.h +++ b/subvertpy/wc.h @@ -24,7 +24,28 @@ #pragma GCC visibility push(hidden) #endif +bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_array_header_t **ret); void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool); +PyObject *py_wc_status2(svn_wc_status2_t *status); +#if ONLY_SINCE_SVN(1, 5) +extern const svn_ra_reporter3_t py_ra_reporter3; +#endif +extern const svn_ra_reporter2_t py_ra_reporter2; + +#if ONLY_SINCE_SVN(1, 6) +svn_error_t *wc_validator3(void *baton, const char *uuid, const char *url, const char *root_url, apr_pool_t *pool); +#endif +svn_error_t *wc_validator2(void *baton, const char *uuid, const char *url, svn_boolean_t root, apr_pool_t *pool); +svn_wc_committed_queue_t *PyObject_GetCommittedQueue(PyObject *obj); +extern PyTypeObject CommittedQueue_Type; +svn_lock_t *py_object_to_svn_lock(PyObject *py_lock, apr_pool_t *pool); + +/* Provided by wc_adm.h */ +extern PyTypeObject Adm_Type; +extern PyTypeObject Entry_Type; +extern PyTypeObject Status2_Type; +svn_wc_adm_access_t *PyObject_GetAdmAccess(PyObject *obj); +extern PyTypeObject Lock_Type; #ifdef __GNUC__ #pragma GCC visibility pop diff --git a/subvertpy/wc_adm.c b/subvertpy/wc_adm.c new file mode 100644 index 00000000..07df1343 --- /dev/null +++ b/subvertpy/wc_adm.c @@ -0,0 +1,2237 @@ +/* + * Copyright © 2008 Jelmer Vernooij <jelmer@jelmer.uk> + * -*- coding: utf-8 -*- + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include <Python.h> +#include <apr_general.h> +#include <svn_wc.h> +#include <svn_path.h> +#include <svn_props.h> +#include <structmember.h> +#include <stdbool.h> +#include <apr_md5.h> +#include <apr_sha1.h> + +#include "util.h" +#include "editor.h" +#include "wc.h" + +/* Suppress warnings for this specific file, as it + * provides backwards compatibility with svn < 1.7 + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +static svn_wc_entry_callbacks2_t py_wc_entry_callbacks2; +static PyObject *py_entry(const svn_wc_entry_t *entry); + +typedef struct { + PyObject_VAR_HEAD + svn_wc_adm_access_t *adm; + apr_pool_t *pool; +} AdmObject; + +typedef struct { + PyObject_VAR_HEAD + apr_pool_t *pool; + svn_wc_entry_t entry; +} EntryObject; + +svn_wc_adm_access_t *Adm_GetAdmAccess(PyObject *obj) { + AdmObject *adm_obj = (AdmObject *)obj; + return adm_obj->adm; +} + +#define ADM_CHECK_CLOSED(adm_obj) \ + if (adm_obj->adm == NULL) { \ + PyErr_SetString(PyExc_RuntimeError, "WorkingCopy instance already closed"); \ + return NULL; \ + } + +svn_wc_adm_access_t *PyObject_GetAdmAccess(PyObject *obj) +{ + return ((AdmObject *)obj)->adm; +} + +static PyObject *adm_init(PyTypeObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *associated, *py_path; + const char *path; + bool write_lock=false; + int depth=0; + svn_wc_adm_access_t *parent_wc; + svn_error_t *err; + AdmObject *ret; + char *kwnames[] = { "associated", "path", "write_lock", "depth", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bi", kwnames, + &associated, &py_path, &write_lock, &depth)) + return NULL; + + ret = PyObject_New(AdmObject, &Adm_Type); + if (ret == NULL) + return NULL; + + ret->pool = Pool(NULL); + if (ret->pool == NULL) { + return NULL; + } + if (associated == Py_None) { + parent_wc = NULL; + } else { + parent_wc = ((AdmObject *)associated)->adm; + } + + path = py_object_to_svn_abspath(py_path, ret->pool); + if (path == NULL) { + Py_DECREF(ret); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + err = svn_wc_adm_open3(&ret->adm, parent_wc, + path, + write_lock, depth, py_cancel_check, NULL, + ret->pool); + Py_END_ALLOW_THREADS + + if (err != NULL) { + handle_svn_error(err); + svn_error_clear(err); + Py_DECREF(ret); + return NULL; + } + + return (PyObject *)ret; +} + +static PyObject *adm_access_path(PyObject *self) +{ + AdmObject *admobj = (AdmObject *)self; + ADM_CHECK_CLOSED(admobj); + return py_object_from_svn_abspath(svn_wc_adm_access_path(admobj->adm)); +} + +static PyObject *adm_locked(PyObject *self) +{ + AdmObject *admobj = (AdmObject *)self; + ADM_CHECK_CLOSED(admobj); + return PyBool_FromLong(svn_wc_adm_locked(admobj->adm)); +} + +static PyObject *adm_prop_get(PyObject *self, PyObject *args) +{ + char *name; + const char *path; + AdmObject *admobj = (AdmObject *)self; + const svn_string_t *value; + apr_pool_t *temp_pool; + PyObject *ret, *py_path; + + if (!PyArg_ParseTuple(args, "sO", &name, &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_get(&value, name, path, admobj->adm, temp_pool)); + if (value == NULL || value->data == NULL) { + ret = Py_None; + Py_INCREF(ret); + } else { + ret = PyBytes_FromStringAndSize(value->data, value->len); + } + apr_pool_destroy(temp_pool); + return ret; +} + +static PyObject *adm_prop_set(PyObject *self, PyObject *args) +{ + char *name, *value; + const char *path; + AdmObject *admobj = (AdmObject *)self; + bool skip_checks=false; + apr_pool_t *temp_pool; + int vallen; + svn_string_t *cvalue; + PyObject *py_path; + PyObject *notify_func = Py_None; + + if (!PyArg_ParseTuple(args, "sz#O|bO", &name, &value, &vallen, &py_path, &skip_checks, + ¬ify_func)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + if (value == NULL) { + cvalue = NULL; + } else { + cvalue = svn_string_ncreate(value, vallen, temp_pool); + } +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set3(name, cvalue, path, admobj->adm, + skip_checks, py_wc_notify_func, (void *)notify_func, + temp_pool)); +#else + RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set2(name, cvalue, path, admobj->adm, + skip_checks, temp_pool)); +#endif + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *adm_entries_read(PyObject *self, PyObject *args) +{ + apr_hash_t *entries; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + bool show_hidden=false; + apr_hash_index_t *idx; + const char *key; + apr_ssize_t klen; + svn_wc_entry_t *entry; + PyObject *py_entries, *obj; + + if (!PyArg_ParseTuple(args, "|b", &show_hidden)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + RUN_SVN_WITH_POOL(temp_pool, svn_wc_entries_read(&entries, admobj->adm, + show_hidden, temp_pool)); + py_entries = PyDict_New(); + if (py_entries == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + idx = apr_hash_first(temp_pool, entries); + while (idx != NULL) { + apr_hash_this(idx, (const void **)&key, &klen, (void **)&entry); + if (entry == NULL) { + obj = Py_None; + Py_INCREF(obj); + } else { + obj = py_entry(entry); + } + PyDict_SetItemString(py_entries, key, obj); + Py_DECREF(obj); + idx = apr_hash_next(idx); + } + apr_pool_destroy(temp_pool); + return py_entries; +} + +static PyObject *adm_walk_entries(PyObject *self, PyObject *args) +{ + const char *path; + PyObject *callbacks; + bool show_hidden=false; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + svn_depth_t depth = svn_depth_infinity; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "OO|bi", &py_path, &callbacks, &show_hidden, &depth)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries3( + path, admobj->adm, + &py_wc_entry_callbacks2, (void *)callbacks, + depth, show_hidden, py_cancel_check, NULL, + temp_pool)); +#else + if (depth != svn_depth_infinity) { + PyErr_SetString(PyExc_NotImplementedError, + "depth != infinity not supported for svn < 1.5"); + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries2( + path, admobj->adm, + &py_wc_entry_callbacks, (void *)callbacks, + show_hidden, py_cancel_check, NULL, + temp_pool)); +#endif + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *adm_entry(PyObject *self, PyObject *args) +{ + const char *path; + PyObject *py_path; + bool show_hidden=false; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + const svn_wc_entry_t *entry; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "O|b", &py_path, &show_hidden)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_entry(&entry, path, admobj->adm, show_hidden, temp_pool)); + + if (entry == NULL) { + PyErr_Format(PyExc_KeyError, "No such entry '%s'", path); + ret = NULL; + } else { + ret = py_entry(entry); + } + + apr_pool_destroy(temp_pool); + return ret; +} + +static PyObject *adm_get_prop_diffs(PyObject *self, PyObject *args) +{ + const char *path; + apr_pool_t *temp_pool; + apr_array_header_t *propchanges; + apr_hash_t *original_props; + PyObject *py_path; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_propchanges, *py_orig_props; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_prop_diffs(&propchanges, &original_props, + path, admobj->adm, temp_pool)); + py_propchanges = propchanges_to_list(propchanges); + if (py_propchanges == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + py_orig_props = prop_hash_to_dict(original_props); + apr_pool_destroy(temp_pool); + if (py_orig_props == NULL) { + Py_DECREF(py_propchanges); + return NULL; + } + return Py_BuildValue("(NN)", py_propchanges, py_orig_props); +} + +static PyObject *adm_add(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *path; + const char *copyfrom_url = NULL; + svn_revnum_t copyfrom_rev=-1; + char *kwnames[] = { "path", "copyfrom_url", "copyfrom_rev", "notify_func", "depth", NULL }; + PyObject *notify_func=Py_None, *py_path; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + svn_depth_t depth = svn_depth_infinity; + PyObject *py_copyfrom_url = Py_None; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OlOi", kwnames, &py_path, + &py_copyfrom_url, ©from_rev, ¬ify_func, &depth)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + if (py_copyfrom_url != Py_None) { + copyfrom_url = py_object_to_svn_uri(py_copyfrom_url, temp_pool); + if (copyfrom_url == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + } else { + copyfrom_url = NULL; + } + +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_add3( + path, admobj->adm, + depth, copyfrom_url, + copyfrom_rev, py_cancel_check, NULL, + py_wc_notify_func, + (void *)notify_func, + temp_pool)); +#else + if (depth != svn_depth_infinity) { + PyErr_SetString(PyExc_NotImplementedError, "depth != infinity not supported on svn < 1.6"); + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, svn_wc_add2( + path, admobj->adm, copyfrom_url, + copyfrom_rev, py_cancel_check, + NULL, + py_wc_notify_func, + (void *)notify_func, + temp_pool)); +#endif + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *adm_copy(PyObject *self, PyObject *args) +{ + AdmObject *admobj = (AdmObject *)self; + char *src, *dst; + PyObject *notify_func=Py_None; + apr_pool_t *temp_pool; + + if (!PyArg_ParseTuple(args, "ss|O", &src, &dst, ¬ify_func)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + RUN_SVN_WITH_POOL(temp_pool, svn_wc_copy2(src, admobj->adm, dst, + py_cancel_check, NULL, + py_wc_notify_func, (void *)notify_func, + temp_pool)); + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *adm_delete(PyObject *self, PyObject *args, PyObject *kwargs) +{ + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + char *kwnames[] = { "path", "notify_func", "keep_local", + NULL }; + const char *path; + PyObject *py_path; + PyObject *notify_func=Py_None; + bool keep_local = false; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Ob:delete", kwnames, + &py_path, ¬ify_func, &keep_local)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete3(path, admobj->adm, + py_cancel_check, NULL, + py_wc_notify_func, (void *)notify_func, + keep_local, + temp_pool)); +#else + if (keep_local) { + PyErr_SetString(PyExc_NotImplementedError, + "keep_local not supported on Subversion < 1.5"); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete2(path, admobj->adm, + py_cancel_check, NULL, + py_wc_notify_func, (void *)notify_func, + temp_pool)); +#endif + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *adm_crawl_revisions(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *path; + PyObject *reporter; + bool restore_files=true, recurse=true, use_commit_times=true; + PyObject *notify_func=Py_None; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + svn_wc_traversal_info_t *traversal_info; + bool depth_compatibility_trick = false; + bool honor_depth_exclude = false; + char *kwnames[] = { "path", "reporter", "restore_files", "recurse", + "use_commit_times", "notify_func", "depth_compatibility_trick", + "honor_depth_exclude,", NULL }; + PyObject *py_path; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|bbbObb", kwnames, &py_path, + &reporter, &restore_files, &recurse, &use_commit_times, + ¬ify_func, &depth_compatibility_trick, &honor_depth_exclude)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + traversal_info = svn_wc_init_traversal_info(temp_pool); +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions4(path, admobj->adm, + &py_ra_reporter3, (void *)reporter, + restore_files, recurse?svn_depth_infinity:svn_depth_files, + honor_depth_exclude?TRUE:FALSE, + depth_compatibility_trick?TRUE:FALSE, use_commit_times, + py_wc_notify_func, (void *)notify_func, + traversal_info, temp_pool)); +#elif ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions3(path, admobj->adm, + &py_ra_reporter3, (void *)reporter, + restore_files, recurse?svn_depth_infinity:svn_depth_files, + depth_compatibility_trick, use_commit_times, + py_wc_notify_func, (void *)notify_func, + traversal_info, temp_pool)); +#else + RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions2(path, admobj->adm, + &py_ra_reporter2, (void *)reporter, + restore_files, recurse, use_commit_times, + py_wc_notify_func, (void *)notify_func, + traversal_info, temp_pool)); +#endif + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static void wc_done_handler(void *self) +{ + AdmObject *admobj = (AdmObject *)self; + + Py_DECREF(admobj); +} + +static PyObject *adm_get_switch_editor(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *target; + bool use_commit_times=true; + PyObject * notify_func=Py_None; + char *diff3_cmd=NULL; + const svn_delta_editor_t *editor; + AdmObject *admobj = (AdmObject *)self; + void *edit_baton; + apr_pool_t *pool; + svn_revnum_t *latest_revnum; + svn_error_t *err; + bool allow_unver_obstructions = false; + bool depth_is_sticky = false; + int depth = svn_depth_infinity; + const char *switch_url; + PyObject *py_target, *py_switch_url; + char *kwnames[] = { + "target", "switch_url", "use_commit_times", "depth", "notify_func", + "diff3_cmd", "depth_is_sticky", "allow_unver_obstructions", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|biOzbb", kwnames, + &py_target, &py_switch_url, &use_commit_times, + &depth, ¬ify_func, &diff3_cmd, + &depth_is_sticky, + &allow_unver_obstructions)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + pool = Pool(NULL); + if (pool == NULL) + return NULL; + + target = py_object_to_svn_string(py_target, pool); + if (target == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + switch_url = py_object_to_svn_uri(py_switch_url, pool); + if (switch_url == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + latest_revnum = (svn_revnum_t *)apr_palloc(pool, sizeof(svn_revnum_t)); + Py_BEGIN_ALLOW_THREADS +#if ONLY_SINCE_SVN(1, 5) + /* FIXME: Support fetch_func */ + /* FIXME: Support conflict func */ + err = svn_wc_get_switch_editor3(latest_revnum, admobj->adm, target, switch_url, + use_commit_times, depth, + depth_is_sticky?TRUE:FALSE, allow_unver_obstructions?TRUE:FALSE, + py_wc_notify_func, (void *)notify_func, + py_cancel_check, NULL, + NULL, NULL, diff3_cmd, NULL, &editor, + &edit_baton, NULL, pool); +#else + if (allow_unver_obstructions) { + PyErr_SetString(PyExc_NotImplementedError, + "allow_unver_obstructions is not supported in svn < 1.5"); + apr_pool_destroy(pool); + PyEval_RestoreThread(_save); + return NULL; + } + if (depth_is_sticky) { + PyErr_SetString(PyExc_NotImplementedError, + "depth_is_sticky is not supported in svn < 1.5"); + apr_pool_destroy(pool); + PyEval_RestoreThread(_save); + return NULL; + } + err = svn_wc_get_switch_editor2(latest_revnum, admobj->adm, target, switch_url, + use_commit_times, recurse, py_wc_notify_func, (void *)notify_func, + py_cancel_check, NULL, diff3_cmd, &editor, &edit_baton, + NULL, pool); +#endif + Py_END_ALLOW_THREADS + if (err != NULL) { + handle_svn_error(err); + svn_error_clear(err); + apr_pool_destroy(pool); + return NULL; + } + Py_INCREF(admobj); + return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, + wc_done_handler, admobj, NULL); +} + +static PyObject *adm_get_update_editor(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *target; + bool use_commit_times=true; + PyObject * notify_func=Py_None; + char *diff3_cmd=NULL; + const svn_delta_editor_t *editor; + AdmObject *admobj = (AdmObject *)self; + void *edit_baton; + apr_pool_t *pool; + svn_revnum_t *latest_revnum; + svn_error_t *err; + bool allow_unver_obstructions = false; + bool depth_is_sticky = false; + int depth = svn_depth_infinity; + PyObject *py_target; + char *kwnames[] = { + "target", "use_commit_times", "depth", "notify_func", + "diff3_cmd", "depth_is_sticky", "allow_unver_obstructions", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|biOzbb", kwnames, + &py_target, &use_commit_times, + &depth, ¬ify_func, &diff3_cmd, + &depth_is_sticky, + &allow_unver_obstructions)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + pool = Pool(NULL); + if (pool == NULL) + return NULL; + + target = py_object_to_svn_string(py_target, pool); + if (target == NULL) { + apr_pool_destroy(pool); + return NULL; + } + latest_revnum = (svn_revnum_t *)apr_palloc(pool, sizeof(svn_revnum_t)); + Py_BEGIN_ALLOW_THREADS +#if ONLY_SINCE_SVN(1, 5) + /* FIXME: Support fetch_func */ + /* FIXME: Support conflict func */ + err = svn_wc_get_update_editor3(latest_revnum, admobj->adm, target, + use_commit_times, depth, + depth_is_sticky?TRUE:FALSE, allow_unver_obstructions?TRUE:FALSE, + py_wc_notify_func, (void *)notify_func, + py_cancel_check, NULL, + NULL, NULL, NULL, NULL, + diff3_cmd, NULL, &editor, &edit_baton, + NULL, pool); +#else + if (allow_unver_obstructions) { + PyErr_SetString(PyExc_NotImplementedError, + "allow_unver_obstructions is not supported in svn < 1.5"); + apr_pool_destroy(pool); + PyEval_RestoreThread(_save); + return NULL; + } + if (depth_is_sticky) { + PyErr_SetString(PyExc_NotImplementedError, + "depth_is_sticky is not supported in svn < 1.5"); + apr_pool_destroy(pool); + PyEval_RestoreThread(_save); + return NULL; + } + err = svn_wc_get_update_editor2(latest_revnum, admobj->adm, target, + use_commit_times, recurse, py_wc_notify_func, (void *)notify_func, + py_cancel_check, NULL, diff3_cmd, &editor, &edit_baton, + NULL, pool); +#endif + Py_END_ALLOW_THREADS + if (err != NULL) { + handle_svn_error(err); + svn_error_clear(err); + apr_pool_destroy(pool); + return NULL; + } + Py_INCREF(admobj); + return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, + wc_done_handler, admobj, NULL); +} + +static PyObject *adm_has_binary_prop(PyObject *self, PyObject *args) +{ + const char *path; + svn_boolean_t binary; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_has_binary_prop(&binary, path, admobj->adm, temp_pool)); + + apr_pool_destroy(temp_pool); + + return PyBool_FromLong(binary); +} + +static PyObject *adm_process_committed(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *path; + char *rev_date = NULL, *rev_author = NULL; + bool recurse, remove_lock = false; + unsigned char *digest = NULL; + svn_revnum_t new_revnum; + PyObject *py_wcprop_changes = Py_None, *py_path; + apr_array_header_t *wcprop_changes = NULL; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + int digest_len; + bool remove_changelist = false; + char *kwnames[] = { "path", "recurse", "new_revnum", "rev_date", "rev_author", + "wcprop_changes", "remove_lock", "digest", "remove_changelist", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oblzz|Obz#b", kwnames, + &py_path, &recurse, &new_revnum, &rev_date, + &rev_author, &py_wcprop_changes, + &remove_lock, &digest, &digest_len, &remove_changelist)) + return NULL; + + PyErr_WarnEx(PyExc_DeprecationWarning, "process_committed is deprecated. Use process_committed_queue instead.", 2); + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + if (!py_dict_to_wcprop_changes(py_wcprop_changes, temp_pool, &wcprop_changes)) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed4( + path, admobj->adm, recurse, new_revnum, + rev_date, rev_author, wcprop_changes, + remove_lock, remove_changelist?TRUE:FALSE, digest, temp_pool)); +#else + if (remove_changelist) { + PyErr_SetString(PyExc_NotImplementedError, "remove_changelist only supported in svn < 1.6"); + apr_pool_destroy(temp_pool); + return NULL; + } + RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(path, admobj->adm, recurse, new_revnum, + rev_date, rev_author, wcprop_changes, + remove_lock, digest, temp_pool)); +#endif + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *adm_close(PyObject *self) +{ + AdmObject *admobj = (AdmObject *)self; + if (admobj->adm != NULL) { +#if ONLY_SINCE_SVN(1, 6) + apr_pool_t *temp_pool = Pool(NULL); + Py_BEGIN_ALLOW_THREADS + svn_wc_adm_close2(admobj->adm, temp_pool); + apr_pool_destroy(temp_pool); +#else + Py_BEGIN_ALLOW_THREADS + svn_wc_adm_close(admobj->adm); +#endif + Py_END_ALLOW_THREADS + admobj->adm = NULL; + } + + Py_RETURN_NONE; +} + +static void adm_dealloc(PyObject *self) +{ + apr_pool_destroy(((AdmObject *)self)->pool); + PyObject_Del(self); +} + +static PyObject *adm_repr(PyObject *self) +{ + AdmObject *admobj = (AdmObject *)self; + + if (admobj->adm == NULL) { + return PyRepr_FromFormat("<wc.WorkingCopy (closed) at 0x%p>", admobj); + } else { + return PyRepr_FromFormat("<wc.WorkingCopy at '%s'>", + svn_wc_adm_access_path(admobj->adm)); + } +} + +static PyObject *adm_remove_lock(PyObject *self, PyObject *args) +{ + const char *path; + PyObject *py_path; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_remove_lock(path, admobj->adm, temp_pool)) + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *get_ancestry(PyObject *self, PyObject *args) +{ + const char *path; + char *url; + svn_revnum_t rev; + apr_pool_t *temp_pool; + PyObject *py_path; + AdmObject *admobj = (AdmObject *)self; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_ancestry(&url, &rev, path, admobj->adm, temp_pool)); + + apr_pool_destroy(temp_pool); + + return Py_BuildValue("(si)", url, rev); +} + +static PyObject *maybe_set_repos_root(PyObject *self, PyObject *args) +{ + char *path, *repos; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + + if (!PyArg_ParseTuple(args, "ss", &path, &repos)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_maybe_set_repos_root(admobj->adm, path, repos, temp_pool)); + + Py_RETURN_NONE; +} + +static PyObject *add_repos_file(PyObject *self, PyObject *args, PyObject *kwargs) +{ + char *kwnames[] = { "dst_path", "new_base_contents", "new_contents", + "new_base_props", "new_props", "copyfrom_url", "copyfrom_rev", + "notify", NULL }; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_dst_path; + char *copyfrom_url = NULL; + svn_revnum_t copyfrom_rev = -1; + PyObject *py_new_base_contents, *py_new_contents, *py_new_base_props, + *py_new_props, *notify = Py_None; +#if ONLY_SINCE_SVN(1, 6) + apr_pool_t *temp_pool; + const char *dst_path; + svn_stream_t *new_contents, *new_base_contents; + apr_hash_t *new_props, *new_base_props; +#endif + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOOOO|zlO", kwnames, + &py_dst_path, &py_new_base_contents, &py_new_contents, &py_new_base_props, + &py_new_props, ©from_url, ©from_rev, ¬ify)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + +#if ONLY_SINCE_SVN(1, 6) + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + new_base_props = prop_dict_to_hash(temp_pool, py_new_base_props); + + new_props = prop_dict_to_hash(temp_pool, py_new_props); + + new_base_contents = new_py_stream(temp_pool, py_new_base_contents); + + new_contents = new_py_stream(temp_pool, py_new_contents); + + dst_path = py_object_to_svn_abspath(py_dst_path, temp_pool); + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_add_repos_file3(dst_path, admobj->adm, + new_base_contents, + new_contents, + new_base_props, + new_props, + copyfrom_url, copyfrom_rev, + py_cancel_check, NULL, + py_wc_notify_func, notify, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; + +#else + PyErr_SetString(PyExc_NotImplementedError, + "add_repos_file3 not supported on svn < 1.6"); + return NULL; +#endif +} + +static PyObject *mark_missing_deleted(PyObject *self, PyObject *args) +{ + const char *path; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_mark_missing_deleted(path, admobj->adm, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *remove_from_revision_control(PyObject *self, PyObject *args) +{ + char *name; + bool destroy_wf = false, instant_error = false; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + + if (!PyArg_ParseTuple(args, "s|bb", &name, &destroy_wf, &instant_error)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_remove_from_revision_control(admobj->adm, name, + destroy_wf?TRUE:FALSE, instant_error?TRUE:FALSE, py_cancel_check, NULL, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *relocate(PyObject *self, PyObject *args) +{ + const char *path; + char *from, *to; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + bool recurse = true; + PyObject *py_validator = Py_None, *py_path; + + if (!PyArg_ParseTuple(args, "Oss|bO:relocate", &py_path, &from, &to, &recurse, + &py_validator)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_relocate3(path, admobj->adm, from, to, recurse?TRUE:FALSE, wc_validator3, py_validator, temp_pool)); +#else + RUN_SVN_WITH_POOL(temp_pool, svn_wc_relocate2(path, admobj->adm, from, to, recurse?TRUE:FALSE, wc_validator2, py_validator, temp_pool)); +#endif + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *crop_tree(PyObject *self, PyObject *args) +{ + char *target; + svn_depth_t depth; + PyObject *notify; +#if ONLY_SINCE_SVN(1, 6) + apr_pool_t *temp_pool; +#endif + AdmObject *admobj = (AdmObject *)self; + + if (!PyArg_ParseTuple(args, "si|O", &target, &depth, ¬ify)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + +#if ONLY_SINCE_SVN(1, 6) + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_crop_tree(admobj->adm, + target, depth, py_wc_notify_func, notify, + py_cancel_check, NULL, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +#else + PyErr_SetString(PyExc_NotImplementedError, + "crop_tree only available on subversion < 1.6"); + return NULL; +#endif +} + +static PyObject *translated_stream(PyObject *self, PyObject *args) +{ + const char *path, *versioned_file; + StreamObject *ret; + svn_stream_t *stream; + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *stream_pool; + PyObject *py_path, *py_versioned_file; + int flags; + + if (!PyArg_ParseTuple(args, "OOi", &py_path, &py_versioned_file, &flags)) + return NULL; + +#if ONLY_SINCE_SVN(1, 5) + ADM_CHECK_CLOSED(admobj); + + stream_pool = Pool(NULL); + if (stream_pool == NULL) + return NULL; + + path = py_object_to_svn_abspath(py_path, stream_pool); + if (path == NULL) { + apr_pool_destroy(stream_pool); + return NULL; + } + + versioned_file = py_object_to_svn_abspath(py_versioned_file, stream_pool); + if (versioned_file == NULL) { + apr_pool_destroy(stream_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(stream_pool, + svn_wc_translated_stream(&stream, path, versioned_file, admobj->adm, + flags, stream_pool)); + + ret = PyObject_New(StreamObject, &Stream_Type); + if (ret == NULL) + return NULL; + + ret->pool = stream_pool; + ret->closed = FALSE; + ret->stream = stream; + + return (PyObject *)ret; +#else + PyErr_SetString(PyExc_NotImplementedError, + "translated_stream() is only available on Subversion >= 1.5"); + return NULL; +#endif +} + +static PyObject *adm_text_modified(PyObject *self, PyObject *args) +{ + const char *path; + bool force_comparison = false; + apr_pool_t *temp_pool; + svn_boolean_t ret; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O|b", &py_path, &force_comparison)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_text_modified_p(&ret, path, force_comparison?TRUE:FALSE, admobj->adm, + temp_pool)); + + apr_pool_destroy(temp_pool); + + return PyBool_FromLong(ret); +} + +static PyObject *adm_props_modified(PyObject *self, PyObject *args) +{ + const char *path; + apr_pool_t *temp_pool; + svn_boolean_t ret; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_props_modified_p(&ret, path, admobj->adm, temp_pool)); + + apr_pool_destroy(temp_pool); + + return PyBool_FromLong(ret); +} + +static PyObject *adm_process_committed_queue(PyObject *self, PyObject *args) +{ + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + svn_revnum_t revnum; + char *date, *author; + PyObject *py_queue; + + if (!PyArg_ParseTuple(args, "O!lss", &CommittedQueue_Type, &py_queue, + &revnum, &date, &author)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) + return NULL; + + svn_wc_committed_queue_t *committed_queue = PyObject_GetCommittedQueue(py_queue); + +#if ONLY_SINCE_SVN(1, 5) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed_queue(committed_queue, admobj->adm, revnum, date, author, temp_pool)); +#else + { + int i; + for (i = 0; i < py_queue->queue->queue->nelts; i++) { + committed_queue_item_t *cqi = APR_ARRAY_IDX(committed_queue->queue, i, + committed_queue_item_t *); + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(cqi->path, admobj->adm, + cqi->recurse, revnum, date, author, cqi->wcprop_changes, + cqi->remove_lock, cqi->digest, temp_pool)); + } + } +#endif + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + + +static PyObject *is_wc_root(PyObject *self, PyObject *args) +{ + const char *path; + svn_boolean_t wc_root; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_is_wc_root(&wc_root, path, admobj->adm, temp_pool)); + + apr_pool_destroy(temp_pool); + + return PyBool_FromLong(wc_root); +} + +static PyObject *transmit_text_deltas(PyObject *self, PyObject *args) +{ + const char *path; + const char *tempfile; + bool fulltext; + PyObject *editor_obj, *py_digest, *py_path; + unsigned char digest[APR_MD5_DIGESTSIZE]; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "ObO", &py_path, &fulltext, &editor_obj)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + Py_INCREF(editor_obj); + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_transmit_text_deltas2(&tempfile, digest, + path, admobj->adm, fulltext?TRUE:FALSE, + &py_editor, editor_obj, temp_pool)); + + py_digest = PyBytes_FromStringAndSize((char *)digest, APR_MD5_DIGESTSIZE); + if (py_digest == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + ret = Py_BuildValue("sN", tempfile, py_digest); + if (ret == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + apr_pool_destroy(temp_pool); + + return ret; +} + +static PyObject *transmit_prop_deltas(PyObject *self, PyObject *args) +{ + const char *path; + PyObject *editor_obj; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_path; + EntryObject *py_entry; + + if (!PyArg_ParseTuple(args, "OO!O", &py_path, &Entry_Type, &py_entry, &editor_obj)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + Py_INCREF(editor_obj); + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_transmit_prop_deltas(path, + admobj->adm, &(py_entry->entry), &py_editor, editor_obj, NULL, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *retrieve(PyObject *self, PyObject *args) +{ + const char *path; + svn_wc_adm_access_t *result; + PyObject *py_path; + AdmObject *admobj = (AdmObject *)self, *ret; + apr_pool_t *pool; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + pool = Pool(NULL); + if (pool == NULL) + return NULL; + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_adm_retrieve(&result, admobj->adm, + path, pool)); + + ret = PyObject_New(AdmObject, &Adm_Type); + if (ret == NULL) + return NULL; + + ret->pool = pool; + ret->adm = result; + + return (PyObject *)ret; +} + +static PyObject *probe_retrieve(PyObject *self, PyObject *args) +{ + const char *path; + svn_wc_adm_access_t *result; + AdmObject *admobj = (AdmObject *)self, *ret; + apr_pool_t *pool; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + pool = Pool(NULL); + if (pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_adm_probe_retrieve(&result, admobj->adm, + path, pool)); + + ret = PyObject_New(AdmObject, &Adm_Type); + if (ret == NULL) + return NULL; + + ret->pool = pool; + ret->adm = result; + + return (PyObject *)ret; +} + +static PyObject *probe_try(PyObject *self, PyObject *args) +{ + const char *path; + svn_wc_adm_access_t *result = NULL; + AdmObject *admobj = (AdmObject *)self, *ret; + apr_pool_t *pool; + int levels_to_lock = -1; + bool writelock = false; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O|bi", &py_path, &writelock, &levels_to_lock)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + pool = Pool(NULL); + if (pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, pool); + if (path == NULL) { + apr_pool_destroy(pool); + return NULL; + } + + RUN_SVN_WITH_POOL(pool, svn_wc_adm_probe_try3(&result, admobj->adm, + path, writelock, levels_to_lock, + py_cancel_check, NULL, pool)); + + if (result == NULL) { + apr_pool_destroy(pool); + Py_RETURN_NONE; + } + + ret = PyObject_New(AdmObject, &Adm_Type); + if (ret == NULL) + return NULL; + + ret->pool = pool; + ret->adm = result; + + return (PyObject *)ret; +} + +static PyObject *resolved_conflict(PyObject *self, PyObject *args) +{ + AdmObject *admobj = (AdmObject *)self; + apr_pool_t *temp_pool; + bool resolve_props, resolve_tree, resolve_text; + int depth; +#if ONLY_SINCE_SVN(1, 5) + svn_wc_conflict_choice_t conflict_choice; +#else + int conflict_choice; +#endif + PyObject *notify_func = Py_None; + const char *path; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "Obbbii|O", &py_path, &resolve_text, + &resolve_props, &resolve_tree, &depth, + &conflict_choice, ¬ify_func)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_resolved_conflict4(path, admobj->adm, resolve_text?TRUE:FALSE, + resolve_props?TRUE:FALSE, resolve_tree?TRUE:FALSE, depth, + conflict_choice, py_wc_notify_func, + (void *)notify_func, py_cancel_check, + NULL, temp_pool)); +#elif ONLY_SINCE_SVN(1, 5) + if (resolve_tree) { + PyErr_SetString(PyExc_NotImplementedError, + "resolve_tree not supported with svn < 1.6"); + apr_pool_destroy(temp_pool); + return NULL; + } else { + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_resolved_conflict3(path, admobj->adm, resolve_text?TRUE:FALSE, + resolve_props?TRUE:FALSE, depth, + conflict_choice, py_wc_notify_func, + (void *)notify_func, py_cancel_check, + NULL, temp_pool)); + } +#else + if (resolve_tree) { + PyErr_SetString(PyExc_NotImplementedError, + "resolve_tree not supported with svn < 1.6"); + apr_pool_destroy(temp_pool); + return NULL; + } else if (depth != svn_depth_infinity && depth != svn_depth_files) { + PyErr_SetString(PyExc_NotImplementedError, + "only infinity and files values for depth are supported"); + apr_pool_destroy(temp_pool); + return NULL; + } else if (conflict_choice != 0) { + PyErr_SetString(PyExc_NotImplementedError, + "conflict choice not supported with svn < 1.5"); + apr_pool_destroy(temp_pool); + return NULL; + } else { + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_resolved_conflict2(path, admobj->adm, resolve_text?TRUE:FALSE, + resolve_props?TRUE:FALSE, + (depth == svn_depth_infinity), + py_wc_notify_func, + (void *)notify_func, py_cancel_check, + NULL, + temp_pool)); + } +#endif + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *conflicted(PyObject *self, PyObject *args) +{ + const char *path; + apr_pool_t *temp_pool; + PyObject *ret; + AdmObject *admobj = (AdmObject *)self; + svn_boolean_t text_conflicted, prop_conflicted; + PyObject *py_path; +#if ONLY_BEFORE_SVN(1, 6) + const svn_wc_entry_t *entry; +#else + svn_boolean_t tree_conflicted; +#endif + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + +#if ONLY_SINCE_SVN(1, 6) + RUN_SVN_WITH_POOL(temp_pool, svn_wc_conflicted_p2(&text_conflicted, + &prop_conflicted, &tree_conflicted, path, admobj->adm, temp_pool)); + + ret = Py_BuildValue("(bbb)", text_conflicted, prop_conflicted, tree_conflicted); +#else + RUN_SVN_WITH_POOL(temp_pool, svn_wc_entry(&entry, path, admobj->adm, TRUE, temp_pool)); + + RUN_SVN_WITH_POOL(temp_pool, svn_wc_conflicted_p(&text_conflicted, + &prop_conflicted, path, entry, temp_pool)); + + ret = Py_BuildValue("(bbO)", text_conflicted, prop_conflicted, Py_None); +#endif + + apr_pool_destroy(temp_pool); + + return ret; +} + +/** + * Determine the status of a file in the specified working copy. + * + * :return: A status object. + */ +static PyObject *wc_status(PyObject *self, PyObject *args) +{ + const char *path; + svn_wc_status2_t *st; + apr_pool_t *temp_pool; + PyObject *ret; + AdmObject *admobj = (AdmObject *)self; + PyObject *py_path; + + if (!PyArg_ParseTuple(args, "O", &py_path)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_status2(&st, path, admobj->adm, temp_pool)); + + ret = py_wc_status2(st); + + apr_pool_destroy(temp_pool); + + return (PyObject*)ret; +} + +static PyObject *wc_add_lock(PyObject *self, PyObject *args) +{ + const char *path; + apr_pool_t *temp_pool; + AdmObject *admobj = (AdmObject *)self; + svn_lock_t *lock; + PyObject *py_path, *py_lock; + + if (!PyArg_ParseTuple(args, "OO!", &py_path, &Lock_Type, &py_lock)) + return NULL; + + ADM_CHECK_CLOSED(admobj); + + temp_pool = Pool(NULL); + if (temp_pool == NULL) { + return NULL; + } + + path = py_object_to_svn_abspath(py_path, temp_pool); + if (path == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + lock = py_object_to_svn_lock(py_lock, temp_pool); + if (lock == NULL) { + apr_pool_destroy(temp_pool); + return NULL; + } + + RUN_SVN_WITH_POOL(temp_pool, + svn_wc_add_lock(path, lock, admobj->adm, temp_pool)); + + apr_pool_destroy(temp_pool); + + Py_RETURN_NONE; +} + +static PyObject *wc_enter(PyObject *self) +{ + Py_INCREF(self); + return self; +} + +static PyObject *wc_exit(PyObject *self, PyObject *args) +{ + PyObject *exc_type, *exc_value, *exc_tb, *ret; + + if (!PyArg_ParseTuple(args, "OOO", &exc_type, &exc_value, &exc_tb)) + return NULL; + + ret = adm_close(self); + if (ret == NULL) { + return NULL; + } + + Py_RETURN_NONE; +} + +static PyMethodDef adm_methods[] = { + { "prop_set", adm_prop_set, METH_VARARGS, "S.prop_set(name, value, path, skip_checks=False)" }, + { "access_path", (PyCFunction)adm_access_path, METH_NOARGS, + "S.access_path() -> path\n" + "Returns the base path for this working copy handle." }, + { "prop_get", adm_prop_get, METH_VARARGS, "S.prop_get(name, path) -> value" }, + { "entries_read", adm_entries_read, METH_VARARGS, "S.entries_read(include_hidden=False) -> dict" }, + { "walk_entries", adm_walk_entries, METH_VARARGS, + "S.walk_entries(path, callback, show_hidden=False)\n" + "callback should be a function that takes a path and a wc entry" }, + { "locked", (PyCFunction)adm_locked, METH_NOARGS, + "S.locked() -> bool" }, + { "get_prop_diffs", adm_get_prop_diffs, METH_VARARGS, + "S.get_prop_diffs(path) -> (propchanges, originalprops)" }, + { "add", (PyCFunction)adm_add, METH_VARARGS|METH_KEYWORDS, "S.add(path, copyfrom_url=None, copyfrom_rev=-1, notify_func=None)" }, + { "copy", adm_copy, METH_VARARGS, "S.copy(src_path, dest_path, notify_func=None)" }, + { "delete", (PyCFunction)adm_delete, METH_VARARGS|METH_KEYWORDS, "S.delete(path, notify_func=None, keep_local=False)" }, + { "crawl_revisions", (PyCFunction)adm_crawl_revisions, METH_VARARGS|METH_KEYWORDS, + "S.crawl_revisions(path, reporter, restore_files=True, recurse=True, use_commit_times=True, notify_func=None) -> None" }, + { "get_update_editor", (PyCFunction)adm_get_update_editor, + METH_VARARGS|METH_KEYWORDS, NULL }, + { "get_switch_editor", (PyCFunction)adm_get_switch_editor, + METH_VARARGS|METH_KEYWORDS, NULL }, + { "close", (PyCFunction)adm_close, METH_NOARGS, + "S.close()" }, + { "entry", (PyCFunction)adm_entry, METH_VARARGS, + "s.entry(path, show_hidden=False) -> entry" }, + { "process_committed", (PyCFunction)adm_process_committed, METH_VARARGS|METH_KEYWORDS, "S.process_committed(path, recurse, new_revnum, rev_date, rev_author, wcprop_changes=None, remove_lock=False, digest=None)" }, + { "process_committed_queue", (PyCFunction)adm_process_committed_queue, METH_VARARGS, "S.process_committed_queue(queue, new_revnum, rev_date, rev_author)" }, + { "remove_lock", (PyCFunction)adm_remove_lock, METH_VARARGS, "S.remove_lock(path)" }, + { "has_binary_prop", (PyCFunction)adm_has_binary_prop, METH_VARARGS, "S.has_binary_prop(path) -> bool" }, + { "text_modified", (PyCFunction)adm_text_modified, METH_VARARGS, "S.text_modified(filename, force_comparison=False) -> bool" }, + { "props_modified", (PyCFunction)adm_props_modified, METH_VARARGS, "S.props_modified(filename) -> bool" }, + { "get_ancestry", (PyCFunction)get_ancestry, METH_VARARGS, + "S.get_ancestry(path) -> (url, rev)" }, + { "maybe_set_repos_root", (PyCFunction)maybe_set_repos_root, METH_VARARGS, "S.maybe_set_repos_root(path, repos)" }, + { "add_repos_file", (PyCFunction)add_repos_file, METH_KEYWORDS|METH_VARARGS, + "S.add_repos_file(dst_path, new_base_contents, new_contents, new_base_props, new_props, copyfrom_url=None, copyfrom_rev=-1, notify_func=None)" }, + { "mark_missing_deleted", (PyCFunction)mark_missing_deleted, METH_VARARGS, + "S.mark_missing_deleted(path)" }, + { "remove_from_revision_control", (PyCFunction)remove_from_revision_control, METH_VARARGS, + "S.remove_from_revision_control(name, destroy_wf=False, instant_error=False)" }, + { "relocate", (PyCFunction)relocate, METH_VARARGS, + "S.relocate(path, from, to, recurse=TRUE, validator=None)" }, + { "crop_tree", (PyCFunction)crop_tree, METH_VARARGS, + "S.crop_tree(target, depth, notify_func=None, cancel=None)" }, + { "translated_stream", (PyCFunction)translated_stream, METH_VARARGS, + "S.translated_stream(path, versioned_file, flags) -> stream" }, + { "is_wc_root", (PyCFunction)is_wc_root, METH_VARARGS, + "S.is_wc_root(path) -> wc_root" }, + { "transmit_text_deltas", (PyCFunction)transmit_text_deltas, METH_VARARGS, + "S.transmit_text_deltas(fulltext, editor) -> (tempfile, digest)" }, + { "transmit_prop_deltas", (PyCFunction)transmit_prop_deltas, METH_VARARGS, + "S.transmit_prop_deltas(path, entry, editor)" }, + { "probe_retrieve", (PyCFunction)probe_retrieve, METH_VARARGS, + "S.probe_retrieve(path) -> WorkingCopy" }, + { "retrieve", (PyCFunction)retrieve, METH_VARARGS, + "S.retrieve(path) -> WorkingCopy" }, + { "probe_try", (PyCFunction)probe_try, METH_VARARGS, + "S.probe_try(path, write_lock=False, levels_to_lock=-1)" }, + { "conflicted", (PyCFunction)conflicted, METH_VARARGS, + "S.conflicted(path) -> (text_conflicted, prop_conflicted, tree_conflicted)" }, + { "resolved_conflict", (PyCFunction)resolved_conflict, METH_VARARGS, + "S.resolved_conflict(path, resolve_text, resolve_props, resolve_tree, depth, conflict_choice, notify_func=None, cancel=None)" }, + { "status", (PyCFunction)wc_status, METH_VARARGS, "status(wc_path) -> Status" }, + { "add_lock", (PyCFunction)wc_add_lock, METH_VARARGS, "add_lock(path, lock)" }, + { "__enter__", (PyCFunction)wc_enter, METH_NOARGS, "__enter__() -> self" }, + { "__exit__", (PyCFunction)wc_exit, METH_VARARGS, "__exit__(exc_type, exc_value, exc_tb)" }, + { NULL, } +}; + +PyTypeObject Adm_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "wc.WorkingCopy", /* const char *tp_name; For printing, in format "<module>.<name>" */ + sizeof(AdmObject), + 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ + + /* Methods to implement standard operations */ + + adm_dealloc, /* destructor tp_dealloc; */ + NULL, /* printfunc tp_print; */ + NULL, /* getattrfunc tp_getattr; */ + NULL, /* setattrfunc tp_setattr; */ + NULL, /* cmpfunc tp_compare; */ + adm_repr, /* reprfunc tp_repr; */ + + /* Method suites for standard classes */ + + NULL, /* PyNumberMethods *tp_as_number; */ + NULL, /* PySequenceMethods *tp_as_sequence; */ + NULL, /* PyMappingMethods *tp_as_mapping; */ + + /* More standard operations (here for binary compatibility) */ + + NULL, /* hashfunc tp_hash; */ + NULL, /* ternaryfunc tp_call; */ + adm_repr, /* reprfunc tp_repr; */ + NULL, /* getattrofunc tp_getattro; */ + NULL, /* setattrofunc tp_setattro; */ + + /* Functions to access object as input/output buffer */ + NULL, /* PyBufferProcs *tp_as_buffer; */ + + /* Flags to define presence of optional/expanded features */ + 0, /* long tp_flags; */ + + "Local working copy", /* const char *tp_doc; Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + NULL, /* traverseproc tp_traverse; */ + + /* delete references to contained objects */ + NULL, /* inquiry tp_clear; */ + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + NULL, /* richcmpfunc tp_richcompare; */ + + /* weak reference enabler */ + 0, /* Py_ssize_t tp_weaklistoffset; */ + + /* Added in release 2.2 */ + /* Iterators */ + NULL, /* getiterfunc tp_iter; */ + NULL, /* iternextfunc tp_iternext; */ + + /* Attribute descriptor and subclassing stuff */ + adm_methods, /* struct PyMethodDef *tp_methods; */ + NULL, /* struct PyMemberDef *tp_members; */ + NULL, /* struct PyGetSetDef *tp_getset; */ + NULL, /* struct _typeobject *tp_base; */ + NULL, /* PyObject *tp_dict; */ + NULL, /* descrgetfunc tp_descr_get; */ + NULL, /* descrsetfunc tp_descr_set; */ + 0, /* Py_ssize_t tp_dictoffset; */ + NULL, /* initproc tp_init; */ + NULL, /* allocfunc tp_alloc; */ + adm_init, /* newfunc tp_new; */ +}; + +static void entry_dealloc(PyObject *self) +{ + apr_pool_destroy(((EntryObject *)self)->pool); + PyObject_Del(self); +} + +static PyMemberDef entry_members[] = { + { "name", T_STRING, offsetof(EntryObject, entry.name), READONLY, + "Name of the file"}, + { "copyfrom_url", T_STRING, offsetof(EntryObject, entry.copyfrom_url), READONLY, + "Copyfrom location" }, + { "copyfrom_rev", T_LONG, offsetof(EntryObject, entry.copyfrom_rev), READONLY, + "Copyfrom revision" }, + { "uuid", T_STRING, offsetof(EntryObject, entry.uuid), READONLY, + "UUID of repository" }, + { "url", T_STRING, offsetof(EntryObject, entry.url), READONLY, + "URL in repository" }, + { "repos", T_STRING, offsetof(EntryObject, entry.repos), READONLY, + "Canonical repository URL" }, + { "schedule", T_INT, offsetof(EntryObject, entry.schedule), READONLY, + "Scheduling (add, replace, delete, etc)" }, + { "kind", T_INT, offsetof(EntryObject, entry.kind), READONLY, + "Kind of file (file, dir, etc)" }, + { "revision", T_LONG, offsetof(EntryObject, entry.revision), READONLY, + "Base revision", }, + { "cmt_rev", T_LONG, offsetof(EntryObject, entry.cmt_rev), READONLY, + "Last revision this was changed" }, + { "checksum", T_STRING, offsetof(EntryObject, entry.checksum), READONLY, + "Hex MD5 checksum for the untranslated text base file" }, + { "cmt_date", T_LONG, offsetof(EntryObject, entry.cmt_date), READONLY, + "Last date this was changed" }, + { "cmt_author", T_STRING, offsetof(EntryObject, entry.cmt_author), READONLY, + "Last commit author of this item" }, + { NULL, } +}; + +PyTypeObject Entry_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "wc.Entry", /* const char *tp_name; For printing, in format "<module>.<name>" */ + sizeof(EntryObject), + 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ + + /* Methods to implement standard operations */ + + entry_dealloc, /* destructor tp_dealloc; */ + NULL, /* printfunc tp_print; */ + NULL, /* getattrfunc tp_getattr; */ + NULL, /* setattrfunc tp_setattr; */ + NULL, /* cmpfunc tp_compare; */ + NULL, /* reprfunc tp_repr; */ + + /* Method suites for standard classes */ + + NULL, /* PyNumberMethods *tp_as_number; */ + NULL, /* PySequenceMethods *tp_as_sequence; */ + NULL, /* PyMappingMethods *tp_as_mapping; */ + + /* More standard operations (here for binary compatibility) */ + + NULL, /* hashfunc tp_hash; */ + NULL, /* ternaryfunc tp_call; */ + NULL, /* reprfunc tp_str; */ + NULL, /* getattrofunc tp_getattro; */ + NULL, /* setattrofunc tp_setattro; */ + + /* Functions to access object as input/output buffer */ + NULL, /* PyBufferProcs *tp_as_buffer; */ + + /* Flags to define presence of optional/expanded features */ + 0, /* long tp_flags; */ + + NULL, /* const char *tp_doc; Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + NULL, /* traverseproc tp_traverse; */ + + /* delete references to contained objects */ + NULL, /* inquiry tp_clear; */ + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + NULL, /* richcmpfunc tp_richcompare; */ + + /* weak reference enabler */ + 0, /* Py_ssize_t tp_weaklistoffset; */ + + /* Added in release 2.2 */ + /* Iterators */ + NULL, /* getiterfunc tp_iter; */ + NULL, /* iternextfunc tp_iternext; */ + + /* Attribute descriptor and subclassing stuff */ + NULL, /* struct PyMethodDef *tp_methods; */ + entry_members, /* struct PyMemberDef *tp_members; */ + +}; + +static PyObject *py_entry(const svn_wc_entry_t *entry) +{ + EntryObject *ret; + + if (entry == NULL) { + Py_RETURN_NONE; + } + + ret = PyObject_New(EntryObject, &Entry_Type); + if (ret == NULL) + return NULL; + + ret->pool = Pool(NULL); + if (ret->pool == NULL) + return NULL; + ret->entry = *svn_wc_entry_dup(entry, ret->pool); + return (PyObject *)ret; +} + +typedef struct { + PyObject_VAR_HEAD + apr_pool_t *pool; + svn_wc_status2_t status; + PyObject *entry; +} Status2Object; + +static void status_dealloc(PyObject *self) +{ + apr_pool_destroy(((Status2Object *)self)->pool); + Py_XDECREF(((Status2Object *)self)->entry); + PyObject_Del(self); +} + +static PyMemberDef status_members[] = { + { "entry", T_OBJECT, offsetof(Status2Object, entry), READONLY, + "Can be NULL if not under version control." }, + { "locked", T_BOOL, offsetof(Status2Object, status.locked), READONLY, + "a directory can be 'locked' if a working copy update was interrupted." }, + { "copied", T_BOOL, offsetof(Status2Object, status.copied), READONLY, + "a file or directory can be 'copied' if it's scheduled for addition-with-history (or part of a subtree that is scheduled as such.)." }, + { "switched", T_BOOL, offsetof(Status2Object, status.switched), READONLY, + "a file or directory can be 'switched' if the switch command has been used." }, + { "url", T_STRING, offsetof(Status2Object, status.url), READONLY, + "URL (actual or expected) in repository" }, + { "revision", T_LONG, offsetof(Status2Object, status.ood_last_cmt_rev), READONLY, + "Set to the youngest committed revision, or SVN_INVALID_REVNUM if not out of date.", }, + { "kind", T_INT, offsetof(Status2Object, status.ood_kind), READONLY, + "Set to the node kind of the youngest commit, or svn_node_none if not out of date.", }, + { "status", T_INT, offsetof(Status2Object, status.text_status), READONLY, + "The status of the entry.", }, + { NULL, } +}; + +PyTypeObject Status2_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "wc.Status", /* const char *tp_name; For printing, in format "<module>.<name>" */ + sizeof(Status2Object), + 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ + + /* Methods to implement standard operations */ + + status_dealloc, /* destructor tp_dealloc; */ + NULL, /* printfunc tp_print; */ + NULL, /* getattrfunc tp_getattr; */ + NULL, /* setattrfunc tp_setattr; */ + NULL, /* cmpfunc tp_compare; */ + NULL, /* reprfunc tp_repr; */ + + /* Method suites for standard classes */ + + NULL, /* PyNumberMethods *tp_as_number; */ + NULL, /* PySequenceMethods *tp_as_sequence; */ + NULL, /* PyMappingMethods *tp_as_mapping; */ + + /* More standard operations (here for binary compatibility) */ + + NULL, /* hashfunc tp_hash; */ + NULL, /* ternaryfunc tp_call; */ + NULL, /* reprfunc tp_str; */ + NULL, /* getattrofunc tp_getattro; */ + NULL, /* setattrofunc tp_setattro; */ + + /* Functions to access object as input/output buffer */ + NULL, /* PyBufferProcs *tp_as_buffer; */ + + /* Flags to define presence of optional/expanded features */ + 0, /* long tp_flags; */ + + "Working copy status object", /* const char *tp_doc; Documentation string */ + + /* Assigned meaning in release 2.0 */ + /* call function for all accessible objects */ + NULL, /* traverseproc tp_traverse; */ + + /* delete references to contained objects */ + NULL, /* inquiry tp_clear; */ + + /* Assigned meaning in release 2.1 */ + /* rich comparisons */ + NULL, /* richcmpfunc tp_richcompare; */ + + /* weak reference enabler */ + 0, /* Py_ssize_t tp_weaklistoffset; */ + + /* Added in release 2.2 */ + /* Iterators */ + NULL, /* getiterfunc tp_iter; */ + NULL, /* iternextfunc tp_iternext; */ + + /* Attribute descriptor and subclassing stuff */ + NULL, /* struct PyMethodDef *tp_methods; */ + status_members, /* struct PyMemberDef *tp_members; */ + +}; + +PyObject *py_wc_status2(svn_wc_status2_t *status) +{ + Status2Object *ret; + svn_wc_status2_t *dup_status; + + ret = PyObject_New(Status2Object, &Status2_Type); + if (ret == NULL) + return NULL; + + ret->pool = Pool(NULL); + if (ret->pool == NULL) { + PyObject_Del(ret); + return NULL; + } + + dup_status = svn_wc_dup_status2(status, ret->pool); + if (dup_status == NULL) + { + PyErr_NoMemory(); + return NULL; + } + ret->status = *dup_status; + + ret->entry = py_entry(ret->status.entry); + return (PyObject *)ret; +} + +static svn_error_t *py_wc_found_entry(const char *path, const svn_wc_entry_t *entry, void *walk_baton, apr_pool_t *pool) +{ + PyObject *fn, *ret; + PyObject *callbacks = (PyObject *)walk_baton; + PyGILState_STATE state = PyGILState_Ensure(); + if (PyTuple_Check(callbacks)) { + fn = PyTuple_GET_ITEM(callbacks, 0); + } else { + fn = (PyObject *)walk_baton; + } + ret = PyObject_CallFunction(fn, "sO", path, py_entry(entry)); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + return NULL; +} + +#if ONLY_SINCE_SVN(1, 5) + +svn_error_t *py_wc_handle_error(const char *path, svn_error_t *err, void *walk_baton, apr_pool_t *pool) +{ + PyObject *fn, *ret; + PyObject *py_err; + PyGILState_STATE state; + PyObject *callbacks = (PyObject *)walk_baton; + if (PyTuple_Check(callbacks)) { + fn = PyTuple_GET_ITEM(callbacks, 1); + } else { + return err; + } + state = PyGILState_Ensure(); + py_err = PyErr_NewSubversionException(err); + ret = PyObject_CallFunction(fn, "sO", path, py_err); + CB_CHECK_PYRETVAL(ret); + Py_DECREF(ret); + PyGILState_Release(state); + Py_DECREF(py_err); + return NULL; +} + +static svn_wc_entry_callbacks2_t py_wc_entry_callbacks2 = { + py_wc_found_entry, + py_wc_handle_error, +}; +#else +static svn_wc_entry_callbacks_t py_wc_entry_callbacks = { + py_wc_found_entry +}; +#endif + +#pragma GCC diagnostic pop diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..311abc92 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +downloadcache = {toxworkdir}/cache/ +envlist = py27, py35 + +[testenv] + +commands = make check +recreate = True +whitelist_externals = make |